199 lines
4.5 KiB
Go
199 lines
4.5 KiB
Go
|
//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()
|
||
|
value := flag.GetValue()
|
||
|
if value != "" {
|
||
|
description += " (default: " + value + ")"
|
||
|
}
|
||
|
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
|
||
|
}
|