1 package cli
2
3 import (
4 "fmt"
5 "io"
6 "os"
7 "strings"
8 "text/tabwriter"
9 "text/template"
10 "unicode/utf8"
11 )
12
13 const (
14 helpName = "help"
15 helpAlias = "h"
16 )
17
18
19
20 var helpCommandDontUse = &Command{
21 Name: helpName,
22 Aliases: []string{helpAlias},
23 Usage: "Shows a list of commands or help for one command",
24 ArgsUsage: "[command]",
25 }
26
27 var helpCommand = &Command{
28 Name: helpName,
29 Aliases: []string{helpAlias},
30 Usage: "Shows a list of commands or help for one command",
31 ArgsUsage: "[command]",
32 Action: func(cCtx *Context) error {
33 args := cCtx.Args()
34 argsPresent := args.First() != ""
35 firstArg := args.First()
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 if cCtx.Command.Name == helpName || cCtx.Command.Name == helpAlias {
54 cCtx = cCtx.parentContext
55 }
56
57
58
59 if argsPresent {
60 return ShowCommandHelp(cCtx, firstArg)
61 }
62
63
64
65
66 if cCtx.parentContext.App == nil {
67 _ = ShowAppHelp(cCtx)
68 return nil
69 }
70
71
72 if (len(cCtx.Command.Subcommands) == 1 && !cCtx.Command.HideHelp && !cCtx.Command.HideHelpCommand) ||
73 (len(cCtx.Command.Subcommands) == 0 && cCtx.Command.HideHelp) {
74 templ := cCtx.Command.CustomHelpTemplate
75 if templ == "" {
76 templ = CommandHelpTemplate
77 }
78 HelpPrinter(cCtx.App.Writer, templ, cCtx.Command)
79 return nil
80 }
81
82
83 return ShowSubcommandHelp(cCtx)
84 },
85 }
86
87
88 type helpPrinter func(w io.Writer, templ string, data interface{})
89
90
91 type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
92
93
94
95
96
97
98
99
100 var HelpPrinter helpPrinter = printHelp
101
102
103
104
105
106
107
108
109
110 var HelpPrinterCustom helpPrinterCustom = printHelpCustom
111
112
113 var VersionPrinter = printVersion
114
115
116 func ShowAppHelpAndExit(c *Context, exitCode int) {
117 _ = ShowAppHelp(c)
118 os.Exit(exitCode)
119 }
120
121
122 func ShowAppHelp(cCtx *Context) error {
123 tpl := cCtx.App.CustomAppHelpTemplate
124 if tpl == "" {
125 tpl = AppHelpTemplate
126 }
127
128 if cCtx.App.ExtraInfo == nil {
129 HelpPrinter(cCtx.App.Writer, tpl, cCtx.App)
130 return nil
131 }
132
133 customAppData := func() map[string]interface{} {
134 return map[string]interface{}{
135 "ExtraInfo": cCtx.App.ExtraInfo,
136 }
137 }
138 HelpPrinterCustom(cCtx.App.Writer, tpl, cCtx.App, customAppData())
139
140 return nil
141 }
142
143
144 func DefaultAppComplete(cCtx *Context) {
145 DefaultCompleteWithFlags(nil)(cCtx)
146 }
147
148 func printCommandSuggestions(commands []*Command, writer io.Writer) {
149 for _, command := range commands {
150 if command.Hidden {
151 continue
152 }
153 if strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
154 for _, name := range command.Names() {
155 _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
156 }
157 } else {
158 for _, name := range command.Names() {
159 _, _ = fmt.Fprintf(writer, "%s\n", name)
160 }
161 }
162 }
163 }
164
165 func cliArgContains(flagName string) bool {
166 for _, name := range strings.Split(flagName, ",") {
167 name = strings.TrimSpace(name)
168 count := utf8.RuneCountInString(name)
169 if count > 2 {
170 count = 2
171 }
172 flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
173 for _, a := range os.Args {
174 if a == flag {
175 return true
176 }
177 }
178 }
179 return false
180 }
181
182 func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
183 cur := strings.TrimPrefix(lastArg, "-")
184 cur = strings.TrimPrefix(cur, "-")
185 for _, flag := range flags {
186 if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden {
187 continue
188 }
189 for _, name := range flag.Names() {
190 name = strings.TrimSpace(name)
191
192 count := utf8.RuneCountInString(name)
193 if count > 2 {
194 count = 2
195 }
196
197
198 if strings.HasPrefix(lastArg, "--") && count == 1 {
199 continue
200 }
201
202 if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) {
203 flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
204 _, _ = fmt.Fprintln(writer, flagCompletion)
205 }
206 }
207 }
208 }
209
210 func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) {
211 return func(cCtx *Context) {
212 var lastArg string
213
214
215
216 if len(os.Args) > 2 {
217 lastArg = os.Args[len(os.Args)-2]
218 }
219
220 if lastArg != "" {
221 if strings.HasPrefix(lastArg, "-") {
222 if cmd != nil {
223 printFlagSuggestions(lastArg, cmd.Flags, cCtx.App.Writer)
224
225 return
226 }
227
228 printFlagSuggestions(lastArg, cCtx.App.Flags, cCtx.App.Writer)
229
230 return
231 }
232 }
233
234 if cmd != nil {
235 printCommandSuggestions(cmd.Subcommands, cCtx.App.Writer)
236 return
237 }
238
239 printCommandSuggestions(cCtx.Command.Subcommands, cCtx.App.Writer)
240 }
241 }
242
243
244 func ShowCommandHelpAndExit(c *Context, command string, code int) {
245 _ = ShowCommandHelp(c, command)
246 os.Exit(code)
247 }
248
249
250 func ShowCommandHelp(ctx *Context, command string) error {
251
252 commands := ctx.App.Commands
253 if ctx.Command.Subcommands != nil {
254 commands = ctx.Command.Subcommands
255 }
256 for _, c := range commands {
257 if c.HasName(command) {
258 if !ctx.App.HideHelpCommand && !c.HasName(helpName) && len(c.Subcommands) != 0 && c.Command(helpName) == nil {
259 c.Subcommands = append(c.Subcommands, helpCommandDontUse)
260 }
261 if !ctx.App.HideHelp && HelpFlag != nil {
262 c.appendFlag(HelpFlag)
263 }
264 templ := c.CustomHelpTemplate
265 if templ == "" {
266 if len(c.Subcommands) == 0 {
267 templ = CommandHelpTemplate
268 } else {
269 templ = SubcommandHelpTemplate
270 }
271 }
272
273 HelpPrinter(ctx.App.Writer, templ, c)
274
275 return nil
276 }
277 }
278
279 if ctx.App.CommandNotFound == nil {
280 errMsg := fmt.Sprintf("No help topic for '%v'", command)
281 if ctx.App.Suggest && SuggestCommand != nil {
282 if suggestion := SuggestCommand(ctx.Command.Subcommands, command); suggestion != "" {
283 errMsg += ". " + suggestion
284 }
285 }
286 return Exit(errMsg, 3)
287 }
288
289 ctx.App.CommandNotFound(ctx, command)
290 return nil
291 }
292
293
294 func ShowSubcommandHelpAndExit(c *Context, exitCode int) {
295 _ = ShowSubcommandHelp(c)
296 os.Exit(exitCode)
297 }
298
299
300 func ShowSubcommandHelp(cCtx *Context) error {
301 if cCtx == nil {
302 return nil
303 }
304
305 templ := SubcommandHelpTemplate
306 if cCtx.Command != nil && cCtx.Command.CustomHelpTemplate != "" {
307 templ = cCtx.Command.CustomHelpTemplate
308 }
309 HelpPrinter(cCtx.App.Writer, templ, cCtx.Command)
310 return nil
311 }
312
313
314 func ShowVersion(cCtx *Context) {
315 VersionPrinter(cCtx)
316 }
317
318 func printVersion(cCtx *Context) {
319 _, _ = fmt.Fprintf(cCtx.App.Writer, "%v version %v\n", cCtx.App.Name, cCtx.App.Version)
320 }
321
322
323 func ShowCompletions(cCtx *Context) {
324 c := cCtx.Command
325 if c != nil && c.BashComplete != nil {
326 c.BashComplete(cCtx)
327 }
328 }
329
330
331 func ShowCommandCompletions(ctx *Context, command string) {
332 c := ctx.Command.Command(command)
333 if c != nil {
334 if c.BashComplete != nil {
335 c.BashComplete(ctx)
336 } else {
337 DefaultCompleteWithFlags(c)(ctx)
338 }
339 }
340
341 }
342
343
344
345
346
347 func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
348
349 const maxLineLength = 10000
350
351 funcMap := template.FuncMap{
352 "join": strings.Join,
353 "subtract": subtract,
354 "indent": indent,
355 "nindent": nindent,
356 "trim": strings.TrimSpace,
357 "wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) },
358 "offset": offset,
359 "offsetCommands": offsetCommands,
360 }
361
362 if customFuncs["wrapAt"] != nil {
363 if wa, ok := customFuncs["wrapAt"]; ok {
364 if waf, ok := wa.(func() int); ok {
365 wrapAt := waf()
366 customFuncs["wrap"] = func(input string, offset int) string {
367 return wrap(input, offset, wrapAt)
368 }
369 }
370 }
371 }
372
373 for key, value := range customFuncs {
374 funcMap[key] = value
375 }
376
377 w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
378 t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
379 templates := map[string]string{
380 "helpNameTemplate": helpNameTemplate,
381 "usageTemplate": usageTemplate,
382 "descriptionTemplate": descriptionTemplate,
383 "visibleCommandTemplate": visibleCommandTemplate,
384 "copyrightTemplate": copyrightTemplate,
385 "versionTemplate": versionTemplate,
386 "visibleFlagCategoryTemplate": visibleFlagCategoryTemplate,
387 "visibleFlagTemplate": visibleFlagTemplate,
388 "visibleGlobalFlagCategoryTemplate": strings.Replace(visibleFlagCategoryTemplate, "OPTIONS", "GLOBAL OPTIONS", -1),
389 "authorsTemplate": authorsTemplate,
390 "visibleCommandCategoryTemplate": visibleCommandCategoryTemplate,
391 }
392 for name, value := range templates {
393 if _, err := t.New(name).Parse(value); err != nil {
394 if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
395 _, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
396 }
397 }
398 }
399
400 err := t.Execute(w, data)
401 if err != nil {
402
403
404 if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
405 _, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
406 }
407 return
408 }
409 _ = w.Flush()
410 }
411
412 func printHelp(out io.Writer, templ string, data interface{}) {
413 HelpPrinterCustom(out, templ, data, nil)
414 }
415
416 func checkVersion(cCtx *Context) bool {
417 found := false
418 for _, name := range VersionFlag.Names() {
419 if cCtx.Bool(name) {
420 found = true
421 }
422 }
423 return found
424 }
425
426 func checkHelp(cCtx *Context) bool {
427 if HelpFlag == nil {
428 return false
429 }
430 found := false
431 for _, name := range HelpFlag.Names() {
432 if cCtx.Bool(name) {
433 found = true
434 break
435 }
436 }
437
438 return found
439 }
440
441 func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
442 if !a.EnableBashCompletion {
443 return false, arguments
444 }
445
446 pos := len(arguments) - 1
447 lastArg := arguments[pos]
448
449 if lastArg != "--generate-bash-completion" {
450 return false, arguments
451 }
452
453 return true, arguments[:pos]
454 }
455
456 func checkCompletions(cCtx *Context) bool {
457 if !cCtx.shellComplete {
458 return false
459 }
460
461 if args := cCtx.Args(); args.Present() {
462 name := args.First()
463 if cmd := cCtx.Command.Command(name); cmd != nil {
464
465 return false
466 }
467 }
468
469 ShowCompletions(cCtx)
470 return true
471 }
472
473 func subtract(a, b int) int {
474 return a - b
475 }
476
477 func indent(spaces int, v string) string {
478 pad := strings.Repeat(" ", spaces)
479 return pad + strings.Replace(v, "\n", "\n"+pad, -1)
480 }
481
482 func nindent(spaces int, v string) string {
483 return "\n" + indent(spaces, v)
484 }
485
486 func wrap(input string, offset int, wrapAt int) string {
487 var ss []string
488
489 lines := strings.Split(input, "\n")
490
491 padding := strings.Repeat(" ", offset)
492
493 for i, line := range lines {
494 if line == "" {
495 ss = append(ss, line)
496 } else {
497 wrapped := wrapLine(line, offset, wrapAt, padding)
498 if i == 0 {
499 ss = append(ss, wrapped)
500 } else {
501 ss = append(ss, padding+wrapped)
502
503 }
504
505 }
506 }
507
508 return strings.Join(ss, "\n")
509 }
510
511 func wrapLine(input string, offset int, wrapAt int, padding string) string {
512 if wrapAt <= offset || len(input) <= wrapAt-offset {
513 return input
514 }
515
516 lineWidth := wrapAt - offset
517 words := strings.Fields(input)
518 if len(words) == 0 {
519 return input
520 }
521
522 wrapped := words[0]
523 spaceLeft := lineWidth - len(wrapped)
524 for _, word := range words[1:] {
525 if len(word)+1 > spaceLeft {
526 wrapped += "\n" + padding + word
527 spaceLeft = lineWidth - len(word)
528 } else {
529 wrapped += " " + word
530 spaceLeft -= 1 + len(word)
531 }
532 }
533
534 return wrapped
535 }
536
537 func offset(input string, fixed int) int {
538 return len(input) + fixed
539 }
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555 func offsetCommands(cmds []*Command, fixed int) int {
556 var max int = 0
557 for _, cmd := range cmds {
558 s := strings.Join(cmd.Names(), ", ")
559 if len(s) > max {
560 max = len(s)
561 }
562 }
563 return max + fixed
564 }
565
View as plain text