1package cli 2 3import ( 4 "bytes" 5 "fmt" 6 "io" 7 "sort" 8 "strings" 9 "text/template" 10 11 "github.com/cpuguy83/go-md2man/v2/md2man" 12) 13 14// ToMarkdown creates a markdown string for the `*App` 15// The function errors if either parsing or writing of the string fails. 16func (a *App) ToMarkdown() (string, error) { 17 var w bytes.Buffer 18 if err := a.writeDocTemplate(&w); err != nil { 19 return "", err 20 } 21 return w.String(), nil 22} 23 24// ToMan creates a man page string for the `*App` 25// The function errors if either parsing or writing of the string fails. 26func (a *App) ToMan() (string, error) { 27 var w bytes.Buffer 28 if err := a.writeDocTemplate(&w); err != nil { 29 return "", err 30 } 31 man := md2man.Render(w.Bytes()) 32 return string(man), nil 33} 34 35type cliTemplate struct { 36 App *App 37 Commands []string 38 GlobalArgs []string 39 SynopsisArgs []string 40} 41 42func (a *App) writeDocTemplate(w io.Writer) error { 43 const name = "cli" 44 t, err := template.New(name).Parse(MarkdownDocTemplate) 45 if err != nil { 46 return err 47 } 48 return t.ExecuteTemplate(w, name, &cliTemplate{ 49 App: a, 50 Commands: prepareCommands(a.Commands, 0), 51 GlobalArgs: prepareArgsWithValues(a.VisibleFlags()), 52 SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()), 53 }) 54} 55 56func prepareCommands(commands []*Command, level int) []string { 57 var coms []string 58 for _, command := range commands { 59 if command.Hidden { 60 continue 61 } 62 usage := "" 63 if command.Usage != "" { 64 usage = command.Usage 65 } 66 67 prepared := fmt.Sprintf("%s %s\n\n%s\n", 68 strings.Repeat("#", level+2), 69 strings.Join(command.Names(), ", "), 70 usage, 71 ) 72 73 flags := prepareArgsWithValues(command.Flags) 74 if len(flags) > 0 { 75 prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) 76 } 77 78 coms = append(coms, prepared) 79 80 // recursevly iterate subcommands 81 if len(command.Subcommands) > 0 { 82 coms = append( 83 coms, 84 prepareCommands(command.Subcommands, level+1)..., 85 ) 86 } 87 } 88 89 return coms 90} 91 92func prepareArgsWithValues(flags []Flag) []string { 93 return prepareFlags(flags, ", ", "**", "**", `""`, true) 94} 95 96func prepareArgsSynopsis(flags []Flag) []string { 97 return prepareFlags(flags, "|", "[", "]", "[value]", false) 98} 99 100func prepareFlags( 101 flags []Flag, 102 sep, opener, closer, value string, 103 addDetails bool, 104) []string { 105 args := []string{} 106 for _, f := range flags { 107 flag, ok := f.(DocGenerationFlag) 108 if !ok { 109 continue 110 } 111 modifiedArg := opener 112 113 for _, s := range flag.Names() { 114 trimmed := strings.TrimSpace(s) 115 if len(modifiedArg) > len(opener) { 116 modifiedArg += sep 117 } 118 if len(trimmed) > 1 { 119 modifiedArg += fmt.Sprintf("--%s", trimmed) 120 } else { 121 modifiedArg += fmt.Sprintf("-%s", trimmed) 122 } 123 } 124 modifiedArg += closer 125 if flag.TakesValue() { 126 modifiedArg += fmt.Sprintf("=%s", value) 127 } 128 129 if addDetails { 130 modifiedArg += flagDetails(flag) 131 } 132 133 args = append(args, modifiedArg+"\n") 134 135 } 136 sort.Strings(args) 137 return args 138} 139 140// flagDetails returns a string containing the flags metadata 141func flagDetails(flag DocGenerationFlag) string { 142 description := flag.GetUsage() 143 value := flag.GetValue() 144 if value != "" { 145 description += " (default: " + value + ")" 146 } 147 return ": " + description 148} 149