Skip to content

Commit

Permalink
Complete subcommands when TraverseChildren is true in custom completion
Browse files Browse the repository at this point in the history
The current custom completion logic does not complete
subcommands when a local flag is set. This is good unless
TraverseChildren is set to true where local flags
can be set on parent commands.

This commit allows subcommands to be completed
if TraverseChildren is set to true on the root cmd.

Closes spf13#1170

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
  • Loading branch information
Paul Holzinger committed Jul 19, 2020
1 parent 19e41cf commit f28720f
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 9 deletions.
30 changes: 21 additions & 9 deletions custom_completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,16 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
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, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
Expand Down Expand Up @@ -271,20 +279,24 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
var completions []string
directive := ShellCompDirectiveDefault
if flag == nil {
// Check if there are any local, non-persistent flags on the command-line
foundLocalNonPersistentFlag := false
localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
foundLocalNonPersistentFlag = true
}
})
// 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
// - 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) {
Expand Down
47 changes: 47 additions & 0 deletions custom_completions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ func TestNoCmdNameCompletionInGo(t *testing.T) {
Use: "root",
Run: emptyRun,
}
rootCmd.Flags().String("localroot", "", "local root flag")

childCmd1 := &Command{
Use: "childCmd1",
Short: "First command",
Expand Down Expand Up @@ -187,6 +189,51 @@ func TestNoCmdNameCompletionInGo(t *testing.T) {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names are completed if a local non-persistent flag is present and TraverseChildren is set to true
// set TraverseChildren to true on the root cmd
rootCmd.TraverseChildren = true

output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset TraverseChildren for next command
rootCmd.TraverseChildren = false

expected = strings.Join([]string{
"childCmd1",
"help",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names are completed if a local non-persistent flag is present
// and TraverseChildren is set to true on a child command
rootCmd.TraverseChildren = true
childCmd1.TraverseChildren = true

output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "--nonPersistent", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset TraverseChildren for next command
rootCmd.TraverseChildren = false
childCmd1.TraverseChildren = false
// Reset the flag for the next command
nonPersistentFlag.Changed = false

expected = strings.Join([]string{
"childCmd2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names are not completed if a local non-persistent short flag is present
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "")
if err != nil {
Expand Down

0 comments on commit f28720f

Please sign in to comment.