1package cobra
2
3import (
4	"fmt"
5	"os"
6	"strings"
7	"sync"
8
9	"github.com/spf13/pflag"
10)
11
12const (
13	// ShellCompRequestCmd is the name of the hidden command that is used to request
14	// completion results from the program.  It is used by the shell completion scripts.
15	ShellCompRequestCmd = "__complete"
16	// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
17	// completion results without their description.  It is used by the shell completion scripts.
18	ShellCompNoDescRequestCmd = "__completeNoDesc"
19)
20
21// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
22var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}
23
24// lock for reading and writing from flagCompletionFunctions
25var flagCompletionMutex = &sync.RWMutex{}
26
27// ShellCompDirective is a bit map representing the different behaviors the shell
28// can be instructed to have once completions have been provided.
29type ShellCompDirective int
30
31type flagCompError struct {
32	subCommand string
33	flagName   string
34}
35
36func (e *flagCompError) Error() string {
37	return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'"
38}
39
40const (
41	// ShellCompDirectiveError indicates an error occurred and completions should be ignored.
42	ShellCompDirectiveError ShellCompDirective = 1 << iota
43
44	// ShellCompDirectiveNoSpace indicates that the shell should not add a space
45	// after the completion even if there is a single completion provided.
46	ShellCompDirectiveNoSpace
47
48	// ShellCompDirectiveNoFileComp indicates that the shell should not provide
49	// file completion even when no completion is provided.
50	ShellCompDirectiveNoFileComp
51
52	// ShellCompDirectiveFilterFileExt indicates that the provided completions
53	// should be used as file extension filters.
54	// For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
55	// is a shortcut to using this directive explicitly.  The BashCompFilenameExt
56	// annotation can also be used to obtain the same behavior for flags.
57	ShellCompDirectiveFilterFileExt
58
59	// ShellCompDirectiveFilterDirs indicates that only directory names should
60	// be provided in file completion.  To request directory names within another
61	// directory, the returned completions should specify the directory within
62	// which to search.  The BashCompSubdirsInDir annotation can be used to
63	// obtain the same behavior but only for flags.
64	ShellCompDirectiveFilterDirs
65
66	// ===========================================================================
67
68	// All directives using iota should be above this one.
69	// For internal use.
70	shellCompDirectiveMaxValue
71
72	// ShellCompDirectiveDefault indicates to let the shell perform its default
73	// behavior after completions have been provided.
74	// This one must be last to avoid messing up the iota count.
75	ShellCompDirectiveDefault ShellCompDirective = 0
76)
77
78const (
79	// Constants for the completion command
80	compCmdName              = "completion"
81	compCmdNoDescFlagName    = "no-descriptions"
82	compCmdNoDescFlagDesc    = "disable completion descriptions"
83	compCmdNoDescFlagDefault = false
84)
85
86// CompletionOptions are the options to control shell completion
87type CompletionOptions struct {
88	// DisableDefaultCmd prevents Cobra from creating a default 'completion' command
89	DisableDefaultCmd bool
90	// DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag
91	// for shells that support completion descriptions
92	DisableNoDescFlag bool
93	// DisableDescriptions turns off all completion descriptions for shells
94	// that support them
95	DisableDescriptions bool
96}
97
98// NoFileCompletions can be used to disable file completion for commands that should
99// not trigger file completions.
100func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
101	return nil, ShellCompDirectiveNoFileComp
102}
103
104// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
105func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error {
106	flag := c.Flag(flagName)
107	if flag == nil {
108		return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
109	}
110	flagCompletionMutex.Lock()
111	defer flagCompletionMutex.Unlock()
112
113	if _, exists := flagCompletionFunctions[flag]; exists {
114		return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName)
115	}
116	flagCompletionFunctions[flag] = f
117	return nil
118}
119
120// Returns a string listing the different directive enabled in the specified parameter
121func (d ShellCompDirective) string() string {
122	var directives []string
123	if d&ShellCompDirectiveError != 0 {
124		directives = append(directives, "ShellCompDirectiveError")
125	}
126	if d&ShellCompDirectiveNoSpace != 0 {
127		directives = append(directives, "ShellCompDirectiveNoSpace")
128	}
129	if d&ShellCompDirectiveNoFileComp != 0 {
130		directives = append(directives, "ShellCompDirectiveNoFileComp")
131	}
132	if d&ShellCompDirectiveFilterFileExt != 0 {
133		directives = append(directives, "ShellCompDirectiveFilterFileExt")
134	}
135	if d&ShellCompDirectiveFilterDirs != 0 {
136		directives = append(directives, "ShellCompDirectiveFilterDirs")
137	}
138	if len(directives) == 0 {
139		directives = append(directives, "ShellCompDirectiveDefault")
140	}
141
142	if d >= shellCompDirectiveMaxValue {
143		return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
144	}
145	return strings.Join(directives, ", ")
146}
147
148// Adds a special hidden command that can be used to request custom completions.
149func (c *Command) initCompleteCmd(args []string) {
150	completeCmd := &Command{
151		Use:                   fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
152		Aliases:               []string{ShellCompNoDescRequestCmd},
153		DisableFlagsInUseLine: true,
154		Hidden:                true,
155		DisableFlagParsing:    true,
156		Args:                  MinimumNArgs(1),
157		Short:                 "Request shell completion choices for the specified command-line",
158		Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s",
159			"to request completion choices for the specified command-line.", ShellCompRequestCmd),
160		Run: func(cmd *Command, args []string) {
161			finalCmd, completions, directive, err := cmd.getCompletions(args)
162			if err != nil {
163				CompErrorln(err.Error())
164				// Keep going for multiple reasons:
165				// 1- There could be some valid completions even though there was an error
166				// 2- Even without completions, we need to print the directive
167			}
168
169			noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
170			for _, comp := range completions {
171				if noDescriptions {
172					// Remove any description that may be included following a tab character.
173					comp = strings.Split(comp, "\t")[0]
174				}
175
176				// Make sure we only write the first line to the output.
177				// This is needed if a description contains a linebreak.
178				// Otherwise the shell scripts will interpret the other lines as new flags
179				// and could therefore provide a wrong completion.
180				comp = strings.Split(comp, "\n")[0]
181
182				// Finally trim the completion.  This is especially important to get rid
183				// of a trailing tab when there are no description following it.
184				// For example, a sub-command without a description should not be completed
185				// with a tab at the end (or else zsh will show a -- following it
186				// although there is no description).
187				comp = strings.TrimSpace(comp)
188
189				// Print each possible completion to stdout for the completion script to consume.
190				fmt.Fprintln(finalCmd.OutOrStdout(), comp)
191			}
192
193			// As the last printout, print the completion directive for the completion script to parse.
194			// The directive integer must be that last character following a single colon (:).
195			// The completion script expects :<directive>
196			fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive)
197
198			// Print some helpful info to stderr for the user to understand.
199			// Output from stderr must be ignored by the completion script.
200			fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string())
201		},
202	}
203	c.AddCommand(completeCmd)
204	subCmd, _, err := c.Find(args)
205	if err != nil || subCmd.Name() != ShellCompRequestCmd {
206		// Only create this special command if it is actually being called.
207		// This reduces possible side-effects of creating such a command;
208		// for example, having this command would cause problems to a
209		// cobra program that only consists of the root command, since this
210		// command would cause the root command to suddenly have a subcommand.
211		c.RemoveCommand(completeCmd)
212	}
213}
214
215func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
216	// The last argument, which is not completely typed by the user,
217	// should not be part of the list of arguments
218	toComplete := args[len(args)-1]
219	trimmedArgs := args[:len(args)-1]
220
221	var finalCmd *Command
222	var finalArgs []string
223	var err error
224	// Find the real command for which completion must be performed
225	// check if we need to traverse here to parse local flags on parent commands
226	if c.Root().TraverseChildren {
227		finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
228	} else {
229		finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
230	}
231	if err != nil {
232		// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
233		return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
234	}
235	finalCmd.ctx = c.ctx
236
237	// Check if we are doing flag value completion before parsing the flags.
238	// This is important because if we are completing a flag value, we need to also
239	// remove the flag name argument from the list of finalArgs or else the parsing
240	// could fail due to an invalid value (incomplete) for the flag.
241	flag, finalArgs, toComplete, flagErr := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
242
243	// Check if interspersed is false or -- was set on a previous arg.
244	// This works by counting the arguments. Normally -- is not counted as arg but
245	// if -- was already set or interspersed is false and there is already one arg then
246	// the extra added -- is counted as arg.
247	flagCompletion := true
248	_ = finalCmd.ParseFlags(append(finalArgs, "--"))
249	newArgCount := finalCmd.Flags().NArg()
250
251	// Parse the flags early so we can check if required flags are set
252	if err = finalCmd.ParseFlags(finalArgs); err != nil {
253		return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
254	}
255
256	realArgCount := finalCmd.Flags().NArg()
257	if newArgCount > realArgCount {
258		// don't do flag completion (see above)
259		flagCompletion = false
260	}
261	// Error while attempting to parse flags
262	if flagErr != nil {
263		// If error type is flagCompError and we don't want flagCompletion we should ignore the error
264		if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) {
265			return finalCmd, []string{}, ShellCompDirectiveDefault, flagErr
266		}
267	}
268
269	if flag != nil && flagCompletion {
270		// Check if we are completing a flag value subject to annotations
271		if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
272			if len(validExts) != 0 {
273				// File completion filtered by extensions
274				return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil
275			}
276
277			// The annotation requests simple file completion.  There is no reason to do
278			// that since it is the default behavior anyway.  Let's ignore this annotation
279			// in case the program also registered a completion function for this flag.
280			// Even though it is a mistake on the program's side, let's be nice when we can.
281		}
282
283		if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {
284			if len(subDir) == 1 {
285				// Directory completion from within a directory
286				return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
287			}
288			// Directory completion
289			return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil
290		}
291	}
292
293	// When doing completion of a flag name, as soon as an argument starts with
294	// a '-' we know it is a flag.  We cannot use isFlagArg() here as it requires
295	// the flag name to be complete
296	if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion {
297		var completions []string
298
299		// First check for required flags
300		completions = completeRequireFlags(finalCmd, toComplete)
301
302		// If we have not found any required flags, only then can we show regular flags
303		if len(completions) == 0 {
304			doCompleteFlags := func(flag *pflag.Flag) {
305				if !flag.Changed ||
306					strings.Contains(flag.Value.Type(), "Slice") ||
307					strings.Contains(flag.Value.Type(), "Array") {
308					// If the flag is not already present, or if it can be specified multiple times (Array or Slice)
309					// we suggest it as a completion
310					completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
311				}
312			}
313
314			// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
315			// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
316			// non-inherited flags.
317			finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
318				doCompleteFlags(flag)
319			})
320			finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
321				doCompleteFlags(flag)
322			})
323		}
324
325		directive := ShellCompDirectiveNoFileComp
326		if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
327			// If there is a single completion, the shell usually adds a space
328			// after the completion.  We don't want that if the flag ends with an =
329			directive = ShellCompDirectiveNoSpace
330		}
331		return finalCmd, completions, directive, nil
332	}
333
334	// We only remove the flags from the arguments if DisableFlagParsing is not set.
335	// This is important for commands which have requested to do their own flag completion.
336	if !finalCmd.DisableFlagParsing {
337		finalArgs = finalCmd.Flags().Args()
338	}
339
340	var completions []string
341	directive := ShellCompDirectiveDefault
342	if flag == nil {
343		foundLocalNonPersistentFlag := false
344		// If TraverseChildren is true on the root command we don't check for
345		// local flags because we can use a local flag on a parent command
346		if !finalCmd.Root().TraverseChildren {
347			// Check if there are any local, non-persistent flags on the command-line
348			localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
349			finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
350				if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
351					foundLocalNonPersistentFlag = true
352				}
353			})
354		}
355
356		// Complete subcommand names, including the help command
357		if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
358			// We only complete sub-commands if:
359			// - there are no arguments on the command-line and
360			// - there are no local, non-persistent flags on the command-line or TraverseChildren is true
361			for _, subCmd := range finalCmd.Commands() {
362				if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
363					if strings.HasPrefix(subCmd.Name(), toComplete) {
364						completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
365					}
366					directive = ShellCompDirectiveNoFileComp
367				}
368			}
369		}
370
371		// Complete required flags even without the '-' prefix
372		completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
373
374		// Always complete ValidArgs, even if we are completing a subcommand name.
375		// This is for commands that have both subcommands and ValidArgs.
376		if len(finalCmd.ValidArgs) > 0 {
377			if len(finalArgs) == 0 {
378				// ValidArgs are only for the first argument
379				for _, validArg := range finalCmd.ValidArgs {
380					if strings.HasPrefix(validArg, toComplete) {
381						completions = append(completions, validArg)
382					}
383				}
384				directive = ShellCompDirectiveNoFileComp
385
386				// If no completions were found within commands or ValidArgs,
387				// see if there are any ArgAliases that should be completed.
388				if len(completions) == 0 {
389					for _, argAlias := range finalCmd.ArgAliases {
390						if strings.HasPrefix(argAlias, toComplete) {
391							completions = append(completions, argAlias)
392						}
393					}
394				}
395			}
396
397			// If there are ValidArgs specified (even if they don't match), we stop completion.
398			// Only one of ValidArgs or ValidArgsFunction can be used for a single command.
399			return finalCmd, completions, directive, nil
400		}
401
402		// Let the logic continue so as to add any ValidArgsFunction completions,
403		// even if we already found sub-commands.
404		// This is for commands that have subcommands but also specify a ValidArgsFunction.
405	}
406
407	// Find the completion function for the flag or command
408	var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
409	if flag != nil && flagCompletion {
410		flagCompletionMutex.RLock()
411		completionFn = flagCompletionFunctions[flag]
412		flagCompletionMutex.RUnlock()
413	} else {
414		completionFn = finalCmd.ValidArgsFunction
415	}
416	if completionFn != nil {
417		// Go custom completion defined for this flag or command.
418		// Call the registered completion function to get the completions.
419		var comps []string
420		comps, directive = completionFn(finalCmd, finalArgs, toComplete)
421		completions = append(completions, comps...)
422	}
423
424	return finalCmd, completions, directive, nil
425}
426
427func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
428	if nonCompletableFlag(flag) {
429		return []string{}
430	}
431
432	var completions []string
433	flagName := "--" + flag.Name
434	if strings.HasPrefix(flagName, toComplete) {
435		// Flag without the =
436		completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
437
438		// Why suggest both long forms: --flag and --flag= ?
439		// This forces the user to *always* have to type either an = or a space after the flag name.
440		// Let's be nice and avoid making users have to do that.
441		// Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
442		// The = form will still work, we just won't suggest it.
443		// This also makes the list of suggested flags shorter as we avoid all the = forms.
444		//
445		// if len(flag.NoOptDefVal) == 0 {
446		// 	// Flag requires a value, so it can be suffixed with =
447		// 	flagName += "="
448		// 	completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
449		// }
450	}
451
452	flagName = "-" + flag.Shorthand
453	if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {
454		completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
455	}
456
457	return completions
458}
459
460func completeRequireFlags(finalCmd *Command, toComplete string) []string {
461	var completions []string
462
463	doCompleteRequiredFlags := func(flag *pflag.Flag) {
464		if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
465			if !flag.Changed {
466				// If the flag is not already present, we suggest it as a completion
467				completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
468			}
469		}
470	}
471
472	// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
473	// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
474	// non-inherited flags.
475	finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
476		doCompleteRequiredFlags(flag)
477	})
478	finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
479		doCompleteRequiredFlags(flag)
480	})
481
482	return completions
483}
484
485func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
486	if finalCmd.DisableFlagParsing {
487		// We only do flag completion if we are allowed to parse flags
488		// This is important for commands which have requested to do their own flag completion.
489		return nil, args, lastArg, nil
490	}
491
492	var flagName string
493	trimmedArgs := args
494	flagWithEqual := false
495	orgLastArg := lastArg
496
497	// When doing completion of a flag name, as soon as an argument starts with
498	// a '-' we know it is a flag.  We cannot use isFlagArg() here as that function
499	// requires the flag name to be complete
500	if len(lastArg) > 0 && lastArg[0] == '-' {
501		if index := strings.Index(lastArg, "="); index >= 0 {
502			// Flag with an =
503			if strings.HasPrefix(lastArg[:index], "--") {
504				// Flag has full name
505				flagName = lastArg[2:index]
506			} else {
507				// Flag is shorthand
508				// We have to get the last shorthand flag name
509				// e.g. `-asd` => d to provide the correct completion
510				// https://github.com/spf13/cobra/issues/1257
511				flagName = lastArg[index-1 : index]
512			}
513			lastArg = lastArg[index+1:]
514			flagWithEqual = true
515		} else {
516			// Normal flag completion
517			return nil, args, lastArg, nil
518		}
519	}
520
521	if len(flagName) == 0 {
522		if len(args) > 0 {
523			prevArg := args[len(args)-1]
524			if isFlagArg(prevArg) {
525				// Only consider the case where the flag does not contain an =.
526				// If the flag contains an = it means it has already been fully processed,
527				// so we don't need to deal with it here.
528				if index := strings.Index(prevArg, "="); index < 0 {
529					if strings.HasPrefix(prevArg, "--") {
530						// Flag has full name
531						flagName = prevArg[2:]
532					} else {
533						// Flag is shorthand
534						// We have to get the last shorthand flag name
535						// e.g. `-asd` => d to provide the correct completion
536						// https://github.com/spf13/cobra/issues/1257
537						flagName = prevArg[len(prevArg)-1:]
538					}
539					// Remove the uncompleted flag or else there could be an error created
540					// for an invalid value for that flag
541					trimmedArgs = args[:len(args)-1]
542				}
543			}
544		}
545	}
546
547	if len(flagName) == 0 {
548		// Not doing flag completion
549		return nil, trimmedArgs, lastArg, nil
550	}
551
552	flag := findFlag(finalCmd, flagName)
553	if flag == nil {
554		// Flag not supported by this command, the interspersed option might be set so return the original args
555		return nil, args, orgLastArg, &flagCompError{subCommand: finalCmd.Name(), flagName: flagName}
556	}
557
558	if !flagWithEqual {
559		if len(flag.NoOptDefVal) != 0 {
560			// We had assumed dealing with a two-word flag but the flag is a boolean flag.
561			// In that case, there is no value following it, so we are not really doing flag completion.
562			// Reset everything to do noun completion.
563			trimmedArgs = args
564			flag = nil
565		}
566	}
567
568	return flag, trimmedArgs, lastArg, nil
569}
570
571// initDefaultCompletionCmd adds a default 'completion' command to c.
572// This function will do nothing if any of the following is true:
573// 1- the feature has been explicitly disabled by the program,
574// 2- c has no subcommands (to avoid creating one),
575// 3- c already has a 'completion' command provided by the program.
576func (c *Command) initDefaultCompletionCmd() {
577	if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() {
578		return
579	}
580
581	for _, cmd := range c.commands {
582		if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) {
583			// A completion command is already available
584			return
585		}
586	}
587
588	haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions
589
590	completionCmd := &Command{
591		Use:   compCmdName,
592		Short: "generate the autocompletion script for the specified shell",
593		Long: fmt.Sprintf(`
594Generate the autocompletion script for %[1]s for the specified shell.
595See each sub-command's help for details on how to use the generated script.
596`, c.Root().Name()),
597		Args:              NoArgs,
598		ValidArgsFunction: NoFileCompletions,
599	}
600	c.AddCommand(completionCmd)
601
602	out := c.OutOrStdout()
603	noDesc := c.CompletionOptions.DisableDescriptions
604	shortDesc := "generate the autocompletion script for %s"
605	bash := &Command{
606		Use:   "bash",
607		Short: fmt.Sprintf(shortDesc, "bash"),
608		Long: fmt.Sprintf(`
609Generate the autocompletion script for the bash shell.
610
611This script depends on the 'bash-completion' package.
612If it is not installed already, you can install it via your OS's package manager.
613
614To load completions in your current shell session:
615$ source <(%[1]s completion bash)
616
617To load completions for every new session, execute once:
618Linux:
619  $ %[1]s completion bash > /etc/bash_completion.d/%[1]s
620MacOS:
621  $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s
622
623You will need to start a new shell for this setup to take effect.
624  `, c.Root().Name()),
625		Args:                  NoArgs,
626		DisableFlagsInUseLine: true,
627		ValidArgsFunction:     NoFileCompletions,
628		RunE: func(cmd *Command, args []string) error {
629			return cmd.Root().GenBashCompletionV2(out, !noDesc)
630		},
631	}
632	if haveNoDescFlag {
633		bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
634	}
635
636	zsh := &Command{
637		Use:   "zsh",
638		Short: fmt.Sprintf(shortDesc, "zsh"),
639		Long: fmt.Sprintf(`
640Generate the autocompletion script for the zsh shell.
641
642If shell completion is not already enabled in your environment you will need
643to enable it.  You can execute the following once:
644
645$ echo "autoload -U compinit; compinit" >> ~/.zshrc
646
647To load completions for every new session, execute once:
648# Linux:
649$ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
650# macOS:
651$ %[1]s completion zsh > /usr/local/share/zsh/site-functions/_%[1]s
652
653You will need to start a new shell for this setup to take effect.
654`, c.Root().Name()),
655		Args:              NoArgs,
656		ValidArgsFunction: NoFileCompletions,
657		RunE: func(cmd *Command, args []string) error {
658			if noDesc {
659				return cmd.Root().GenZshCompletionNoDesc(out)
660			}
661			return cmd.Root().GenZshCompletion(out)
662		},
663	}
664	if haveNoDescFlag {
665		zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
666	}
667
668	fish := &Command{
669		Use:   "fish",
670		Short: fmt.Sprintf(shortDesc, "fish"),
671		Long: fmt.Sprintf(`
672Generate the autocompletion script for the fish shell.
673
674To load completions in your current shell session:
675$ %[1]s completion fish | source
676
677To load completions for every new session, execute once:
678$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
679
680You will need to start a new shell for this setup to take effect.
681`, c.Root().Name()),
682		Args:              NoArgs,
683		ValidArgsFunction: NoFileCompletions,
684		RunE: func(cmd *Command, args []string) error {
685			return cmd.Root().GenFishCompletion(out, !noDesc)
686		},
687	}
688	if haveNoDescFlag {
689		fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
690	}
691
692	powershell := &Command{
693		Use:   "powershell",
694		Short: fmt.Sprintf(shortDesc, "powershell"),
695		Long: fmt.Sprintf(`
696Generate the autocompletion script for powershell.
697
698To load completions in your current shell session:
699PS C:\> %[1]s completion powershell | Out-String | Invoke-Expression
700
701To load completions for every new session, add the output of the above command
702to your powershell profile.
703`, c.Root().Name()),
704		Args:              NoArgs,
705		ValidArgsFunction: NoFileCompletions,
706		RunE: func(cmd *Command, args []string) error {
707			if noDesc {
708				return cmd.Root().GenPowerShellCompletion(out)
709			}
710			return cmd.Root().GenPowerShellCompletionWithDesc(out)
711
712		},
713	}
714	if haveNoDescFlag {
715		powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
716	}
717
718	completionCmd.AddCommand(bash, zsh, fish, powershell)
719}
720
721func findFlag(cmd *Command, name string) *pflag.Flag {
722	flagSet := cmd.Flags()
723	if len(name) == 1 {
724		// First convert the short flag into a long flag
725		// as the cmd.Flag() search only accepts long flags
726		if short := flagSet.ShorthandLookup(name); short != nil {
727			name = short.Name
728		} else {
729			set := cmd.InheritedFlags()
730			if short = set.ShorthandLookup(name); short != nil {
731				name = short.Name
732			} else {
733				return nil
734			}
735		}
736	}
737	return cmd.Flag(name)
738}
739
740// CompDebug prints the specified string to the same file as where the
741// completion script prints its logs.
742// Note that completion printouts should never be on stdout as they would
743// be wrongly interpreted as actual completion choices by the completion script.
744func CompDebug(msg string, printToStdErr bool) {
745	msg = fmt.Sprintf("[Debug] %s", msg)
746
747	// Such logs are only printed when the user has set the environment
748	// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
749	if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
750		f, err := os.OpenFile(path,
751			os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
752		if err == nil {
753			defer f.Close()
754			WriteStringAndCheck(f, msg)
755		}
756	}
757
758	if printToStdErr {
759		// Must print to stderr for this not to be read by the completion script.
760		fmt.Fprint(os.Stderr, msg)
761	}
762}
763
764// CompDebugln prints the specified string with a newline at the end
765// to the same file as where the completion script prints its logs.
766// Such logs are only printed when the user has set the environment
767// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
768func CompDebugln(msg string, printToStdErr bool) {
769	CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr)
770}
771
772// CompError prints the specified completion message to stderr.
773func CompError(msg string) {
774	msg = fmt.Sprintf("[Error] %s", msg)
775	CompDebug(msg, true)
776}
777
778// CompErrorln prints the specified completion message to stderr with a newline at the end.
779func CompErrorln(msg string) {
780	CompError(fmt.Sprintf("%s\n", msg))
781}
782