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.Flags), 52 SynopsisArgs: prepareArgsSynopsis(a.Flags), 53 }) 54} 55 56func prepareCommands(commands []Command, level int) []string { 57 coms := []string{} 58 for i := range commands { 59 command := &commands[i] 60 if command.Hidden { 61 continue 62 } 63 usage := "" 64 if command.Usage != "" { 65 usage = command.Usage 66 } 67 68 prepared := fmt.Sprintf("%s %s\n\n%s\n", 69 strings.Repeat("#", level+2), 70 strings.Join(command.Names(), ", "), 71 usage, 72 ) 73 74 flags := prepareArgsWithValues(command.Flags) 75 if len(flags) > 0 { 76 prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) 77 } 78 79 coms = append(coms, prepared) 80 81 // recursevly iterate subcommands 82 if len(command.Subcommands) > 0 { 83 coms = append( 84 coms, 85 prepareCommands(command.Subcommands, level+1)..., 86 ) 87 } 88 } 89 90 return coms 91} 92 93func prepareArgsWithValues(flags []Flag) []string { 94 return prepareFlags(flags, ", ", "**", "**", `""`, true) 95} 96 97func prepareArgsSynopsis(flags []Flag) []string { 98 return prepareFlags(flags, "|", "[", "]", "[value]", false) 99} 100 101func prepareFlags( 102 flags []Flag, 103 sep, opener, closer, value string, 104 addDetails bool, 105) []string { 106 args := []string{} 107 for _, f := range flags { 108 flag, ok := f.(DocGenerationFlag) 109 if !ok { 110 continue 111 } 112 modifiedArg := opener 113 for _, s := range strings.Split(flag.GetName(), ",") { 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