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 }