git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/cobra/completions.go (about)

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