Path: blob/main/vendor/github.com/spf13/cobra/completions.go
2875 views
// Copyright 2013-2023 The Cobra Authors1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314package cobra1516import (17"fmt"18"os"19"regexp"20"strconv"21"strings"22"sync"2324"github.com/spf13/pflag"25)2627const (28// ShellCompRequestCmd is the name of the hidden command that is used to request29// completion results from the program. It is used by the shell completion scripts.30ShellCompRequestCmd = "__complete"31// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request32// completion results without their description. It is used by the shell completion scripts.33ShellCompNoDescRequestCmd = "__completeNoDesc"34)3536// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.37var flagCompletionFunctions = map[*pflag.Flag]CompletionFunc{}3839// lock for reading and writing from flagCompletionFunctions40var flagCompletionMutex = &sync.RWMutex{}4142// ShellCompDirective is a bit map representing the different behaviors the shell43// can be instructed to have once completions have been provided.44type ShellCompDirective int4546type flagCompError struct {47subCommand string48flagName string49}5051func (e *flagCompError) Error() string {52return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'"53}5455const (56// ShellCompDirectiveError indicates an error occurred and completions should be ignored.57ShellCompDirectiveError ShellCompDirective = 1 << iota5859// ShellCompDirectiveNoSpace indicates that the shell should not add a space60// after the completion even if there is a single completion provided.61ShellCompDirectiveNoSpace6263// ShellCompDirectiveNoFileComp indicates that the shell should not provide64// file completion even when no completion is provided.65ShellCompDirectiveNoFileComp6667// ShellCompDirectiveFilterFileExt indicates that the provided completions68// should be used as file extension filters.69// For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()70// is a shortcut to using this directive explicitly. The BashCompFilenameExt71// annotation can also be used to obtain the same behavior for flags.72ShellCompDirectiveFilterFileExt7374// ShellCompDirectiveFilterDirs indicates that only directory names should75// be provided in file completion. To request directory names within another76// directory, the returned completions should specify the directory within77// which to search. The BashCompSubdirsInDir annotation can be used to78// obtain the same behavior but only for flags.79ShellCompDirectiveFilterDirs8081// ShellCompDirectiveKeepOrder indicates that the shell should preserve the order82// in which the completions are provided83ShellCompDirectiveKeepOrder8485// ===========================================================================8687// All directives using iota should be above this one.88// For internal use.89shellCompDirectiveMaxValue9091// ShellCompDirectiveDefault indicates to let the shell perform its default92// behavior after completions have been provided.93// This one must be last to avoid messing up the iota count.94ShellCompDirectiveDefault ShellCompDirective = 095)9697const (98// Constants for the completion command99compCmdName = "completion"100compCmdNoDescFlagName = "no-descriptions"101compCmdNoDescFlagDesc = "disable completion descriptions"102compCmdNoDescFlagDefault = false103)104105// CompletionOptions are the options to control shell completion106type CompletionOptions struct {107// DisableDefaultCmd prevents Cobra from creating a default 'completion' command108DisableDefaultCmd bool109// DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag110// for shells that support completion descriptions111DisableNoDescFlag bool112// DisableDescriptions turns off all completion descriptions for shells113// that support them114DisableDescriptions bool115// HiddenDefaultCmd makes the default 'completion' command hidden116HiddenDefaultCmd bool117// DefaultShellCompDirective sets the ShellCompDirective that is returned118// if no special directive can be determined119DefaultShellCompDirective *ShellCompDirective120}121122func (receiver *CompletionOptions) SetDefaultShellCompDirective(directive ShellCompDirective) {123receiver.DefaultShellCompDirective = &directive124}125126// Completion is a string that can be used for completions127//128// two formats are supported:129// - the completion choice130// - the completion choice with a textual description (separated by a TAB).131//132// [CompletionWithDesc] can be used to create a completion string with a textual description.133//134// Note: Go type alias is used to provide a more descriptive name in the documentation, but any string can be used.135type Completion = string136137// CompletionFunc is a function that provides completion results.138type CompletionFunc = func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)139140// CompletionWithDesc returns a [Completion] with a description by using the TAB delimited format.141func CompletionWithDesc(choice string, description string) Completion {142return choice + "\t" + description143}144145// NoFileCompletions can be used to disable file completion for commands that should146// not trigger file completions.147//148// This method satisfies [CompletionFunc].149// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].150func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {151return nil, ShellCompDirectiveNoFileComp152}153154// FixedCompletions can be used to create a completion function which always155// returns the same results.156//157// This method returns a function that satisfies [CompletionFunc]158// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].159func FixedCompletions(choices []Completion, directive ShellCompDirective) CompletionFunc {160return func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {161return choices, directive162}163}164165// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.166//167// You can use pre-defined completion functions such as [FixedCompletions] or [NoFileCompletions],168// or you can define your own.169func (c *Command) RegisterFlagCompletionFunc(flagName string, f CompletionFunc) error {170flag := c.Flag(flagName)171if flag == nil {172return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)173}174flagCompletionMutex.Lock()175defer flagCompletionMutex.Unlock()176177if _, exists := flagCompletionFunctions[flag]; exists {178return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName)179}180flagCompletionFunctions[flag] = f181return nil182}183184// GetFlagCompletionFunc returns the completion function for the given flag of the command, if available.185func (c *Command) GetFlagCompletionFunc(flagName string) (CompletionFunc, bool) {186flag := c.Flag(flagName)187if flag == nil {188return nil, false189}190191flagCompletionMutex.RLock()192defer flagCompletionMutex.RUnlock()193194completionFunc, exists := flagCompletionFunctions[flag]195return completionFunc, exists196}197198// Returns a string listing the different directive enabled in the specified parameter199func (d ShellCompDirective) string() string {200var directives []string201if d&ShellCompDirectiveError != 0 {202directives = append(directives, "ShellCompDirectiveError")203}204if d&ShellCompDirectiveNoSpace != 0 {205directives = append(directives, "ShellCompDirectiveNoSpace")206}207if d&ShellCompDirectiveNoFileComp != 0 {208directives = append(directives, "ShellCompDirectiveNoFileComp")209}210if d&ShellCompDirectiveFilterFileExt != 0 {211directives = append(directives, "ShellCompDirectiveFilterFileExt")212}213if d&ShellCompDirectiveFilterDirs != 0 {214directives = append(directives, "ShellCompDirectiveFilterDirs")215}216if d&ShellCompDirectiveKeepOrder != 0 {217directives = append(directives, "ShellCompDirectiveKeepOrder")218}219if len(directives) == 0 {220directives = append(directives, "ShellCompDirectiveDefault")221}222223if d >= shellCompDirectiveMaxValue {224return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)225}226return strings.Join(directives, ", ")227}228229// initCompleteCmd adds a special hidden command that can be used to request custom completions.230func (c *Command) initCompleteCmd(args []string) {231completeCmd := &Command{232Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),233Aliases: []string{ShellCompNoDescRequestCmd},234DisableFlagsInUseLine: true,235Hidden: true,236DisableFlagParsing: true,237Args: MinimumNArgs(1),238Short: "Request shell completion choices for the specified command-line",239Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s",240"to request completion choices for the specified command-line.", ShellCompRequestCmd),241Run: func(cmd *Command, args []string) {242finalCmd, completions, directive, err := cmd.getCompletions(args)243if err != nil {244CompErrorln(err.Error())245// Keep going for multiple reasons:246// 1- There could be some valid completions even though there was an error247// 2- Even without completions, we need to print the directive248}249250noDescriptions := cmd.CalledAs() == ShellCompNoDescRequestCmd251if !noDescriptions {252if doDescriptions, err := strconv.ParseBool(getEnvConfig(cmd, configEnvVarSuffixDescriptions)); err == nil {253noDescriptions = !doDescriptions254}255}256noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable257out := finalCmd.OutOrStdout()258for _, comp := range completions {259if noActiveHelp && strings.HasPrefix(comp, activeHelpMarker) {260// Remove all activeHelp entries if it's disabled.261continue262}263if noDescriptions {264// Remove any description that may be included following a tab character.265comp = strings.SplitN(comp, "\t", 2)[0]266}267268// Make sure we only write the first line to the output.269// This is needed if a description contains a linebreak.270// Otherwise the shell scripts will interpret the other lines as new flags271// and could therefore provide a wrong completion.272comp = strings.SplitN(comp, "\n", 2)[0]273274// Finally trim the completion. This is especially important to get rid275// of a trailing tab when there are no description following it.276// For example, a sub-command without a description should not be completed277// with a tab at the end (or else zsh will show a -- following it278// although there is no description).279comp = strings.TrimSpace(comp)280281// Print each possible completion to the output for the completion script to consume.282fmt.Fprintln(out, comp)283}284285// As the last printout, print the completion directive for the completion script to parse.286// The directive integer must be that last character following a single colon (:).287// The completion script expects :<directive>288fmt.Fprintf(out, ":%d\n", directive)289290// Print some helpful info to stderr for the user to understand.291// Output from stderr must be ignored by the completion script.292fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string())293},294}295c.AddCommand(completeCmd)296subCmd, _, err := c.Find(args)297if err != nil || subCmd.Name() != ShellCompRequestCmd {298// Only create this special command if it is actually being called.299// This reduces possible side-effects of creating such a command;300// for example, having this command would cause problems to a301// cobra program that only consists of the root command, since this302// command would cause the root command to suddenly have a subcommand.303c.RemoveCommand(completeCmd)304}305}306307// SliceValue is a reduced version of [pflag.SliceValue]. It is used to detect308// flags that accept multiple values and therefore can provide completion309// multiple times.310type SliceValue interface {311// GetSlice returns the flag value list as an array of strings.312GetSlice() []string313}314315func (c *Command) getCompletions(args []string) (*Command, []Completion, ShellCompDirective, error) {316// The last argument, which is not completely typed by the user,317// should not be part of the list of arguments318toComplete := args[len(args)-1]319trimmedArgs := args[:len(args)-1]320321var finalCmd *Command322var finalArgs []string323var err error324// Find the real command for which completion must be performed325// check if we need to traverse here to parse local flags on parent commands326if c.Root().TraverseChildren {327finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)328} else {329// For Root commands that don't specify any value for their Args fields, when we call330// Find(), if those Root commands don't have any sub-commands, they will accept arguments.331// However, because we have added the __complete sub-command in the current code path, the332// call to Find() -> legacyArgs() will return an error if there are any arguments.333// To avoid this, we first remove the __complete command to get back to having no sub-commands.334rootCmd := c.Root()335if len(rootCmd.Commands()) == 1 {336rootCmd.RemoveCommand(c)337}338339finalCmd, finalArgs, err = rootCmd.Find(trimmedArgs)340}341if err != nil {342// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>343return c, []Completion{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs)344}345finalCmd.ctx = c.ctx346347// These flags are normally added when `execute()` is called on `finalCmd`,348// however, when doing completion, we don't call `finalCmd.execute()`.349// Let's add the --help and --version flag ourselves but only if the finalCmd350// has not disabled flag parsing; if flag parsing is disabled, it is up to the351// finalCmd itself to handle the completion of *all* flags.352if !finalCmd.DisableFlagParsing {353finalCmd.InitDefaultHelpFlag()354finalCmd.InitDefaultVersionFlag()355}356357// Check if we are doing flag value completion before parsing the flags.358// This is important because if we are completing a flag value, we need to also359// remove the flag name argument from the list of finalArgs or else the parsing360// could fail due to an invalid value (incomplete) for the flag.361flag, finalArgs, toComplete, flagErr := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)362363// Check if interspersed is false or -- was set on a previous arg.364// This works by counting the arguments. Normally -- is not counted as arg but365// if -- was already set or interspersed is false and there is already one arg then366// the extra added -- is counted as arg.367flagCompletion := true368_ = finalCmd.ParseFlags(append(finalArgs, "--"))369newArgCount := finalCmd.Flags().NArg()370371// Parse the flags early so we can check if required flags are set372if err = finalCmd.ParseFlags(finalArgs); err != nil {373return finalCmd, []Completion{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())374}375376realArgCount := finalCmd.Flags().NArg()377if newArgCount > realArgCount {378// don't do flag completion (see above)379flagCompletion = false380}381// Error while attempting to parse flags382if flagErr != nil {383// If error type is flagCompError and we don't want flagCompletion we should ignore the error384if _, ok := flagErr.(*flagCompError); !ok || flagCompletion {385return finalCmd, []Completion{}, ShellCompDirectiveDefault, flagErr386}387}388389// Look for the --help or --version flags. If they are present,390// there should be no further completions.391if helpOrVersionFlagPresent(finalCmd) {392return finalCmd, []Completion{}, ShellCompDirectiveNoFileComp, nil393}394395// We only remove the flags from the arguments if DisableFlagParsing is not set.396// This is important for commands which have requested to do their own flag completion.397if !finalCmd.DisableFlagParsing {398finalArgs = finalCmd.Flags().Args()399}400401if flag != nil && flagCompletion {402// Check if we are completing a flag value subject to annotations403if validExts, present := flag.Annotations[BashCompFilenameExt]; present {404if len(validExts) != 0 {405// File completion filtered by extensions406return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil407}408409// The annotation requests simple file completion. There is no reason to do410// that since it is the default behavior anyway. Let's ignore this annotation411// in case the program also registered a completion function for this flag.412// Even though it is a mistake on the program's side, let's be nice when we can.413}414415if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {416if len(subDir) == 1 {417// Directory completion from within a directory418return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil419}420// Directory completion421return finalCmd, []Completion{}, ShellCompDirectiveFilterDirs, nil422}423}424425var completions []Completion426var directive ShellCompDirective427428// Enforce flag groups before doing flag completions429finalCmd.enforceFlagGroupsForCompletion()430431// Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true;432// doing this allows for completion of persistent flag names even for commands that disable flag parsing.433//434// When doing completion of a flag name, as soon as an argument starts with435// a '-' we know it is a flag. We cannot use isFlagArg() here as it requires436// the flag name to be complete437if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion {438// First check for required flags439completions = completeRequireFlags(finalCmd, toComplete)440441// If we have not found any required flags, only then can we show regular flags442if len(completions) == 0 {443doCompleteFlags := func(flag *pflag.Flag) {444_, acceptsMultiple := flag.Value.(SliceValue)445acceptsMultiple = acceptsMultiple ||446strings.Contains(flag.Value.Type(), "Slice") ||447strings.Contains(flag.Value.Type(), "Array") ||448strings.HasPrefix(flag.Value.Type(), "stringTo")449450if !flag.Changed || acceptsMultiple {451// If the flag is not already present, or if it can be specified multiple times (Array, Slice, or stringTo)452// we suggest it as a completion453completions = append(completions, getFlagNameCompletions(flag, toComplete)...)454}455}456457// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands458// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and459// non-inherited flags.460finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {461doCompleteFlags(flag)462})463// Try to complete non-inherited flags even if DisableFlagParsing==true.464// This allows programs to tell Cobra about flags for completion even465// if the actual parsing of flags is not done by Cobra.466// For instance, Helm uses this to provide flag name completion for467// some of its plugins.468finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {469doCompleteFlags(flag)470})471}472473directive = ShellCompDirectiveNoFileComp474if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {475// If there is a single completion, the shell usually adds a space476// after the completion. We don't want that if the flag ends with an =477directive = ShellCompDirectiveNoSpace478}479480if !finalCmd.DisableFlagParsing {481// If DisableFlagParsing==false, we have completed the flags as known by Cobra;482// we can return what we found.483// If DisableFlagParsing==true, Cobra may not be aware of all flags, so we484// let the logic continue to see if ValidArgsFunction needs to be called.485return finalCmd, completions, directive, nil486}487} else {488directive = ShellCompDirectiveDefault489// check current and parent commands for a custom DefaultShellCompDirective490for cmd := finalCmd; cmd != nil; cmd = cmd.parent {491if cmd.CompletionOptions.DefaultShellCompDirective != nil {492directive = *cmd.CompletionOptions.DefaultShellCompDirective493break494}495}496497if flag == nil {498foundLocalNonPersistentFlag := false499// If TraverseChildren is true on the root command we don't check for500// local flags because we can use a local flag on a parent command501if !finalCmd.Root().TraverseChildren {502// Check if there are any local, non-persistent flags on the command-line503localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()504finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {505if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {506foundLocalNonPersistentFlag = true507}508})509}510511// Complete subcommand names, including the help command512if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {513// We only complete sub-commands if:514// - there are no arguments on the command-line and515// - there are no local, non-persistent flags on the command-line or TraverseChildren is true516for _, subCmd := range finalCmd.Commands() {517if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {518if strings.HasPrefix(subCmd.Name(), toComplete) {519completions = append(completions, CompletionWithDesc(subCmd.Name(), subCmd.Short))520}521directive = ShellCompDirectiveNoFileComp522}523}524}525526// Complete required flags even without the '-' prefix527completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)528529// Always complete ValidArgs, even if we are completing a subcommand name.530// This is for commands that have both subcommands and ValidArgs.531if len(finalCmd.ValidArgs) > 0 {532if len(finalArgs) == 0 {533// ValidArgs are only for the first argument534for _, validArg := range finalCmd.ValidArgs {535if strings.HasPrefix(validArg, toComplete) {536completions = append(completions, validArg)537}538}539directive = ShellCompDirectiveNoFileComp540541// If no completions were found within commands or ValidArgs,542// see if there are any ArgAliases that should be completed.543if len(completions) == 0 {544for _, argAlias := range finalCmd.ArgAliases {545if strings.HasPrefix(argAlias, toComplete) {546completions = append(completions, argAlias)547}548}549}550}551552// If there are ValidArgs specified (even if they don't match), we stop completion.553// Only one of ValidArgs or ValidArgsFunction can be used for a single command.554return finalCmd, completions, directive, nil555}556557// Let the logic continue so as to add any ValidArgsFunction completions,558// even if we already found sub-commands.559// This is for commands that have subcommands but also specify a ValidArgsFunction.560}561}562563// Find the completion function for the flag or command564var completionFn CompletionFunc565if flag != nil && flagCompletion {566flagCompletionMutex.RLock()567completionFn = flagCompletionFunctions[flag]568flagCompletionMutex.RUnlock()569} else {570completionFn = finalCmd.ValidArgsFunction571}572if completionFn != nil {573// Go custom completion defined for this flag or command.574// Call the registered completion function to get the completions.575var comps []Completion576comps, directive = completionFn(finalCmd, finalArgs, toComplete)577completions = append(completions, comps...)578}579580return finalCmd, completions, directive, nil581}582583func helpOrVersionFlagPresent(cmd *Command) bool {584if versionFlag := cmd.Flags().Lookup("version"); versionFlag != nil &&585len(versionFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && versionFlag.Changed {586return true587}588if helpFlag := cmd.Flags().Lookup(helpFlagName); helpFlag != nil &&589len(helpFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && helpFlag.Changed {590return true591}592return false593}594595func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []Completion {596if nonCompletableFlag(flag) {597return []Completion{}598}599600var completions []Completion601flagName := "--" + flag.Name602if strings.HasPrefix(flagName, toComplete) {603// Flag without the =604completions = append(completions, CompletionWithDesc(flagName, flag.Usage))605606// Why suggest both long forms: --flag and --flag= ?607// This forces the user to *always* have to type either an = or a space after the flag name.608// Let's be nice and avoid making users have to do that.609// Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.610// The = form will still work, we just won't suggest it.611// This also makes the list of suggested flags shorter as we avoid all the = forms.612//613// if len(flag.NoOptDefVal) == 0 {614// // Flag requires a value, so it can be suffixed with =615// flagName += "="616// completions = append(completions, CompletionWithDesc(flagName, flag.Usage))617// }618}619620flagName = "-" + flag.Shorthand621if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {622completions = append(completions, CompletionWithDesc(flagName, flag.Usage))623}624625return completions626}627628func completeRequireFlags(finalCmd *Command, toComplete string) []Completion {629var completions []Completion630631doCompleteRequiredFlags := func(flag *pflag.Flag) {632if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {633if !flag.Changed {634// If the flag is not already present, we suggest it as a completion635completions = append(completions, getFlagNameCompletions(flag, toComplete)...)636}637}638}639640// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands641// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and642// non-inherited flags.643finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {644doCompleteRequiredFlags(flag)645})646finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {647doCompleteRequiredFlags(flag)648})649650return completions651}652653func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {654if finalCmd.DisableFlagParsing {655// We only do flag completion if we are allowed to parse flags656// This is important for commands which have requested to do their own flag completion.657return nil, args, lastArg, nil658}659660var flagName string661trimmedArgs := args662flagWithEqual := false663orgLastArg := lastArg664665// When doing completion of a flag name, as soon as an argument starts with666// a '-' we know it is a flag. We cannot use isFlagArg() here as that function667// requires the flag name to be complete668if len(lastArg) > 0 && lastArg[0] == '-' {669if index := strings.Index(lastArg, "="); index >= 0 {670// Flag with an =671if strings.HasPrefix(lastArg[:index], "--") {672// Flag has full name673flagName = lastArg[2:index]674} else {675// Flag is shorthand676// We have to get the last shorthand flag name677// e.g. `-asd` => d to provide the correct completion678// https://github.com/spf13/cobra/issues/1257679flagName = lastArg[index-1 : index]680}681lastArg = lastArg[index+1:]682flagWithEqual = true683} else {684// Normal flag completion685return nil, args, lastArg, nil686}687}688689if len(flagName) == 0 {690if len(args) > 0 {691prevArg := args[len(args)-1]692if isFlagArg(prevArg) {693// Only consider the case where the flag does not contain an =.694// If the flag contains an = it means it has already been fully processed,695// so we don't need to deal with it here.696if index := strings.Index(prevArg, "="); index < 0 {697if strings.HasPrefix(prevArg, "--") {698// Flag has full name699flagName = prevArg[2:]700} else {701// Flag is shorthand702// We have to get the last shorthand flag name703// e.g. `-asd` => d to provide the correct completion704// https://github.com/spf13/cobra/issues/1257705flagName = prevArg[len(prevArg)-1:]706}707// Remove the uncompleted flag or else there could be an error created708// for an invalid value for that flag709trimmedArgs = args[:len(args)-1]710}711}712}713}714715if len(flagName) == 0 {716// Not doing flag completion717return nil, trimmedArgs, lastArg, nil718}719720flag := findFlag(finalCmd, flagName)721if flag == nil {722// Flag not supported by this command, the interspersed option might be set so return the original args723return nil, args, orgLastArg, &flagCompError{subCommand: finalCmd.Name(), flagName: flagName}724}725726if !flagWithEqual {727if len(flag.NoOptDefVal) != 0 {728// We had assumed dealing with a two-word flag but the flag is a boolean flag.729// In that case, there is no value following it, so we are not really doing flag completion.730// Reset everything to do noun completion.731trimmedArgs = args732flag = nil733}734}735736return flag, trimmedArgs, lastArg, nil737}738739// InitDefaultCompletionCmd adds a default 'completion' command to c.740// This function will do nothing if any of the following is true:741// 1- the feature has been explicitly disabled by the program,742// 2- c has no subcommands (to avoid creating one),743// 3- c already has a 'completion' command provided by the program.744func (c *Command) InitDefaultCompletionCmd(args ...string) {745if c.CompletionOptions.DisableDefaultCmd {746return747}748749for _, cmd := range c.commands {750if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) {751// A completion command is already available752return753}754}755756haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions757758// Special case to know if there are sub-commands or not.759hasSubCommands := false760for _, cmd := range c.commands {761if cmd.Name() != ShellCompRequestCmd && cmd.Name() != helpCommandName {762// We found a real sub-command (not 'help' or '__complete')763hasSubCommands = true764break765}766}767768completionCmd := &Command{769Use: compCmdName,770Short: "Generate the autocompletion script for the specified shell",771Long: fmt.Sprintf(`Generate the autocompletion script for %[1]s for the specified shell.772See each sub-command's help for details on how to use the generated script.773`, c.Root().Name()),774Args: NoArgs,775ValidArgsFunction: NoFileCompletions,776Hidden: c.CompletionOptions.HiddenDefaultCmd,777GroupID: c.completionCommandGroupID,778}779c.AddCommand(completionCmd)780781if !hasSubCommands {782// If the 'completion' command will be the only sub-command,783// we only create it if it is actually being called.784// This avoids breaking programs that would suddenly find themselves with785// a subcommand, which would prevent them from accepting arguments.786// We also create the 'completion' command if the user is triggering787// shell completion for it (prog __complete completion '')788subCmd, cmdArgs, err := c.Find(args)789if err != nil || subCmd.Name() != compCmdName &&790(subCmd.Name() != ShellCompRequestCmd || len(cmdArgs) <= 1 || cmdArgs[0] != compCmdName) {791// The completion command is not being called or being completed so we remove it.792c.RemoveCommand(completionCmd)793return794}795}796797out := c.OutOrStdout()798noDesc := c.CompletionOptions.DisableDescriptions799shortDesc := "Generate the autocompletion script for %s"800bash := &Command{801Use: "bash",802Short: fmt.Sprintf(shortDesc, "bash"),803Long: fmt.Sprintf(`Generate the autocompletion script for the bash shell.804805This script depends on the 'bash-completion' package.806If it is not installed already, you can install it via your OS's package manager.807808To load completions in your current shell session:809810source <(%[1]s completion bash)811812To load completions for every new session, execute once:813814#### Linux:815816%[1]s completion bash > /etc/bash_completion.d/%[1]s817818#### macOS:819820%[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s821822You will need to start a new shell for this setup to take effect.823`, c.Root().Name()),824Args: NoArgs,825DisableFlagsInUseLine: true,826ValidArgsFunction: NoFileCompletions,827RunE: func(cmd *Command, args []string) error {828return cmd.Root().GenBashCompletionV2(out, !noDesc)829},830}831if haveNoDescFlag {832bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)833}834835zsh := &Command{836Use: "zsh",837Short: fmt.Sprintf(shortDesc, "zsh"),838Long: fmt.Sprintf(`Generate the autocompletion script for the zsh shell.839840If shell completion is not already enabled in your environment you will need841to enable it. You can execute the following once:842843echo "autoload -U compinit; compinit" >> ~/.zshrc844845To load completions in your current shell session:846847source <(%[1]s completion zsh)848849To load completions for every new session, execute once:850851#### Linux:852853%[1]s completion zsh > "${fpath[1]}/_%[1]s"854855#### macOS:856857%[1]s completion zsh > $(brew --prefix)/share/zsh/site-functions/_%[1]s858859You will need to start a new shell for this setup to take effect.860`, c.Root().Name()),861Args: NoArgs,862ValidArgsFunction: NoFileCompletions,863RunE: func(cmd *Command, args []string) error {864if noDesc {865return cmd.Root().GenZshCompletionNoDesc(out)866}867return cmd.Root().GenZshCompletion(out)868},869}870if haveNoDescFlag {871zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)872}873874fish := &Command{875Use: "fish",876Short: fmt.Sprintf(shortDesc, "fish"),877Long: fmt.Sprintf(`Generate the autocompletion script for the fish shell.878879To load completions in your current shell session:880881%[1]s completion fish | source882883To load completions for every new session, execute once:884885%[1]s completion fish > ~/.config/fish/completions/%[1]s.fish886887You will need to start a new shell for this setup to take effect.888`, c.Root().Name()),889Args: NoArgs,890ValidArgsFunction: NoFileCompletions,891RunE: func(cmd *Command, args []string) error {892return cmd.Root().GenFishCompletion(out, !noDesc)893},894}895if haveNoDescFlag {896fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)897}898899powershell := &Command{900Use: "powershell",901Short: fmt.Sprintf(shortDesc, "powershell"),902Long: fmt.Sprintf(`Generate the autocompletion script for powershell.903904To load completions in your current shell session:905906%[1]s completion powershell | Out-String | Invoke-Expression907908To load completions for every new session, add the output of the above command909to your powershell profile.910`, c.Root().Name()),911Args: NoArgs,912ValidArgsFunction: NoFileCompletions,913RunE: func(cmd *Command, args []string) error {914if noDesc {915return cmd.Root().GenPowerShellCompletion(out)916}917return cmd.Root().GenPowerShellCompletionWithDesc(out)918919},920}921if haveNoDescFlag {922powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)923}924925completionCmd.AddCommand(bash, zsh, fish, powershell)926}927928func findFlag(cmd *Command, name string) *pflag.Flag {929flagSet := cmd.Flags()930if len(name) == 1 {931// First convert the short flag into a long flag932// as the cmd.Flag() search only accepts long flags933if short := flagSet.ShorthandLookup(name); short != nil {934name = short.Name935} else {936set := cmd.InheritedFlags()937if short = set.ShorthandLookup(name); short != nil {938name = short.Name939} else {940return nil941}942}943}944return cmd.Flag(name)945}946947// CompDebug prints the specified string to the same file as where the948// completion script prints its logs.949// Note that completion printouts should never be on stdout as they would950// be wrongly interpreted as actual completion choices by the completion script.951func CompDebug(msg string, printToStdErr bool) {952msg = fmt.Sprintf("[Debug] %s", msg)953954// Such logs are only printed when the user has set the environment955// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.956if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {957f, err := os.OpenFile(path,958os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)959if err == nil {960defer f.Close()961WriteStringAndCheck(f, msg)962}963}964965if printToStdErr {966// Must print to stderr for this not to be read by the completion script.967fmt.Fprint(os.Stderr, msg)968}969}970971// CompDebugln prints the specified string with a newline at the end972// to the same file as where the completion script prints its logs.973// Such logs are only printed when the user has set the environment974// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.975func CompDebugln(msg string, printToStdErr bool) {976CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr)977}978979// CompError prints the specified completion message to stderr.980func CompError(msg string) {981msg = fmt.Sprintf("[Error] %s", msg)982CompDebug(msg, true)983}984985// CompErrorln prints the specified completion message to stderr with a newline at the end.986func CompErrorln(msg string) {987CompError(fmt.Sprintf("%s\n", msg))988}989990// These values should not be changed: users will be using them explicitly.991const (992configEnvVarGlobalPrefix = "COBRA"993configEnvVarSuffixDescriptions = "COMPLETION_DESCRIPTIONS"994)995996var configEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)997998// configEnvVar returns the name of the program-specific configuration environment999// variable. It has the format <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the1000// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.1001func configEnvVar(name, suffix string) string {1002// This format should not be changed: users will be using it explicitly.1003v := strings.ToUpper(fmt.Sprintf("%s_%s", name, suffix))1004v = configEnvVarPrefixSubstRegexp.ReplaceAllString(v, "_")1005return v1006}10071008// getEnvConfig returns the value of the configuration environment variable1009// <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the root command in upper1010// case, with all non-ASCII-alphanumeric characters replaced by `_`.1011// If the value is empty or not set, the value of the environment variable1012// COBRA_<SUFFIX> is returned instead.1013func getEnvConfig(cmd *Command, suffix string) string {1014v := os.Getenv(configEnvVar(cmd.Root().Name(), suffix))1015if v == "" {1016v = os.Getenv(configEnvVar(configEnvVarGlobalPrefix, suffix))1017}1018return v1019}102010211022