1package cli 2 3import ( 4 "fmt" 5 "io" 6 "os" 7 "strings" 8 "text/tabwriter" 9 "text/template" 10 "unicode/utf8" 11) 12 13var helpCommand = Command{ 14 Name: "help", 15 Aliases: []string{"h"}, 16 Usage: "Shows a list of commands or help for one command", 17 ArgsUsage: "[command]", 18 Action: func(c *Context) error { 19 args := c.Args() 20 if args.Present() { 21 return ShowCommandHelp(c, args.First()) 22 } 23 24 _ = ShowAppHelp(c) 25 return nil 26 }, 27} 28 29var helpSubcommand = Command{ 30 Name: "help", 31 Aliases: []string{"h"}, 32 Usage: "Shows a list of commands or help for one command", 33 ArgsUsage: "[command]", 34 Action: func(c *Context) error { 35 args := c.Args() 36 if args.Present() { 37 return ShowCommandHelp(c, args.First()) 38 } 39 40 return ShowSubcommandHelp(c) 41 }, 42} 43 44// Prints help for the App or Command 45type helpPrinter func(w io.Writer, templ string, data interface{}) 46 47// Prints help for the App or Command with custom template function. 48type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{}) 49 50// HelpPrinter is a function that writes the help output. If not set explicitly, 51// this calls HelpPrinterCustom using only the default template functions. 52// 53// If custom logic for printing help is required, this function can be 54// overridden. If the ExtraInfo field is defined on an App, this function 55// should not be modified, as HelpPrinterCustom will be used directly in order 56// to capture the extra information. 57var HelpPrinter helpPrinter = printHelp 58 59// HelpPrinterCustom is a function that writes the help output. It is used as 60// the default implementation of HelpPrinter, and may be called directly if 61// the ExtraInfo field is set on an App. 62var HelpPrinterCustom helpPrinterCustom = printHelpCustom 63 64// VersionPrinter prints the version for the App 65var VersionPrinter = printVersion 66 67// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code. 68func ShowAppHelpAndExit(c *Context, exitCode int) { 69 _ = ShowAppHelp(c) 70 os.Exit(exitCode) 71} 72 73// ShowAppHelp is an action that displays the help. 74func ShowAppHelp(c *Context) error { 75 template := c.App.CustomAppHelpTemplate 76 if template == "" { 77 template = AppHelpTemplate 78 } 79 80 if c.App.ExtraInfo == nil { 81 HelpPrinter(c.App.Writer, template, c.App) 82 return nil 83 } 84 85 customAppData := func() map[string]interface{} { 86 return map[string]interface{}{ 87 "ExtraInfo": c.App.ExtraInfo, 88 } 89 } 90 HelpPrinterCustom(c.App.Writer, template, c.App, customAppData()) 91 92 return nil 93} 94 95// DefaultAppComplete prints the list of subcommands as the default app completion method 96func DefaultAppComplete(c *Context) { 97 DefaultCompleteWithFlags(nil)(c) 98} 99 100func printCommandSuggestions(commands []Command, writer io.Writer) { 101 for _, command := range commands { 102 if command.Hidden { 103 continue 104 } 105 if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { 106 for _, name := range command.Names() { 107 _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) 108 } 109 } else { 110 for _, name := range command.Names() { 111 _, _ = fmt.Fprintf(writer, "%s\n", name) 112 } 113 } 114 } 115} 116 117func cliArgContains(flagName string) bool { 118 for _, name := range strings.Split(flagName, ",") { 119 name = strings.TrimSpace(name) 120 count := utf8.RuneCountInString(name) 121 if count > 2 { 122 count = 2 123 } 124 flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) 125 for _, a := range os.Args { 126 if a == flag { 127 return true 128 } 129 } 130 } 131 return false 132} 133 134func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { 135 cur := strings.TrimPrefix(lastArg, "-") 136 cur = strings.TrimPrefix(cur, "-") 137 for _, flag := range flags { 138 if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden { 139 continue 140 } 141 for _, name := range strings.Split(flag.GetName(), ",") { 142 name = strings.TrimSpace(name) 143 // this will get total count utf8 letters in flag name 144 count := utf8.RuneCountInString(name) 145 if count > 2 { 146 count = 2 // resuse this count to generate single - or -- in flag completion 147 } 148 // if flag name has more than one utf8 letter and last argument in cli has -- prefix then 149 // skip flag completion for short flags example -v or -x 150 if strings.HasPrefix(lastArg, "--") && count == 1 { 151 continue 152 } 153 // match if last argument matches this flag and it is not repeated 154 if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) { 155 flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) 156 _, _ = fmt.Fprintln(writer, flagCompletion) 157 } 158 } 159 } 160} 161 162func DefaultCompleteWithFlags(cmd *Command) func(c *Context) { 163 return func(c *Context) { 164 if len(os.Args) > 2 { 165 lastArg := os.Args[len(os.Args)-2] 166 if strings.HasPrefix(lastArg, "-") { 167 printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer) 168 if cmd != nil { 169 printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer) 170 } 171 return 172 } 173 } 174 if cmd != nil { 175 printCommandSuggestions(cmd.Subcommands, c.App.Writer) 176 } else { 177 printCommandSuggestions(c.App.Commands, c.App.Writer) 178 } 179 } 180} 181 182// ShowCommandHelpAndExit - exits with code after showing help 183func ShowCommandHelpAndExit(c *Context, command string, code int) { 184 _ = ShowCommandHelp(c, command) 185 os.Exit(code) 186} 187 188// ShowCommandHelp prints help for the given command 189func ShowCommandHelp(ctx *Context, command string) error { 190 // show the subcommand help for a command with subcommands 191 if command == "" { 192 HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) 193 return nil 194 } 195 196 for _, c := range ctx.App.Commands { 197 if c.HasName(command) { 198 templ := c.CustomHelpTemplate 199 if templ == "" { 200 templ = CommandHelpTemplate 201 } 202 203 HelpPrinter(ctx.App.Writer, templ, c) 204 205 return nil 206 } 207 } 208 209 if ctx.App.CommandNotFound == nil { 210 return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3) 211 } 212 213 ctx.App.CommandNotFound(ctx, command) 214 return nil 215} 216 217// ShowSubcommandHelp prints help for the given subcommand 218func ShowSubcommandHelp(c *Context) error { 219 return ShowCommandHelp(c, c.Command.Name) 220} 221 222// ShowVersion prints the version number of the App 223func ShowVersion(c *Context) { 224 VersionPrinter(c) 225} 226 227func printVersion(c *Context) { 228 _, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) 229} 230 231// ShowCompletions prints the lists of commands within a given context 232func ShowCompletions(c *Context) { 233 a := c.App 234 if a != nil && a.BashComplete != nil { 235 a.BashComplete(c) 236 } 237} 238 239// ShowCommandCompletions prints the custom completions for a given command 240func ShowCommandCompletions(ctx *Context, command string) { 241 c := ctx.App.Command(command) 242 if c != nil { 243 if c.BashComplete != nil { 244 c.BashComplete(ctx) 245 } else { 246 DefaultCompleteWithFlags(c)(ctx) 247 } 248 } 249 250} 251 252// printHelpCustom is the default implementation of HelpPrinterCustom. 253// 254// The customFuncs map will be combined with a default template.FuncMap to 255// allow using arbitrary functions in template rendering. 256func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) { 257 funcMap := template.FuncMap{ 258 "join": strings.Join, 259 } 260 for key, value := range customFuncs { 261 funcMap[key] = value 262 } 263 264 w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) 265 t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) 266 err := t.Execute(w, data) 267 if err != nil { 268 // If the writer is closed, t.Execute will fail, and there's nothing 269 // we can do to recover. 270 if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { 271 _, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) 272 } 273 return 274 } 275 _ = w.Flush() 276} 277 278func printHelp(out io.Writer, templ string, data interface{}) { 279 HelpPrinterCustom(out, templ, data, nil) 280} 281 282func checkVersion(c *Context) bool { 283 found := false 284 if VersionFlag.GetName() != "" { 285 eachName(VersionFlag.GetName(), func(name string) { 286 if c.GlobalBool(name) || c.Bool(name) { 287 found = true 288 } 289 }) 290 } 291 return found 292} 293 294func checkHelp(c *Context) bool { 295 found := false 296 if HelpFlag.GetName() != "" { 297 eachName(HelpFlag.GetName(), func(name string) { 298 if c.GlobalBool(name) || c.Bool(name) { 299 found = true 300 } 301 }) 302 } 303 return found 304} 305 306func checkCommandHelp(c *Context, name string) bool { 307 if c.Bool("h") || c.Bool("help") { 308 _ = ShowCommandHelp(c, name) 309 return true 310 } 311 312 return false 313} 314 315func checkSubcommandHelp(c *Context) bool { 316 if c.Bool("h") || c.Bool("help") { 317 _ = ShowSubcommandHelp(c) 318 return true 319 } 320 321 return false 322} 323 324func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { 325 if !a.EnableBashCompletion { 326 return false, arguments 327 } 328 329 pos := len(arguments) - 1 330 lastArg := arguments[pos] 331 332 if lastArg != "--"+BashCompletionFlag.GetName() { 333 return false, arguments 334 } 335 336 return true, arguments[:pos] 337} 338 339func checkCompletions(c *Context) bool { 340 if !c.shellComplete { 341 return false 342 } 343 344 if args := c.Args(); args.Present() { 345 name := args.First() 346 if cmd := c.App.Command(name); cmd != nil { 347 // let the command handle the completion 348 return false 349 } 350 } 351 352 ShowCompletions(c) 353 return true 354} 355 356func checkCommandCompletions(c *Context, name string) bool { 357 if !c.shellComplete { 358 return false 359 } 360 361 ShowCommandCompletions(c, name) 362 return true 363} 364