mirror of
https://github.com/spf13/cobra
synced 2025-04-04 22:09:11 +00:00
Merge 84f8ee7fef
into ceb39aba25
This commit is contained in:
commit
492a99bf2d
4 changed files with 389 additions and 1 deletions
17
command.go
17
command.go
|
@ -257,6 +257,11 @@ type Command struct {
|
|||
// SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions.
|
||||
// Must be > 0.
|
||||
SuggestionsMinimumDistance int
|
||||
|
||||
// When performing commandline completions specific to handling flags only, override
|
||||
// Cobra's default behavior and allow ValidArgsFunction to be used if it exists, otherwise
|
||||
// use the default behavior
|
||||
AllowCustomFlagCompletions bool
|
||||
}
|
||||
|
||||
// Context returns underlying command context. If command was executed
|
||||
|
@ -1602,6 +1607,18 @@ func (c *Command) HasSubCommands() bool {
|
|||
return len(c.commands) > 0
|
||||
}
|
||||
|
||||
// HasCustomFlagCompletion determines if a command has a defined
|
||||
// ValidArgsFunction, if it does, then it returns the value of
|
||||
// the AllowCustomFlagCompletions field indicating if the function
|
||||
// should be used for handling custom flag completions.
|
||||
func (c *Command) HasCustomFlagCompletion() bool {
|
||||
if c.ValidArgsFunction == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.AllowCustomFlagCompletions
|
||||
}
|
||||
|
||||
// IsAvailableCommand determines if a command is available as a non-help command
|
||||
// (this includes all non deprecated/hidden commands).
|
||||
func (c *Command) IsAvailableCommand() bool {
|
||||
|
|
|
@ -2952,3 +2952,83 @@ func TestHelpFuncExecuted(t *testing.T) {
|
|||
|
||||
checkStringContains(t, output, helpText)
|
||||
}
|
||||
|
||||
func TestHasCustomFlagCompletion(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
cmd *Command
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions not set",
|
||||
cmd: &Command{
|
||||
Use: "c",
|
||||
Run: emptyRun,
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions set true",
|
||||
cmd: &Command{
|
||||
Use: "c",
|
||||
Run: emptyRun,
|
||||
AllowCustomFlagCompletions: true,
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions set false",
|
||||
cmd: &Command{
|
||||
Use: "c",
|
||||
Run: emptyRun,
|
||||
AllowCustomFlagCompletions: false,
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions not set with Valid",
|
||||
cmd: &Command{
|
||||
Use: "c",
|
||||
Run: emptyRun,
|
||||
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||
return []string{}, ShellCompDirectiveNoFileComp
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions set true",
|
||||
cmd: &Command{
|
||||
Use: "c",
|
||||
Run: emptyRun,
|
||||
AllowCustomFlagCompletions: true,
|
||||
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||
return []string{}, ShellCompDirectiveNoFileComp
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "HasCustomFlagCompletion() AllowCustomFlagCompletions set false",
|
||||
cmd: &Command{
|
||||
Use: "c",
|
||||
Run: emptyRun,
|
||||
AllowCustomFlagCompletions: false,
|
||||
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||
return []string{}, ShellCompDirectiveNoFileComp
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.cmd.HasCustomFlagCompletion() != tc.expectedResult {
|
||||
t.Errorf("did not return expected results")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -358,7 +358,7 @@ func (c *Command) getCompletions(args []string) (*Command, []Completion, ShellCo
|
|||
// 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
|
||||
flagCompletion := !finalCmd.HasCustomFlagCompletion()
|
||||
_ = finalCmd.ParseFlags(append(finalArgs, "--"))
|
||||
newArgCount := finalCmd.Flags().NArg()
|
||||
|
||||
|
|
|
@ -4016,3 +4016,294 @@ func TestInitDefaultCompletionCmd(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagNameCompletionAllowWithoutValidFunc(t *testing.T) {
|
||||
rootCmd := &Command{
|
||||
Use: "root",
|
||||
Run: emptyRun,
|
||||
AllowCustomFlagCompletions: true,
|
||||
}
|
||||
childCmd := &Command{
|
||||
Use: "childCmd",
|
||||
Short: "first command",
|
||||
Run: emptyRun,
|
||||
}
|
||||
rootCmd.AddCommand(childCmd)
|
||||
|
||||
rootCmd.Flags().IntP("first", "f", -1, "first flag")
|
||||
firstFlag := rootCmd.Flags().Lookup("first")
|
||||
|
||||
rootCmd.Flags().BoolP("second", "s", false, "second flag")
|
||||
secondFlag := rootCmd.Flags().Lookup("second")
|
||||
|
||||
rootCmd.Flags().StringArrayP("array", "a", nil, "array flag")
|
||||
arrayFlag := rootCmd.Flags().Lookup("array")
|
||||
|
||||
rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag")
|
||||
sliceFlag := rootCmd.Flags().Lookup("slice")
|
||||
|
||||
rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag")
|
||||
bsliceFlag := rootCmd.Flags().Lookup("bslice")
|
||||
|
||||
rootCmd.Flags().VarP(&customMultiString{}, "multi", "m", "multi string flag")
|
||||
multiFlag := rootCmd.Flags().Lookup("multi")
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice
|
||||
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
firstFlag.Changed = false
|
||||
|
||||
expected := strings.Join([]string{
|
||||
"--array",
|
||||
"--bslice",
|
||||
"--help",
|
||||
"--multi",
|
||||
"--second",
|
||||
"--slice",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice
|
||||
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
firstFlag.Changed = false
|
||||
secondFlag.Changed = false
|
||||
|
||||
expected = strings.Join([]string{
|
||||
"--array",
|
||||
"--bslice",
|
||||
"--help",
|
||||
"--multi",
|
||||
"--slice",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice
|
||||
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--multi", "val", "--")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
sliceFlag.Changed = false
|
||||
arrayFlag.Changed = false
|
||||
bsliceFlag.Changed = false
|
||||
multiFlag.Changed = false
|
||||
|
||||
expected = strings.Join([]string{
|
||||
"--array",
|
||||
"--bslice",
|
||||
"--first",
|
||||
"--help",
|
||||
"--multi",
|
||||
"--second",
|
||||
"--slice",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice, using shortname
|
||||
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
sliceFlag.Changed = false
|
||||
arrayFlag.Changed = false
|
||||
multiFlag.Changed = false
|
||||
|
||||
expected = strings.Join([]string{
|
||||
"--array",
|
||||
"-a",
|
||||
"--bslice",
|
||||
"-b",
|
||||
"--first",
|
||||
"-f",
|
||||
"--help",
|
||||
"-h",
|
||||
"--multi",
|
||||
"-m",
|
||||
"--second",
|
||||
"-s",
|
||||
"--slice",
|
||||
"-l",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice, using shortname with prefix
|
||||
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
sliceFlag.Changed = false
|
||||
arrayFlag.Changed = false
|
||||
multiFlag.Changed = false
|
||||
|
||||
expected = strings.Join([]string{
|
||||
"-a",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagNameCompletionAllowWithValidFunc(t *testing.T) {
|
||||
rootCmd := &Command{
|
||||
Use: "root",
|
||||
Run: emptyRun,
|
||||
AllowCustomFlagCompletions: true,
|
||||
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||
return []string{"--bogus-flag", "-z", "non-match-value"}, ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
childCmd := &Command{
|
||||
Use: "childCmd",
|
||||
Short: "first command",
|
||||
Run: emptyRun,
|
||||
}
|
||||
rootCmd.AddCommand(childCmd)
|
||||
|
||||
rootCmd.Flags().IntP("first", "f", -1, "first flag")
|
||||
firstFlag := rootCmd.Flags().Lookup("first")
|
||||
|
||||
rootCmd.Flags().BoolP("second", "s", false, "second flag")
|
||||
secondFlag := rootCmd.Flags().Lookup("second")
|
||||
|
||||
rootCmd.Flags().StringArrayP("array", "a", nil, "array flag")
|
||||
arrayFlag := rootCmd.Flags().Lookup("array")
|
||||
|
||||
rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag")
|
||||
sliceFlag := rootCmd.Flags().Lookup("slice")
|
||||
|
||||
rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag")
|
||||
bsliceFlag := rootCmd.Flags().Lookup("bslice")
|
||||
|
||||
rootCmd.Flags().VarP(&customMultiString{}, "multi", "m", "multi string flag")
|
||||
multiFlag := rootCmd.Flags().Lookup("multi")
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice
|
||||
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
firstFlag.Changed = false
|
||||
|
||||
expected := strings.Join([]string{
|
||||
"--bogus-flag",
|
||||
"-z",
|
||||
"non-match-value",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice
|
||||
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
firstFlag.Changed = false
|
||||
secondFlag.Changed = false
|
||||
|
||||
expected = strings.Join([]string{
|
||||
"--bogus-flag",
|
||||
"-z",
|
||||
"non-match-value",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice
|
||||
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--multi", "val", "--")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
sliceFlag.Changed = false
|
||||
arrayFlag.Changed = false
|
||||
bsliceFlag.Changed = false
|
||||
multiFlag.Changed = false
|
||||
|
||||
expected = strings.Join([]string{
|
||||
"--bogus-flag",
|
||||
"-z",
|
||||
"non-match-value",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice, using shortname
|
||||
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
sliceFlag.Changed = false
|
||||
arrayFlag.Changed = false
|
||||
multiFlag.Changed = false
|
||||
|
||||
expected = strings.Join([]string{
|
||||
"--bogus-flag",
|
||||
"-z",
|
||||
"non-match-value",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
|
||||
// Test that flag names are not repeated unless they are an array or slice, using shortname with prefix
|
||||
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
// Reset the flag for the next command
|
||||
sliceFlag.Changed = false
|
||||
arrayFlag.Changed = false
|
||||
multiFlag.Changed = false
|
||||
|
||||
expected = strings.Join([]string{
|
||||
"--bogus-flag",
|
||||
"-z",
|
||||
"non-match-value",
|
||||
":4",
|
||||
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("expected: %q, got: %q", expected, output)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue