aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/spf13/cobra/custom_completions.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/spf13/cobra/custom_completions.go')
-rw-r--r--vendor/github.com/spf13/cobra/custom_completions.go301
1 files changed, 237 insertions, 64 deletions
diff --git a/vendor/github.com/spf13/cobra/custom_completions.go b/vendor/github.com/spf13/cobra/custom_completions.go
index ba57327c1..f9e88e081 100644
--- a/vendor/github.com/spf13/cobra/custom_completions.go
+++ b/vendor/github.com/spf13/cobra/custom_completions.go
@@ -1,7 +1,6 @@
package cobra
import (
- "errors"
"fmt"
"os"
"strings"
@@ -38,8 +37,29 @@ const (
// This currently does not work for zsh or bash < 4
ShellCompDirectiveNoFileComp
+ // ShellCompDirectiveFilterFileExt indicates that the provided completions
+ // should be used as file extension filters.
+ // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
+ // is a shortcut to using this directive explicitly. The BashCompFilenameExt
+ // annotation can also be used to obtain the same behavior for flags.
+ ShellCompDirectiveFilterFileExt
+
+ // ShellCompDirectiveFilterDirs indicates that only directory names should
+ // be provided in file completion. To request directory names within another
+ // directory, the returned completions should specify the directory within
+ // which to search. The BashCompSubdirsInDir annotation can be used to
+ // obtain the same behavior but only for flags.
+ ShellCompDirectiveFilterDirs
+
+ // ===========================================================================
+
+ // All directives using iota should be above this one.
+ // For internal use.
+ shellCompDirectiveMaxValue
+
// ShellCompDirectiveDefault indicates to let the shell perform its default
// behavior after completions have been provided.
+ // This one must be last to avoid messing up the iota count.
ShellCompDirectiveDefault ShellCompDirective = 0
)
@@ -68,11 +88,17 @@ func (d ShellCompDirective) string() string {
if d&ShellCompDirectiveNoFileComp != 0 {
directives = append(directives, "ShellCompDirectiveNoFileComp")
}
+ if d&ShellCompDirectiveFilterFileExt != 0 {
+ directives = append(directives, "ShellCompDirectiveFilterFileExt")
+ }
+ if d&ShellCompDirectiveFilterDirs != 0 {
+ directives = append(directives, "ShellCompDirectiveFilterDirs")
+ }
if len(directives) == 0 {
directives = append(directives, "ShellCompDirectiveDefault")
}
- if d > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
+ if d >= shellCompDirectiveMaxValue {
return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
}
return strings.Join(directives, ", ")
@@ -105,11 +131,25 @@ func (c *Command) initCompleteCmd(args []string) {
// Remove any description that may be included following a tab character.
comp = strings.Split(comp, "\t")[0]
}
+
+ // Make sure we only write the first line to the output.
+ // This is needed if a description contains a linebreak.
+ // Otherwise the shell scripts will interpret the other lines as new flags
+ // and could therefore provide a wrong completion.
+ comp = strings.Split(comp, "\n")[0]
+
+ // Finally trim the completion. This is especially important to get rid
+ // of a trailing tab when there are no description following it.
+ // For example, a sub-command without a description should not be completed
+ // with a tab at the end (or else zsh will show a -- following it
+ // although there is no description).
+ comp = strings.TrimSpace(comp)
+
// Print each possible completion to stdout for the completion script to consume.
fmt.Fprintln(finalCmd.OutOrStdout(), comp)
}
- if directive > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp {
+ if directive >= shellCompDirectiveMaxValue {
directive = ShellCompDirectiveDefault
}
@@ -136,90 +176,179 @@ func (c *Command) initCompleteCmd(args []string) {
}
func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
- var completions []string
-
// The last argument, which is not completely typed by the user,
// should not be part of the list of arguments
toComplete := args[len(args)-1]
trimmedArgs := args[:len(args)-1]
+ var finalCmd *Command
+ var finalArgs []string
+ var err error
// Find the real command for which completion must be performed
- finalCmd, finalArgs, err := c.Root().Find(trimmedArgs)
+ // check if we need to traverse here to parse local flags on parent commands
+ if c.Root().TraverseChildren {
+ finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
+ } else {
+ finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
+ }
if err != nil {
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
- return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
+ return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
+ }
+
+ // 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
+ }
+
+ // 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 {
+ // Check if we are completing a flag value subject to annotations
+ if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
+ if len(validExts) != 0 {
+ // File completion filtered by extensions
+ return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil
+ }
+
+ // The annotation requests simple file completion. There is no reason to do
+ // that since it is the default behavior anyway. Let's ignore this annotation
+ // in case the program also registered a completion function for this flag.
+ // Even though it is a mistake on the program's side, let's be nice when we can.
+ }
+
+ if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {
+ if len(subDir) == 1 {
+ // Directory completion from within a directory
+ return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
+ }
+ // Directory completion
+ return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil
+ }
}
// 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 to be complete
- if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
- // We are completing a flag name
- finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
- completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
- })
- finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
- completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
- })
-
- directive := ShellCompDirectiveDefault
- if len(completions) > 0 {
- if strings.HasSuffix(completions[0], "=") {
- directive = ShellCompDirectiveNoSpace
+ // the flag name to be complete
+ if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") {
+ var completions []string
+
+ // First check for required flags
+ completions = completeRequireFlags(finalCmd, toComplete)
+
+ // If we have not found any required flags, only then can we show regular flags
+ if len(completions) == 0 {
+ doCompleteFlags := func(flag *pflag.Flag) {
+ if !flag.Changed ||
+ strings.Contains(flag.Value.Type(), "Slice") ||
+ strings.Contains(flag.Value.Type(), "Array") {
+ // If the flag is not already present, or if it can be specified multiple times (Array or Slice)
+ // we suggest it as a completion
+ completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
+ }
}
+
+ // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
+ // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
+ // non-inherited flags.
+ finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
+ doCompleteFlags(flag)
+ })
+ finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+ doCompleteFlags(flag)
+ })
+ }
+
+ 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
}
- var flag *pflag.Flag
+ // 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 {
- // We only do flag completion if we are allowed to parse flags
- // This is important for commands which have requested to do their own flag completion.
- flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
- if err != nil {
- // Error while attempting to parse flags
- return finalCmd, completions, ShellCompDirectiveDefault, err
- }
+ finalArgs = finalCmd.Flags().Args()
}
+ var completions []string
+ directive := ShellCompDirectiveDefault
if flag == nil {
- // Complete subcommand names
- for _, subCmd := range finalCmd.Commands() {
- if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) {
- completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
+ 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))
+ }
+ 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 {
- // Always complete ValidArgs, even if we are completing a subcommand name.
- // This is for commands that have both subcommands and ValidArgs.
- for _, validArg := range finalCmd.ValidArgs {
- if strings.HasPrefix(validArg, toComplete) {
- completions = append(completions, validArg)
+ 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)
+ }
+ }
}
}
// 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, ShellCompDirectiveNoFileComp, nil
+ return finalCmd, completions, directive, nil
}
- // Always let the logic continue so as to add any ValidArgsFunction completions,
+ // 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.
}
- // Parse the flags and extract the arguments to prepare for calling the completion function
- if err = finalCmd.ParseFlags(finalArgs); err != nil {
- return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
- }
-
- // 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()
- }
-
// Find the completion function for the flag or command
var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
if flag != nil {
@@ -227,14 +356,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
} else {
completionFn = finalCmd.ValidArgsFunction
}
- if completionFn == nil {
- // Go custom completion not supported/needed for this flag or command
- return finalCmd, completions, ShellCompDirectiveDefault, nil
+ if completionFn != nil {
+ // Go custom completion defined for this flag or command.
+ // Call the registered completion function to get the completions.
+ var comps []string
+ comps, directive = completionFn(finalCmd, finalArgs, toComplete)
+ completions = append(completions, comps...)
}
- // Call the registered completion function to get the completions
- comps, directive := completionFn(finalCmd, finalArgs, toComplete)
- completions = append(completions, comps...)
return finalCmd, completions, directive, nil
}
@@ -249,11 +378,18 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
// Flag without the =
completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
- if len(flag.NoOptDefVal) == 0 {
- // Flag requires a value, so it can be suffixed with =
- flagName += "="
- completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
- }
+ // Why suggest both long forms: --flag and --flag= ?
+ // This forces the user to *always* have to type either an = or a space after the flag name.
+ // Let's be nice and avoid making users have to do that.
+ // Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
+ // The = form will still work, we just won't suggest it.
+ // This also makes the list of suggested flags shorter as we avoid all the = forms.
+ //
+ // if len(flag.NoOptDefVal) == 0 {
+ // // Flag requires a value, so it can be suffixed with =
+ // flagName += "="
+ // completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
+ // }
}
flagName = "-" + flag.Shorthand
@@ -264,17 +400,54 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
return completions
}
+func completeRequireFlags(finalCmd *Command, toComplete string) []string {
+ var completions []string
+
+ doCompleteRequiredFlags := func(flag *pflag.Flag) {
+ if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
+ if !flag.Changed {
+ // If the flag is not already present, we suggest it as a completion
+ completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
+ }
+ }
+ }
+
+ // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
+ // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
+ // non-inherited flags.
+ finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
+ doCompleteRequiredFlags(flag)
+ })
+ finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
+ doCompleteRequiredFlags(flag)
+ })
+
+ return completions
+}
+
func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
+ if finalCmd.DisableFlagParsing {
+ // We only do flag completion if we are allowed to parse flags
+ // This is important for commands which have requested to do their own flag completion.
+ return nil, args, lastArg, nil
+ }
+
var flagName string
trimmedArgs := args
flagWithEqual := false
- if isFlagArg(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
+ // requires the flag name to be complete
+ if len(lastArg) > 0 && lastArg[0] == '-' {
if index := strings.Index(lastArg, "="); index >= 0 {
+ // Flag with an =
flagName = strings.TrimLeft(lastArg[:index], "-")
lastArg = lastArg[index+1:]
flagWithEqual = true
} else {
- return nil, nil, "", errors.New("Unexpected completion request for flag")
+ // Normal flag completion
+ return nil, args, lastArg, nil
}
}