| //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 |
| } |