ffmd/ffmd.go

147 lines
2.9 KiB
Go

package ffmd
import (
"bytes"
_ "embed"
"flag"
"fmt"
"reflect"
"sort"
"strings"
"text/template"
"github.com/peterbourgon/ff/v3/ffcli"
)
var (
//go:embed ffmd.tmpl
ffmdtmpl string
Template = template.Must(template.New("").Parse(ffmdtmpl))
)
// FromCommand turns a ffcli.Command into markdown
func FromCommand(cmd *ffcli.Command) (string, error) {
return fromCommand(cmd, 1)
}
// FromFlagSet turns a flag.FlagSet into markdown
func FromFlagSet(fs *flag.FlagSet) (string, error) {
return flagSetCommand(fs, 2).Markdown()
}
func fromCommand(cmd *ffcli.Command, section int) (string, error) {
c := flagSetCommand(cmd.FlagSet, section)
c.Name = cmd.Name
c.Usage = fmt.Sprintf("%s [FLAGS] [ARGS...]", cmd.Name)
if cmd.ShortUsage != "" {
c.Usage = cmd.ShortUsage
}
c.Description = c.Name
if cmd.ShortHelp != "" {
c.Description = cmd.ShortHelp
}
if cmd.LongHelp != "" {
c.Description = cmd.LongHelp
}
// Only top-level gets a tree
if section == 1 {
c.Tree = Tree(cmd)
}
var md strings.Builder
s, err := c.Markdown()
if err != nil {
return "", err
}
md.WriteString(s)
md.WriteString("\n\n-----\n\n")
for _, sub := range cmd.Subcommands {
s, err = fromCommand(sub, section+1)
md.WriteString(s)
}
return md.String(), nil
}
func flagSetCommand(fs *flag.FlagSet, section int) command {
a := command{
Section: section,
Name: fs.Name(),
Description: "",
Usage: fmt.Sprintf("%s [FLAGS] [ARGS...]", fs.Name()),
Flags: []appFlag{
{
Name: "help",
Usage: "Show help",
IsBool: true,
},
},
}
fs.VisitAll(func(f *flag.Flag) {
_, isBool := f.Value.(boolFlag)
def := f.DefValue
if isZeroValue(f, def) {
def = ""
}
a.Flags = append(a.Flags, appFlag{
Name: f.Name,
Usage: f.Usage,
Default: def,
IsBool: isBool,
})
})
sort.Slice(a.Flags, func(i, j int) bool {
return a.Flags[i].Name < a.Flags[j].Name
})
return a
}
type command struct {
Section int
Name string
Description string
Usage string
Flags []appFlag
Tree string
}
func (c command) Header() string {
return strings.Repeat("#", c.Section)
}
func (c command) Markdown() (string, error) {
var buf bytes.Buffer
if err := Template.Execute(&buf, c); err != nil {
return "", err
}
return buf.String(), nil
}
type appFlag struct {
Name string
Usage string
Default string
IsBool bool
}
// From stdlib
func isZeroValue(f *flag.Flag, value string) bool {
// Build a zero value of the flag's Value type, and see if the
// result of calling its String method equals the value passed in.
// This works unless the Value type is itself an interface type.
typ := reflect.TypeOf(f.Value)
var z reflect.Value
if typ.Kind() == reflect.Ptr {
z = reflect.New(typ.Elem())
} else {
z = reflect.Zero(typ)
}
return value == z.Interface().(flag.Value).String()
}
type boolFlag interface {
flag.Value
IsBoolFlag() bool
}