|
@@ -1,9 +1,24 @@
|
|
|
+// Copyright 2013-2022 The Cobra Authors
|
|
|
+//
|
|
|
+// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+// you may not use this file except in compliance with the License.
|
|
|
+// You may obtain a copy of the License at
|
|
|
+//
|
|
|
+// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+//
|
|
|
+// Unless required by applicable law or agreed to in writing, software
|
|
|
+// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+// See the License for the specific language governing permissions and
|
|
|
+// limitations under the License.
|
|
|
+
|
|
|
package cobra
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
"os"
|
|
|
"strings"
|
|
|
+ "sync"
|
|
|
|
|
|
"github.com/spf13/pflag"
|
|
|
)
|
|
@@ -17,13 +32,25 @@ const (
|
|
|
ShellCompNoDescRequestCmd = "__completeNoDesc"
|
|
|
)
|
|
|
|
|
|
-// Global map of flag completion functions.
|
|
|
+// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
|
|
|
var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}
|
|
|
|
|
|
+// lock for reading and writing from flagCompletionFunctions
|
|
|
+var flagCompletionMutex = &sync.RWMutex{}
|
|
|
+
|
|
|
// ShellCompDirective is a bit map representing the different behaviors the shell
|
|
|
// can be instructed to have once completions have been provided.
|
|
|
type ShellCompDirective int
|
|
|
|
|
|
+type flagCompError struct {
|
|
|
+ subCommand string
|
|
|
+ flagName string
|
|
|
+}
|
|
|
+
|
|
|
+func (e *flagCompError) Error() string {
|
|
|
+ return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'"
|
|
|
+}
|
|
|
+
|
|
|
const (
|
|
|
// ShellCompDirectiveError indicates an error occurred and completions should be ignored.
|
|
|
ShellCompDirectiveError ShellCompDirective = 1 << iota
|
|
@@ -34,7 +61,6 @@ const (
|
|
|
|
|
|
// ShellCompDirectiveNoFileComp indicates that the shell should not provide
|
|
|
// file completion even when no completion is provided.
|
|
|
- // This currently does not work for zsh or bash < 4
|
|
|
ShellCompDirectiveNoFileComp
|
|
|
|
|
|
// ShellCompDirectiveFilterFileExt indicates that the provided completions
|
|
@@ -63,12 +89,51 @@ const (
|
|
|
ShellCompDirectiveDefault ShellCompDirective = 0
|
|
|
)
|
|
|
|
|
|
+const (
|
|
|
+ // Constants for the completion command
|
|
|
+ compCmdName = "completion"
|
|
|
+ compCmdNoDescFlagName = "no-descriptions"
|
|
|
+ compCmdNoDescFlagDesc = "disable completion descriptions"
|
|
|
+ compCmdNoDescFlagDefault = false
|
|
|
+)
|
|
|
+
|
|
|
+// CompletionOptions are the options to control shell completion
|
|
|
+type CompletionOptions struct {
|
|
|
+ // DisableDefaultCmd prevents Cobra from creating a default 'completion' command
|
|
|
+ DisableDefaultCmd bool
|
|
|
+ // DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag
|
|
|
+ // for shells that support completion descriptions
|
|
|
+ DisableNoDescFlag bool
|
|
|
+ // DisableDescriptions turns off all completion descriptions for shells
|
|
|
+ // that support them
|
|
|
+ DisableDescriptions bool
|
|
|
+ // HiddenDefaultCmd makes the default 'completion' command hidden
|
|
|
+ HiddenDefaultCmd bool
|
|
|
+}
|
|
|
+
|
|
|
+// NoFileCompletions can be used to disable file completion for commands that should
|
|
|
+// not trigger file completions.
|
|
|
+func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
|
|
+ return nil, ShellCompDirectiveNoFileComp
|
|
|
+}
|
|
|
+
|
|
|
+// FixedCompletions can be used to create a completion function which always
|
|
|
+// returns the same results.
|
|
|
+func FixedCompletions(choices []string, directive ShellCompDirective) func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
|
|
+ return func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
|
|
+ return choices, directive
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
|
|
|
func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error {
|
|
|
flag := c.Flag(flagName)
|
|
|
if flag == nil {
|
|
|
return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
|
|
|
}
|
|
|
+ flagCompletionMutex.Lock()
|
|
|
+ defer flagCompletionMutex.Unlock()
|
|
|
+
|
|
|
if _, exists := flagCompletionFunctions[flag]; exists {
|
|
|
return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName)
|
|
|
}
|
|
@@ -127,6 +192,12 @@ func (c *Command) initCompleteCmd(args []string) {
|
|
|
|
|
|
noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
|
|
|
for _, comp := range completions {
|
|
|
+ if GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable {
|
|
|
+ // Remove all activeHelp entries in this case
|
|
|
+ if strings.HasPrefix(comp, activeHelpMarker) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
if noDescriptions {
|
|
|
// Remove any description that may be included following a tab character.
|
|
|
comp = strings.Split(comp, "\t")[0]
|
|
@@ -149,10 +220,6 @@ func (c *Command) initCompleteCmd(args []string) {
|
|
|
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
|
|
|
}
|
|
|
|
|
|
- if directive >= shellCompDirectiveMaxValue {
|
|
|
- directive = ShellCompDirectiveDefault
|
|
|
- }
|
|
|
-
|
|
|
// As the last printout, print the completion directive for the completion script to parse.
|
|
|
// The directive integer must be that last character following a single colon (:).
|
|
|
// The completion script expects :<directive>
|
|
@@ -189,29 +256,75 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
|
|
if c.Root().TraverseChildren {
|
|
|
finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
|
|
|
} else {
|
|
|
- finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
|
|
|
+ // For Root commands that don't specify any value for their Args fields, when we call
|
|
|
+ // Find(), if those Root commands don't have any sub-commands, they will accept arguments.
|
|
|
+ // However, because we have added the __complete sub-command in the current code path, the
|
|
|
+ // call to Find() -> legacyArgs() will return an error if there are any arguments.
|
|
|
+ // To avoid this, we first remove the __complete command to get back to having no sub-commands.
|
|
|
+ rootCmd := c.Root()
|
|
|
+ if len(rootCmd.Commands()) == 1 {
|
|
|
+ rootCmd.RemoveCommand(c)
|
|
|
+ }
|
|
|
+
|
|
|
+ finalCmd, finalArgs, err = rootCmd.Find(trimmedArgs)
|
|
|
}
|
|
|
if err != nil {
|
|
|
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
|
|
|
return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
|
|
|
}
|
|
|
+ finalCmd.ctx = c.ctx
|
|
|
+
|
|
|
+ // These flags are normally added when `execute()` is called on `finalCmd`,
|
|
|
+ // however, when doing completion, we don't call `finalCmd.execute()`.
|
|
|
+ // Let's add the --help and --version flag ourselves.
|
|
|
+ finalCmd.InitDefaultHelpFlag()
|
|
|
+ finalCmd.InitDefaultVersionFlag()
|
|
|
|
|
|
// Check if we are doing flag value completion before parsing the flags.
|
|
|
// This is important because if we are completing a flag value, we need to also
|
|
|
// remove the flag name argument from the list of finalArgs or else the parsing
|
|
|
// could fail due to an invalid value (incomplete) for the flag.
|
|
|
- flag, finalArgs, toComplete, err := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
|
|
|
- if err != nil {
|
|
|
- // Error while attempting to parse flags
|
|
|
- return finalCmd, []string{}, ShellCompDirectiveDefault, err
|
|
|
- }
|
|
|
+ flag, finalArgs, toComplete, flagErr := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
|
|
|
+
|
|
|
+ // Check if interspersed is false or -- was set on a previous arg.
|
|
|
+ // This works by counting the arguments. Normally -- is not counted as arg but
|
|
|
+ // if -- was already set or interspersed is false and there is already one arg then
|
|
|
+ // the extra added -- is counted as arg.
|
|
|
+ flagCompletion := true
|
|
|
+ _ = finalCmd.ParseFlags(append(finalArgs, "--"))
|
|
|
+ newArgCount := finalCmd.Flags().NArg()
|
|
|
|
|
|
// Parse the flags early so we can check if required flags are set
|
|
|
if err = finalCmd.ParseFlags(finalArgs); err != nil {
|
|
|
return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
|
|
|
}
|
|
|
|
|
|
- if flag != nil {
|
|
|
+ realArgCount := finalCmd.Flags().NArg()
|
|
|
+ if newArgCount > realArgCount {
|
|
|
+ // don't do flag completion (see above)
|
|
|
+ flagCompletion = false
|
|
|
+ }
|
|
|
+ // Error while attempting to parse flags
|
|
|
+ if flagErr != nil {
|
|
|
+ // If error type is flagCompError and we don't want flagCompletion we should ignore the error
|
|
|
+ if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) {
|
|
|
+ return finalCmd, []string{}, ShellCompDirectiveDefault, flagErr
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Look for the --help or --version flags. If they are present,
|
|
|
+ // there should be no further completions.
|
|
|
+ if helpOrVersionFlagPresent(finalCmd) {
|
|
|
+ return finalCmd, []string{}, ShellCompDirectiveNoFileComp, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // We only remove the flags from the arguments if DisableFlagParsing is not set.
|
|
|
+ // This is important for commands which have requested to do their own flag completion.
|
|
|
+ if !finalCmd.DisableFlagParsing {
|
|
|
+ finalArgs = finalCmd.Flags().Args()
|
|
|
+ }
|
|
|
+
|
|
|
+ if flag != nil && flagCompletion {
|
|
|
// Check if we are completing a flag value subject to annotations
|
|
|
if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
|
|
|
if len(validExts) != 0 {
|
|
@@ -235,12 +348,19 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ var completions []string
|
|
|
+ var directive ShellCompDirective
|
|
|
+
|
|
|
+ // Enforce flag groups before doing flag completions
|
|
|
+ finalCmd.enforceFlagGroupsForCompletion()
|
|
|
+
|
|
|
+ // Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true;
|
|
|
+ // doing this allows for completion of persistent flag names even for commands that disable flag parsing.
|
|
|
+ //
|
|
|
// When doing completion of a flag name, as soon as an argument starts with
|
|
|
// a '-' we know it is a flag. We cannot use isFlagArg() here as it requires
|
|
|
// the flag name to be complete
|
|
|
- if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
|
|
|
- var completions []string
|
|
|
-
|
|
|
+ if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion {
|
|
|
// First check for required flags
|
|
|
completions = completeRequireFlags(finalCmd, toComplete)
|
|
|
|
|
@@ -267,92 +387,94 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- directive := ShellCompDirectiveNoFileComp
|
|
|
+ directive = ShellCompDirectiveNoFileComp
|
|
|
if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
|
|
|
// If there is a single completion, the shell usually adds a space
|
|
|
// after the completion. We don't want that if the flag ends with an =
|
|
|
directive = ShellCompDirectiveNoSpace
|
|
|
}
|
|
|
- return finalCmd, completions, directive, nil
|
|
|
- }
|
|
|
-
|
|
|
- // We only remove the flags from the arguments if DisableFlagParsing is not set.
|
|
|
- // This is important for commands which have requested to do their own flag completion.
|
|
|
- if !finalCmd.DisableFlagParsing {
|
|
|
- finalArgs = finalCmd.Flags().Args()
|
|
|
- }
|
|
|
|
|
|
- var completions []string
|
|
|
- directive := ShellCompDirectiveDefault
|
|
|
- if flag == nil {
|
|
|
- foundLocalNonPersistentFlag := false
|
|
|
- // If TraverseChildren is true on the root command we don't check for
|
|
|
- // local flags because we can use a local flag on a parent command
|
|
|
- if !finalCmd.Root().TraverseChildren {
|
|
|
- // Check if there are any local, non-persistent flags on the command-line
|
|
|
- localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
|
|
|
- finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
|
|
- if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
|
|
|
- foundLocalNonPersistentFlag = true
|
|
|
- }
|
|
|
- })
|
|
|
+ if !finalCmd.DisableFlagParsing {
|
|
|
+ // If DisableFlagParsing==false, we have completed the flags as known by Cobra;
|
|
|
+ // we can return what we found.
|
|
|
+ // If DisableFlagParsing==true, Cobra may not be aware of all flags, so we
|
|
|
+ // let the logic continue to see if ValidArgsFunction needs to be called.
|
|
|
+ return finalCmd, completions, directive, nil
|
|
|
}
|
|
|
+ } else {
|
|
|
+ directive = ShellCompDirectiveDefault
|
|
|
+ if flag == nil {
|
|
|
+ foundLocalNonPersistentFlag := false
|
|
|
+ // If TraverseChildren is true on the root command we don't check for
|
|
|
+ // local flags because we can use a local flag on a parent command
|
|
|
+ if !finalCmd.Root().TraverseChildren {
|
|
|
+ // Check if there are any local, non-persistent flags on the command-line
|
|
|
+ localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
|
|
|
+ finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
|
|
+ if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
|
|
|
+ foundLocalNonPersistentFlag = true
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
- // Complete subcommand names, including the help command
|
|
|
- if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
|
|
|
- // We only complete sub-commands if:
|
|
|
- // - there are no arguments on the command-line and
|
|
|
- // - there are no local, non-peristent flag on the command-line or TraverseChildren is true
|
|
|
- for _, subCmd := range finalCmd.Commands() {
|
|
|
- if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
|
|
|
- if strings.HasPrefix(subCmd.Name(), toComplete) {
|
|
|
- completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
|
|
|
+ // Complete subcommand names, including the help command
|
|
|
+ if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
|
|
|
+ // We only complete sub-commands if:
|
|
|
+ // - there are no arguments on the command-line and
|
|
|
+ // - there are no local, non-persistent flags on the command-line or TraverseChildren is true
|
|
|
+ for _, subCmd := range finalCmd.Commands() {
|
|
|
+ if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
|
|
|
+ if strings.HasPrefix(subCmd.Name(), toComplete) {
|
|
|
+ completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
|
|
|
+ }
|
|
|
+ directive = ShellCompDirectiveNoFileComp
|
|
|
}
|
|
|
- directive = ShellCompDirectiveNoFileComp
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // Complete required flags even without the '-' prefix
|
|
|
- completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
|
|
|
-
|
|
|
- // Always complete ValidArgs, even if we are completing a subcommand name.
|
|
|
- // This is for commands that have both subcommands and ValidArgs.
|
|
|
- if len(finalCmd.ValidArgs) > 0 {
|
|
|
- if len(finalArgs) == 0 {
|
|
|
- // ValidArgs are only for the first argument
|
|
|
- for _, validArg := range finalCmd.ValidArgs {
|
|
|
- if strings.HasPrefix(validArg, toComplete) {
|
|
|
- completions = append(completions, validArg)
|
|
|
+ // Complete required flags even without the '-' prefix
|
|
|
+ completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
|
|
|
+
|
|
|
+ // Always complete ValidArgs, even if we are completing a subcommand name.
|
|
|
+ // This is for commands that have both subcommands and ValidArgs.
|
|
|
+ if len(finalCmd.ValidArgs) > 0 {
|
|
|
+ if len(finalArgs) == 0 {
|
|
|
+ // ValidArgs are only for the first argument
|
|
|
+ for _, validArg := range finalCmd.ValidArgs {
|
|
|
+ if strings.HasPrefix(validArg, toComplete) {
|
|
|
+ completions = append(completions, validArg)
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- directive = ShellCompDirectiveNoFileComp
|
|
|
-
|
|
|
- // If no completions were found within commands or ValidArgs,
|
|
|
- // see if there are any ArgAliases that should be completed.
|
|
|
- if len(completions) == 0 {
|
|
|
- for _, argAlias := range finalCmd.ArgAliases {
|
|
|
- if strings.HasPrefix(argAlias, toComplete) {
|
|
|
- completions = append(completions, argAlias)
|
|
|
+ directive = ShellCompDirectiveNoFileComp
|
|
|
+
|
|
|
+ // If no completions were found within commands or ValidArgs,
|
|
|
+ // see if there are any ArgAliases that should be completed.
|
|
|
+ if len(completions) == 0 {
|
|
|
+ for _, argAlias := range finalCmd.ArgAliases {
|
|
|
+ if strings.HasPrefix(argAlias, toComplete) {
|
|
|
+ completions = append(completions, argAlias)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // If there are ValidArgs specified (even if they don't match), we stop completion.
|
|
|
+ // Only one of ValidArgs or ValidArgsFunction can be used for a single command.
|
|
|
+ return finalCmd, completions, directive, nil
|
|
|
}
|
|
|
|
|
|
- // If there are ValidArgs specified (even if they don't match), we stop completion.
|
|
|
- // Only one of ValidArgs or ValidArgsFunction can be used for a single command.
|
|
|
- return finalCmd, completions, directive, nil
|
|
|
+ // Let the logic continue so as to add any ValidArgsFunction completions,
|
|
|
+ // even if we already found sub-commands.
|
|
|
+ // This is for commands that have subcommands but also specify a ValidArgsFunction.
|
|
|
}
|
|
|
-
|
|
|
- // Let the logic continue so as to add any ValidArgsFunction completions,
|
|
|
- // even if we already found sub-commands.
|
|
|
- // This is for commands that have subcommands but also specify a ValidArgsFunction.
|
|
|
}
|
|
|
|
|
|
// Find the completion function for the flag or command
|
|
|
var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
|
|
|
- if flag != nil {
|
|
|
+ if flag != nil && flagCompletion {
|
|
|
+ flagCompletionMutex.RLock()
|
|
|
completionFn = flagCompletionFunctions[flag]
|
|
|
+ flagCompletionMutex.RUnlock()
|
|
|
} else {
|
|
|
completionFn = finalCmd.ValidArgsFunction
|
|
|
}
|
|
@@ -367,6 +489,18 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
|
|
return finalCmd, completions, directive, nil
|
|
|
}
|
|
|
|
|
|
+func helpOrVersionFlagPresent(cmd *Command) bool {
|
|
|
+ if versionFlag := cmd.Flags().Lookup("version"); versionFlag != nil &&
|
|
|
+ len(versionFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && versionFlag.Changed {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ if helpFlag := cmd.Flags().Lookup("help"); helpFlag != nil &&
|
|
|
+ len(helpFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && helpFlag.Changed {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
|
|
|
if nonCompletableFlag(flag) {
|
|
|
return []string{}
|
|
@@ -435,6 +569,7 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
|
|
|
var flagName string
|
|
|
trimmedArgs := args
|
|
|
flagWithEqual := false
|
|
|
+ orgLastArg := lastArg
|
|
|
|
|
|
// When doing completion of a flag name, as soon as an argument starts with
|
|
|
// a '-' we know it is a flag. We cannot use isFlagArg() here as that function
|
|
@@ -442,7 +577,16 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
|
|
|
if len(lastArg) > 0 && lastArg[0] == '-' {
|
|
|
if index := strings.Index(lastArg, "="); index >= 0 {
|
|
|
// Flag with an =
|
|
|
- flagName = strings.TrimLeft(lastArg[:index], "-")
|
|
|
+ if strings.HasPrefix(lastArg[:index], "--") {
|
|
|
+ // Flag has full name
|
|
|
+ flagName = lastArg[2:index]
|
|
|
+ } else {
|
|
|
+ // Flag is shorthand
|
|
|
+ // We have to get the last shorthand flag name
|
|
|
+ // e.g. `-asd` => d to provide the correct completion
|
|
|
+ // https://github.com/spf13/cobra/issues/1257
|
|
|
+ flagName = lastArg[index-1 : index]
|
|
|
+ }
|
|
|
lastArg = lastArg[index+1:]
|
|
|
flagWithEqual = true
|
|
|
} else {
|
|
@@ -459,8 +603,16 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
|
|
|
// If the flag contains an = it means it has already been fully processed,
|
|
|
// so we don't need to deal with it here.
|
|
|
if index := strings.Index(prevArg, "="); index < 0 {
|
|
|
- flagName = strings.TrimLeft(prevArg, "-")
|
|
|
-
|
|
|
+ if strings.HasPrefix(prevArg, "--") {
|
|
|
+ // Flag has full name
|
|
|
+ flagName = prevArg[2:]
|
|
|
+ } else {
|
|
|
+ // Flag is shorthand
|
|
|
+ // We have to get the last shorthand flag name
|
|
|
+ // e.g. `-asd` => d to provide the correct completion
|
|
|
+ // https://github.com/spf13/cobra/issues/1257
|
|
|
+ flagName = prevArg[len(prevArg)-1:]
|
|
|
+ }
|
|
|
// Remove the uncompleted flag or else there could be an error created
|
|
|
// for an invalid value for that flag
|
|
|
trimmedArgs = args[:len(args)-1]
|
|
@@ -476,9 +628,8 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
|
|
|
|
|
|
flag := findFlag(finalCmd, flagName)
|
|
|
if flag == nil {
|
|
|
- // Flag not supported by this command, nothing to complete
|
|
|
- err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName)
|
|
|
- return nil, nil, "", err
|
|
|
+ // Flag not supported by this command, the interspersed option might be set so return the original args
|
|
|
+ return nil, args, orgLastArg, &flagCompError{subCommand: finalCmd.Name(), flagName: flagName}
|
|
|
}
|
|
|
|
|
|
if !flagWithEqual {
|
|
@@ -494,6 +645,169 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p
|
|
|
return flag, trimmedArgs, lastArg, nil
|
|
|
}
|
|
|
|
|
|
+// InitDefaultCompletionCmd adds a default 'completion' command to c.
|
|
|
+// This function will do nothing if any of the following is true:
|
|
|
+// 1- the feature has been explicitly disabled by the program,
|
|
|
+// 2- c has no subcommands (to avoid creating one),
|
|
|
+// 3- c already has a 'completion' command provided by the program.
|
|
|
+func (c *Command) InitDefaultCompletionCmd() {
|
|
|
+ if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, cmd := range c.commands {
|
|
|
+ if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) {
|
|
|
+ // A completion command is already available
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions
|
|
|
+
|
|
|
+ completionCmd := &Command{
|
|
|
+ Use: compCmdName,
|
|
|
+ Short: "Generate the autocompletion script for the specified shell",
|
|
|
+ Long: fmt.Sprintf(`Generate the autocompletion script for %[1]s for the specified shell.
|
|
|
+See each sub-command's help for details on how to use the generated script.
|
|
|
+`, c.Root().Name()),
|
|
|
+ Args: NoArgs,
|
|
|
+ ValidArgsFunction: NoFileCompletions,
|
|
|
+ Hidden: c.CompletionOptions.HiddenDefaultCmd,
|
|
|
+ GroupID: c.completionCommandGroupID,
|
|
|
+ }
|
|
|
+ c.AddCommand(completionCmd)
|
|
|
+
|
|
|
+ out := c.OutOrStdout()
|
|
|
+ noDesc := c.CompletionOptions.DisableDescriptions
|
|
|
+ shortDesc := "Generate the autocompletion script for %s"
|
|
|
+ bash := &Command{
|
|
|
+ Use: "bash",
|
|
|
+ Short: fmt.Sprintf(shortDesc, "bash"),
|
|
|
+ Long: fmt.Sprintf(`Generate the autocompletion script for the bash shell.
|
|
|
+
|
|
|
+This script depends on the 'bash-completion' package.
|
|
|
+If it is not installed already, you can install it via your OS's package manager.
|
|
|
+
|
|
|
+To load completions in your current shell session:
|
|
|
+
|
|
|
+ source <(%[1]s completion bash)
|
|
|
+
|
|
|
+To load completions for every new session, execute once:
|
|
|
+
|
|
|
+#### Linux:
|
|
|
+
|
|
|
+ %[1]s completion bash > /etc/bash_completion.d/%[1]s
|
|
|
+
|
|
|
+#### macOS:
|
|
|
+
|
|
|
+ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
|
|
|
+
|
|
|
+You will need to start a new shell for this setup to take effect.
|
|
|
+`, c.Root().Name()),
|
|
|
+ Args: NoArgs,
|
|
|
+ DisableFlagsInUseLine: true,
|
|
|
+ ValidArgsFunction: NoFileCompletions,
|
|
|
+ RunE: func(cmd *Command, args []string) error {
|
|
|
+ return cmd.Root().GenBashCompletionV2(out, !noDesc)
|
|
|
+ },
|
|
|
+ }
|
|
|
+ if haveNoDescFlag {
|
|
|
+ bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
|
|
|
+ }
|
|
|
+
|
|
|
+ zsh := &Command{
|
|
|
+ Use: "zsh",
|
|
|
+ Short: fmt.Sprintf(shortDesc, "zsh"),
|
|
|
+ Long: fmt.Sprintf(`Generate the autocompletion script for the zsh shell.
|
|
|
+
|
|
|
+If shell completion is not already enabled in your environment you will need
|
|
|
+to enable it. You can execute the following once:
|
|
|
+
|
|
|
+ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
|
|
+
|
|
|
+To load completions in your current shell session:
|
|
|
+
|
|
|
+ source <(%[1]s completion zsh); compdef _%[1]s %[1]s
|
|
|
+
|
|
|
+To load completions for every new session, execute once:
|
|
|
+
|
|
|
+#### Linux:
|
|
|
+
|
|
|
+ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
|
|
|
+
|
|
|
+#### macOS:
|
|
|
+
|
|
|
+ %[1]s completion zsh > $(brew --prefix)/share/zsh/site-functions/_%[1]s
|
|
|
+
|
|
|
+You will need to start a new shell for this setup to take effect.
|
|
|
+`, c.Root().Name()),
|
|
|
+ Args: NoArgs,
|
|
|
+ ValidArgsFunction: NoFileCompletions,
|
|
|
+ RunE: func(cmd *Command, args []string) error {
|
|
|
+ if noDesc {
|
|
|
+ return cmd.Root().GenZshCompletionNoDesc(out)
|
|
|
+ }
|
|
|
+ return cmd.Root().GenZshCompletion(out)
|
|
|
+ },
|
|
|
+ }
|
|
|
+ if haveNoDescFlag {
|
|
|
+ zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
|
|
|
+ }
|
|
|
+
|
|
|
+ fish := &Command{
|
|
|
+ Use: "fish",
|
|
|
+ Short: fmt.Sprintf(shortDesc, "fish"),
|
|
|
+ Long: fmt.Sprintf(`Generate the autocompletion script for the fish shell.
|
|
|
+
|
|
|
+To load completions in your current shell session:
|
|
|
+
|
|
|
+ %[1]s completion fish | source
|
|
|
+
|
|
|
+To load completions for every new session, execute once:
|
|
|
+
|
|
|
+ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
|
|
|
+
|
|
|
+You will need to start a new shell for this setup to take effect.
|
|
|
+`, c.Root().Name()),
|
|
|
+ Args: NoArgs,
|
|
|
+ ValidArgsFunction: NoFileCompletions,
|
|
|
+ RunE: func(cmd *Command, args []string) error {
|
|
|
+ return cmd.Root().GenFishCompletion(out, !noDesc)
|
|
|
+ },
|
|
|
+ }
|
|
|
+ if haveNoDescFlag {
|
|
|
+ fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
|
|
|
+ }
|
|
|
+
|
|
|
+ powershell := &Command{
|
|
|
+ Use: "powershell",
|
|
|
+ Short: fmt.Sprintf(shortDesc, "powershell"),
|
|
|
+ Long: fmt.Sprintf(`Generate the autocompletion script for powershell.
|
|
|
+
|
|
|
+To load completions in your current shell session:
|
|
|
+
|
|
|
+ %[1]s completion powershell | Out-String | Invoke-Expression
|
|
|
+
|
|
|
+To load completions for every new session, add the output of the above command
|
|
|
+to your powershell profile.
|
|
|
+`, c.Root().Name()),
|
|
|
+ Args: NoArgs,
|
|
|
+ ValidArgsFunction: NoFileCompletions,
|
|
|
+ RunE: func(cmd *Command, args []string) error {
|
|
|
+ if noDesc {
|
|
|
+ return cmd.Root().GenPowerShellCompletion(out)
|
|
|
+ }
|
|
|
+ return cmd.Root().GenPowerShellCompletionWithDesc(out)
|
|
|
+
|
|
|
+ },
|
|
|
+ }
|
|
|
+ if haveNoDescFlag {
|
|
|
+ powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
|
|
|
+ }
|
|
|
+
|
|
|
+ completionCmd.AddCommand(bash, zsh, fish, powershell)
|
|
|
+}
|
|
|
+
|
|
|
func findFlag(cmd *Command, name string) *pflag.Flag {
|
|
|
flagSet := cmd.Flags()
|
|
|
if len(name) == 1 {
|