This commit is contained in:
Marc Khouzam 2024-04-24 18:43:07 -04:00 committed by GitHub
commit 1808686f0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 82 additions and 13 deletions

View file

@ -1068,12 +1068,6 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
// initialize help at the last point to allow for user overriding
c.InitDefaultHelpCmd()
// initialize completion at the last point to allow for user overriding
c.InitDefaultCompletionCmd()
// Now that all commands have been created, let's make sure all groups
// are properly created also
c.checkCommandGroups()
args := c.args
@ -1082,9 +1076,16 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
args = os.Args[1:]
}
// initialize the hidden command to be used for shell completion
// initialize the __complete command to be used for shell completion
c.initCompleteCmd(args)
// initialize the default completion command
c.InitDefaultCompletionCmd(args)
// Now that all commands have been created, let's make sure all groups
// are properly created also
c.checkCommandGroups()
var flags []string
if c.TraverseChildren {
cmd, flags, err = c.Traverse(args)

View file

@ -687,8 +687,8 @@ 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() {
if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() {
func (c *Command) InitDefaultCompletionCmd(args []string) {
if c.CompletionOptions.DisableDefaultCmd {
return
}
@ -700,6 +700,10 @@ func (c *Command) InitDefaultCompletionCmd() {
}
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
completionCmd := &Command{
Use: compCmdName,
@ -714,6 +718,22 @@ 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'),
// 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.
// We also create the 'completion' command if the user is triggering
// shell completion for it (prog __complete completion '')
subCmd, cmdArgs, err := c.Find(args)
if err != nil || subCmd.Name() != compCmdName &&
!(subCmd.Name() == ShellCompRequestCmd && len(cmdArgs) > 1 && cmdArgs[0] == compCmdName) {
// The completion command is not being called or being completed so we remove it.
c.RemoveCommand(completionCmd)
return
}
}
out := c.OutOrStdout()
noDesc := c.CompletionOptions.DisableDescriptions
shortDesc := "Generate the autocompletion script for %s"

View file

@ -2432,7 +2432,7 @@ func TestDefaultCompletionCmd(t *testing.T) {
Run: emptyRun,
}
// Test that no completion command is created if there are not other sub-commands
// Test that when there are no sub-commands, the completion command is not created if it is not called directly.
assertNoErr(t, rootCmd.Execute())
for _, cmd := range rootCmd.commands {
if cmd.Name() == compCmdName {
@ -2441,6 +2441,17 @@ func TestDefaultCompletionCmd(t *testing.T) {
}
}
// Test that when there are no sub-commands, the completion command is created when it is called directly.
_, err := executeCommand(rootCmd, compCmdName)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset the arguments
rootCmd.args = nil
// Remove completion command for the next test
removeCompCmd(rootCmd)
// Add a sub-command
subCmd := &Command{
Use: "sub",
Run: emptyRun,
@ -2562,6 +2573,42 @@ func TestDefaultCompletionCmd(t *testing.T) {
func TestCompleteCompletion(t *testing.T) {
rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
// Test that when there are no sub-commands, the 'completion' command is not completed
// (because it is not created).
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Test that when there are no sub-commands, completion can be triggered for the default
// 'completion' command
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected = strings.Join([]string{
"bash",
"fish",
"powershell",
"zsh",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
// Add a sub-command
subCmd := &Command{
Use: "sub",
Run: emptyRun,
@ -2569,12 +2616,12 @@ func TestCompleteCompletion(t *testing.T) {
rootCmd.AddCommand(subCmd)
// Test sub-commands of the completion command
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
expected = strings.Join([]string{
"bash",
"fish",
"powershell",

View file

@ -8,7 +8,8 @@ The currently supported shells are:
- PowerShell
Cobra will automatically provide your program with a fully functional `completion` command,
similarly to how it provides the `help` command.
similarly to how it provides the `help` command. If there are no other subcommands, the
default `completion` command will be hidden, but still functional.
## Creating your own completion command