From b33f75c6ce3e2c18e7efb903cc3e86e23e3cb66c Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Fri, 7 Feb 2025 08:43:11 -0500 Subject: [PATCH] start Signed-off-by: Marc Khouzam --- command.go | 2 +- completions.go | 16 +++++--- completions_test.go | 91 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/command.go b/command.go index 31fa5f3ce..c8539135f 100644 --- a/command.go +++ b/command.go @@ -1112,7 +1112,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { c.initCompleteCmd(args) // initialize the default completion command - c.InitDefaultCompletionCmd(args) + c.InitDefaultCompletionCmd(args...) // Now that all commands have been created, let's make sure all groups // are properly created also diff --git a/completions.go b/completions.go index fdab2610a..b25fc6683 100644 --- a/completions.go +++ b/completions.go @@ -711,7 +711,7 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p // 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(args []string) { +func (c *Command) InitDefaultCompletionCmd(args ...string) { if c.CompletionOptions.DisableDefaultCmd { return } @@ -724,10 +724,16 @@ func (c *Command) InitDefaultCompletionCmd(args []string) { } haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions + // Special case to know if there are sub-commands or not. - // If there is exactly 1 sub-command, it must be the __complete command, so we are looking for the case - // where there are *more* than one sub-commands: the _complete command *and* a real sub-command. - hasSubCommands := len(c.commands) > 1 + hasSubCommands := false + for _, cmd := range c.commands { + if cmd.Name() != ShellCompRequestCmd && cmd.Name() != helpCommandName { + // We found a real sub-command (not 'help' or '__complete') + hasSubCommands = true + break + } + } completionCmd := &Command{ Use: compCmdName, @@ -743,7 +749,7 @@ See each sub-command's help for details on how to use the generated script. c.AddCommand(completionCmd) if !hasSubCommands { - // If the 'completion' command will be the only sub-command (other than '__complete'), + // If the 'completion' command will be the only sub-command, // we only create it if it is actually being called. // This avoids breaking programs that would suddenly find themselves with // a subcommand, which would prevent them from accepting arguments. diff --git a/completions_test.go b/completions_test.go index e37075606..5859573d4 100644 --- a/completions_test.go +++ b/completions_test.go @@ -3789,3 +3789,94 @@ func TestDisableDescriptions(t *testing.T) { }) } } + +// A test to make sure the InitDefaultCompletionCmd function works as expected +// in case a project calls it directly. +func TestInitDefaultCompletionCmd(t *testing.T) { + + testCases := []struct { + desc string + hasChildCmd bool + args []string + expectCompCmd bool + }{ + { + desc: "no child command and not calling the completion command", + hasChildCmd: false, + args: []string{"somearg"}, + expectCompCmd: false, + }, + { + desc: "no child command but calling the completion command", + hasChildCmd: false, + args: []string{"completion"}, + expectCompCmd: true, + }, + { + desc: "no child command but calling __complete on the root command", + hasChildCmd: false, + args: []string{"__complete", ""}, + expectCompCmd: false, + }, + { + desc: "no child command but calling __complete on the completion command", + hasChildCmd: false, + args: []string{"__complete", "completion", ""}, + expectCompCmd: true, + }, + { + desc: "with child command", + hasChildCmd: true, + args: []string{"child"}, + expectCompCmd: true, + }, + { + desc: "no child command not passing args", + hasChildCmd: false, + args: nil, + expectCompCmd: false, + }, + { + desc: "with child command not passing args", + hasChildCmd: true, + args: nil, + expectCompCmd: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + + expectedNumSubCommands := 0 + if tc.hasChildCmd { + rootCmd.AddCommand(childCmd) + expectedNumSubCommands++ + } + + if tc.expectCompCmd { + expectedNumSubCommands++ + } + + if len(tc.args) > 0 && tc.args[0] == "__complete" { + expectedNumSubCommands++ + } + + // Setup the __complete command to mimic real world scenarios + rootCmd.initCompleteCmd(tc.args) + + // Call the InitDefaultCompletionCmd function directly + if tc.args == nil { + rootCmd.InitDefaultCompletionCmd() + } else { + rootCmd.InitDefaultCompletionCmd(tc.args...) + } + + // Check if the completion command was added + if len(rootCmd.Commands()) != expectedNumSubCommands { + t.Errorf("Expected %d subcommands, got %d", expectedNumSubCommands, len(rootCmd.Commands())) + } + }) + } +}