| //go:build !urfave_cli_no_docs | |
| // +build !urfave_cli_no_docs | |
| package cli | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "io" | |
| "sort" | |
| "strings" | |
| "text/template" | |
| "github.com/cpuguy83/go-md2man/v2/md2man" | |
| ) | |
| // ToMarkdown creates a markdown string for the `*App` | |
| // The function errors if either parsing or writing of the string fails. | |
| func (a *App) ToMarkdown() (string, error) { | |
| var w bytes.Buffer | |
| if err := a.writeDocTemplate(&w, 0); err != nil { | |
| return "", err | |
| } | |
| return w.String(), nil | |
| } | |
| // ToMan creates a man page string with section number for the `*App` | |
| // The function errors if either parsing or writing of the string fails. | |
| func (a *App) ToManWithSection(sectionNumber int) (string, error) { | |
| var w bytes.Buffer | |
| if err := a.writeDocTemplate(&w, sectionNumber); err != nil { | |
| return "", err | |
| } | |
| man := md2man.Render(w.Bytes()) | |
| return string(man), nil | |
| } | |
| // ToMan creates a man page string for the `*App` | |
| // The function errors if either parsing or writing of the string fails. | |
| func (a *App) ToMan() (string, error) { | |
| man, err := a.ToManWithSection(8) | |
| return man, err | |
| } | |
| type cliTemplate struct { | |
| App *App | |
| SectionNum int | |
| Commands []string | |
| GlobalArgs []string | |
| SynopsisArgs []string | |
| } | |
| func (a *App) writeDocTemplate(w io.Writer, sectionNum int) error { | |
| const name = "cli" | |
| t, err := template.New(name).Parse(MarkdownDocTemplate) | |
| if err != nil { | |
| return err | |
| } | |
| return t.ExecuteTemplate(w, name, &cliTemplate{ | |
| App: a, | |
| SectionNum: sectionNum, | |
| Commands: prepareCommands(a.Commands, 0), | |
| GlobalArgs: prepareArgsWithValues(a.VisibleFlags()), | |
| SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()), | |
| }) | |
| } | |
| func prepareCommands(commands []*Command, level int) []string { | |
| var coms []string | |
| for _, command := range commands { | |
| if command.Hidden { | |
| continue | |
| } | |
| usageText := prepareUsageText(command) | |
| usage := prepareUsage(command, usageText) | |
| prepared := fmt.Sprintf("%s %s\n\n%s%s", | |
| strings.Repeat("#", level+2), | |
| strings.Join(command.Names(), ", "), | |
| usage, | |
| usageText, | |
| ) | |
| flags := prepareArgsWithValues(command.VisibleFlags()) | |
| if len(flags) > 0 { | |
| prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) | |
| } | |
| coms = append(coms, prepared) | |
| // recursively iterate subcommands | |
| if len(command.Subcommands) > 0 { | |
| coms = append( | |
| coms, | |
| prepareCommands(command.Subcommands, level+1)..., | |
| ) | |
| } | |
| } | |
| return coms | |
| } | |
| func prepareArgsWithValues(flags []Flag) []string { | |
| return prepareFlags(flags, ", ", "**", "**", `""`, true) | |
| } | |
| func prepareArgsSynopsis(flags []Flag) []string { | |
| return prepareFlags(flags, "|", "[", "]", "[value]", false) | |
| } | |
| func prepareFlags( | |
| flags []Flag, | |
| sep, opener, closer, value string, | |
| addDetails bool, | |
| ) []string { | |
| args := []string{} | |
| for _, f := range flags { | |
| flag, ok := f.(DocGenerationFlag) | |
| if !ok { | |
| continue | |
| } | |
| modifiedArg := opener | |
| for _, s := range flag.Names() { | |
| trimmed := strings.TrimSpace(s) | |
| if len(modifiedArg) > len(opener) { | |
| modifiedArg += sep | |
| } | |
| if len(trimmed) > 1 { | |
| modifiedArg += fmt.Sprintf("--%s", trimmed) | |
| } else { | |
| modifiedArg += fmt.Sprintf("-%s", trimmed) | |
| } | |
| } | |
| modifiedArg += closer | |
| if flag.TakesValue() { | |
| modifiedArg += fmt.Sprintf("=%s", value) | |
| } | |
| if addDetails { | |
| modifiedArg += flagDetails(flag) | |
| } | |
| args = append(args, modifiedArg+"\n") | |
| } | |
| sort.Strings(args) | |
| return args | |
| } | |
| // flagDetails returns a string containing the flags metadata | |
| func flagDetails(flag DocGenerationFlag) string { | |
| description := flag.GetUsage() | |
| if flag.TakesValue() { | |
| defaultText := flag.GetDefaultText() | |
| if defaultText == "" { | |
| defaultText = flag.GetValue() | |
| } | |
| if defaultText != "" { | |
| description += " (default: " + defaultText + ")" | |
| } | |
| } | |
| return ": " + description | |
| } | |
| func prepareUsageText(command *Command) string { | |
| if command.UsageText == "" { | |
| return "" | |
| } | |
| // Remove leading and trailing newlines | |
| preparedUsageText := strings.Trim(command.UsageText, "\n") | |
| var usageText string | |
| if strings.Contains(preparedUsageText, "\n") { | |
| // Format multi-line string as a code block using the 4 space schema to allow for embedded markdown such | |
| // that it will not break the continuous code block. | |
| for _, ln := range strings.Split(preparedUsageText, "\n") { | |
| usageText += fmt.Sprintf(" %s\n", ln) | |
| } | |
| } else { | |
| // Style a single line as a note | |
| usageText = fmt.Sprintf(">%s\n", preparedUsageText) | |
| } | |
| return usageText | |
| } | |
| func prepareUsage(command *Command, usageText string) string { | |
| if command.Usage == "" { | |
| return "" | |
| } | |
| usage := command.Usage + "\n" | |
| // Add a newline to the Usage IFF there is a UsageText | |
| if usageText != "" { | |
| usage += "\n" | |
| } | |
| return usage | |
| } |