gopass/helpers/man/main.go
Dominik Schulz 72c5f9454a
[chore] Add better test coverage for helpers (#3078)
* [chore] Add better test coverage for helpers

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [fix] Disable one linux specific test on windows.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [fix] Only run helper tests on Linux. Currently there are no plans to
support those on Linux or even Darwin. If you have a need for that
feel free to put in the work to make that work.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

---------

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
2025-03-04 15:14:34 +01:00

202 lines
5.1 KiB
Go

// Copyright 2021 The gopass Authors. All rights reserved.
// Use of this source code is governed by the MIT license,
// that can be found in the LICENSE file.
// Man implements a man(1) documentation generator that is run as part of the
// release helper to generate an up to date manpage for Gopass.
package main
import (
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"text/template"
"time"
"github.com/blang/semver/v4"
ap "github.com/gopasspw/gopass/internal/action"
"github.com/gopasspw/gopass/internal/action/pwgen"
_ "github.com/gopasspw/gopass/internal/backend/crypto"
_ "github.com/gopasspw/gopass/internal/backend/storage"
"github.com/gopasspw/gopass/internal/config"
"github.com/urfave/cli/v2"
)
var (
filename = "VERSION"
stdout io.Writer = os.Stdout
)
func main() {
// this is a workaround for the man helper getting accidentally
// installed into my $GOBIN dir and me not being able to figure out
// why. So instead of being greeted with an ugly panic message
// every now and then when I need to open a man page I decided
// to rather have a little bit of code to automate this away.
if len(os.Args) > 0 && os.Args[0] == "man" {
manPath, err := lookPath("man")
if err != nil {
panic(err)
}
cmd := exec.Command(manPath, os.Args[1:]...)
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
os.Exit(cmd.ProcessState.ExitCode())
}
return
}
vs, err := os.ReadFile(filename)
if err != nil {
panic(err)
}
version := semver.MustParse(strings.TrimSpace(string(vs)))
action, err := ap.New(config.New(), version)
if err != nil {
panic(err)
}
cmds := action.GetCommands()
cmds = append(cmds, pwgen.GetCommands()...)
sort.Slice(cmds, func(i, j int) bool { return cmds[i].Name < cmds[j].Name })
data := &payload{
SectionNumber: 1,
DatePretty: time.Now().UTC().Format("January 2006"),
Version: version.String(),
SectionName: "User Commands",
Commands: cmds,
Flags: getFlags(ap.ShowFlags()),
}
funcMap := template.FuncMap{
"flags": getFlags,
}
if err := template.Must(template.New("man").Funcs(funcMap).Parse(manTpl)).Execute(stdout, data); err != nil {
panic(err)
}
}
func getFlags(flags []cli.Flag) []flag {
sort.Slice(flags, func(i, j int) bool { return flags[i].Names()[0] < flags[j].Names()[0] })
out := make([]flag, 0, len(flags))
for _, f := range flags {
switch v := f.(type) {
case *cli.BoolFlag:
out = append(out, flag{
Name: v.Name,
Aliases: append([]string{v.Name}, v.Aliases...),
Description: v.Usage,
})
case *cli.IntFlag:
out = append(out, flag{
Name: v.Name,
Aliases: append([]string{v.Name}, v.Aliases...),
Description: v.Usage,
})
case *cli.StringFlag:
out = append(out, flag{
Name: v.Name,
Aliases: append([]string{v.Name}, v.Aliases...),
Description: v.Usage,
})
}
}
return out
}
type flag struct {
Name string
Aliases []string
Description string
}
type payload struct {
SectionNumber int // 1
DatePretty string // July 2020
Version string // 1.12.1
SectionName string // User Commands
Commands []*cli.Command
Flags []flag
}
// from https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/os/exec/lp_unix.go
func lookPath(file string) (string, error) {
curPath, err := os.Executable()
if err != nil {
return "", err
}
path := os.Getenv("PATH")
for _, dir := range filepath.SplitList(path) {
if dir == "" {
// Unix shell semantics: path element "" means "."
dir = "."
}
path := filepath.Join(dir, file)
// do not call ourselves
if path == curPath {
continue
}
if err := findExecutable(path); err == nil {
return path, nil
}
}
return "", fmt.Errorf("%s: executable file not found in $PATH", file)
}
func findExecutable(file string) error {
d, err := os.Stat(file)
if err != nil {
return err
}
if m := d.Mode(); !m.IsDir() && m&0o111 != 0 {
return nil
}
return fs.ErrPermission
}
var manTpl = `
.TH GOPASS "{{ .SectionNumber }}" "{{ .DatePretty }}" "gopass (github.com/gopasspw/gopass) {{ .Version }}" "{{ .SectionName }}"
.SH NAME
gopass - The standard Unix password manager
.SH SYNOPSIS
.B gopass
[\fI\,global options\/\fR] \fI\,command\/\fR [\fI\,command options\/\fR] [\fI,arguments\/\fR...]
.SH GLOBAL OPTIONS
{{ range $flag := .Flags }}
.TP{{ range $alias := $flag.Aliases }}
\fB{{ if (gt (len $alias) 1) }}\-{{ end }}\-{{ $alias }}\fR,{{ end }}
{{ $flag.Description }}{{ end }}
.SH COMMANDS
{{ range $cmd := .Commands }}
.SS {{ $cmd.Name }}
{{ $cmd.Usage }}
{{ $cmd.Description }}
{{- if $cmd.Flags }}
.B Flags
{{- range $flag := $cmd.Flags | flags }}
.TP{{ range $alias := $flag.Aliases }}
\fB{{ if (gt (len $alias) 1) }}\-{{ end }}\-{{ $alias }}\fR,{{ end }}
{{ $flag.Description }}{{ end }}
{{- end }}
{{- end}}
.SH "REPORTING BUGS"
Report bugs to <https://github.com/gopasspw/gopass/issues/new>
.SH "COPYRIGHT"
Copyright \(co 2021 Gopass Authors
This program is free software; you may redistribute it under the terms of
the MIT license. This program has absolutely no warranty.
`