From c29bd4d548cadb935d6fdfd00a60e86d0f8faf12 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Tue, 2 Nov 2021 21:21:33 -0400 Subject: [PATCH] Make the default completion accessible Fixes #1507 This allows a program to set whatever field it wants on the default completion command before calling rootCmd.Execute(). For example, cobra.CompletionCmd.Hidden = true // To make the command hidden or/and cobra.CompletionCmd.Use = "shellcomplete" // To rename the command Signed-off-by: Marc Khouzam --- completionCmd.go | 181 +++++++++++++++++++++++++++++++++++++++++++++++ completions.go | 150 --------------------------------------- 2 files changed, 181 insertions(+), 150 deletions(-) create mode 100644 completionCmd.go diff --git a/completionCmd.go b/completionCmd.go new file mode 100644 index 000000000..a80342795 --- /dev/null +++ b/completionCmd.go @@ -0,0 +1,181 @@ +package cobra + +import ( + "fmt" +) + +const shortDesc = "Generate the autocompletion script for %s" + +var ( + CompletionCmd = &Command{ + Use: compCmdName, + Short: "Generate the autocompletion script for the specified shell", + Args: NoArgs, + ValidArgsFunction: NoFileCompletions, + } + BashCompletionCmd = &Command{ + Use: "bash", + Short: fmt.Sprintf(shortDesc, "bash"), + Args: NoArgs, + DisableFlagsInUseLine: true, + ValidArgsFunction: NoFileCompletions, + } + ZshCompletionCmd = &Command{ + Use: "zsh", + Short: fmt.Sprintf(shortDesc, "zsh"), + Args: NoArgs, + DisableFlagsInUseLine: true, + ValidArgsFunction: NoFileCompletions, + } + FishCompletionCmd = &Command{ + Use: "fish", + Short: fmt.Sprintf(shortDesc, "fish"), + Args: NoArgs, + DisableFlagsInUseLine: true, + ValidArgsFunction: NoFileCompletions, + } + PwshCompletionCmd = &Command{ + Use: "powershell", + Short: fmt.Sprintf(shortDesc, "powershell"), + Args: NoArgs, + DisableFlagsInUseLine: true, + ValidArgsFunction: NoFileCompletions, + } +) + +// initDefaultCompletionCmd adds a default 'completion' command to c. +// This function will do nothing if any of the following is true: +// 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() { + return + } + + for _, cmd := range c.commands { + if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) { + // A completion command is already available + return + } + } + + if CompletionCmd.Long == "" { + CompletionCmd.Long = fmt.Sprintf( + `Generate the autocompletion script for %[1]s for the specified shell. +See each sub-command's help for details on how to use the generated script.`, c.Root().Name()) + } + + c.RemoveCommand(CompletionCmd) // Tests can call this function multiple times in a row, so we must reset + c.AddCommand(CompletionCmd) + + out := c.OutOrStdout() + noDesc := c.CompletionOptions.DisableDescriptions + haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions + + bash := BashCompletionCmd + if bash.Long == "" { + bash.Long = fmt.Sprintf( + `Generate the autocompletion script for the bash shell. + +This script depends on the 'bash-completion' package. +If it is not installed already, you can install it via your OS's package manager. + +To load completions in your current shell session: +$ source <(%[1]s %[2]s bash) + +To load completions for every new session, execute once: +Linux: +$ %[1]s %[2]s bash > /etc/bash_completion.d/%[1]s +MacOS: +$ %[1]s %[2]s bash > /usr/local/etc/bash_completion.d/%[1]s + +You will need to start a new shell for this setup to take effect.`, c.Root().Name(), CompletionCmd.Name()) + } + bash.RunE = func(cmd *Command, args []string) error { + return cmd.Root().GenBashCompletionV2(out, !noDesc) + } + + bash.ResetFlags() // Tests can call this function multiple times in a row, so we must reset + if haveNoDescFlag { + bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) + } + + zsh := ZshCompletionCmd + if zsh.Long == "" { + zsh.Long = fmt.Sprintf( + `Generate the autocompletion script for the zsh shell. + +If shell completion is not already enabled in your environment you will need +to enable it. You can execute the following once: + +$ echo "autoload -U compinit; compinit" >> ~/.zshrc + +To load completions for every new session, execute once: +# Linux: +$ %[1]s %[2]s zsh > "${fpath[1]}/_%[1]s" +# macOS: +$ %[1]s %[2]s zsh > /usr/local/share/zsh/site-functions/_%[1]s + +You will need to start a new shell for this setup to take effect.`, c.Root().Name(), CompletionCmd.Name()) + } + zsh.RunE = func(cmd *Command, args []string) error { + if noDesc { + return cmd.Root().GenZshCompletionNoDesc(out) + } + return cmd.Root().GenZshCompletion(out) + } + + zsh.ResetFlags() // Tests can call this function multiple times in a row, so we must reset + if haveNoDescFlag { + zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) + } + + fish := FishCompletionCmd + if fish.Long == "" { + fish.Long = fmt.Sprintf( + `Generate the autocompletion script for the fish shell. + +To load completions in your current shell session: +$ %[1]s %[2]s fish | source + +To load completions for every new session, execute once: +$ %[1]s %[2]s fish > ~/.config/fish/completions/%[1]s.fish + +You will need to start a new shell for this setup to take effect.`, c.Root().Name(), CompletionCmd.Name()) + } + fish.RunE = func(cmd *Command, args []string) error { + return cmd.Root().GenFishCompletion(out, !noDesc) + } + + fish.ResetFlags() // Tests can call this function multiple times in a row, so we must reset + if haveNoDescFlag { + fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) + } + + pwsh := PwshCompletionCmd + if pwsh.Long == "" { + pwsh.Long = fmt.Sprintf( + `Generate the autocompletion script for powershell. + +To load completions in your current shell session: +PS C:\> %[1]s %[2]s powershell | Out-String | Invoke-Expression + +To load completions for every new session, add the output of the above command +to your powershell profile.`, c.Root().Name(), CompletionCmd.Name()) + } + pwsh.RunE = func(cmd *Command, args []string) error { + if noDesc { + return cmd.Root().GenPowerShellCompletion(out) + } + return cmd.Root().GenPowerShellCompletionWithDesc(out) + } + + pwsh.ResetFlags() // Tests can call this function multiple times in a row, so we must reset + if haveNoDescFlag { + pwsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) + } + + CompletionCmd.RemoveCommand(bash, zsh, fish, pwsh) // Tests can call this function multiple times in a row, so we must reset + CompletionCmd.AddCommand(bash, zsh, fish, pwsh) +} diff --git a/completions.go b/completions.go index 7aeccb389..da695c848 100644 --- a/completions.go +++ b/completions.go @@ -578,156 +578,6 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p return flag, trimmedArgs, lastArg, nil } -// initDefaultCompletionCmd adds a default 'completion' command to c. -// This function will do nothing if any of the following is true: -// 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() { - return - } - - for _, cmd := range c.commands { - if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) { - // A completion command is already available - return - } - } - - haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions - - completionCmd := &Command{ - Use: compCmdName, - Short: "generate the autocompletion script for the specified shell", - Long: fmt.Sprintf(` -Generate the autocompletion script for %[1]s for the specified shell. -See each sub-command's help for details on how to use the generated script. -`, c.Root().Name()), - Args: NoArgs, - ValidArgsFunction: NoFileCompletions, - } - c.AddCommand(completionCmd) - - out := c.OutOrStdout() - noDesc := c.CompletionOptions.DisableDescriptions - shortDesc := "generate the autocompletion script for %s" - bash := &Command{ - Use: "bash", - Short: fmt.Sprintf(shortDesc, "bash"), - Long: fmt.Sprintf(` -Generate the autocompletion script for the bash shell. - -This script depends on the 'bash-completion' package. -If it is not installed already, you can install it via your OS's package manager. - -To load completions in your current shell session: -$ source <(%[1]s completion bash) - -To load completions for every new session, execute once: -Linux: - $ %[1]s completion bash > /etc/bash_completion.d/%[1]s -MacOS: - $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s - -You will need to start a new shell for this setup to take effect. - `, c.Root().Name()), - Args: NoArgs, - DisableFlagsInUseLine: true, - ValidArgsFunction: NoFileCompletions, - RunE: func(cmd *Command, args []string) error { - return cmd.Root().GenBashCompletionV2(out, !noDesc) - }, - } - if haveNoDescFlag { - bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) - } - - zsh := &Command{ - Use: "zsh", - Short: fmt.Sprintf(shortDesc, "zsh"), - Long: fmt.Sprintf(` -Generate the autocompletion script for the zsh shell. - -If shell completion is not already enabled in your environment you will need -to enable it. You can execute the following once: - -$ echo "autoload -U compinit; compinit" >> ~/.zshrc - -To load completions for every new session, execute once: -# Linux: -$ %[1]s completion zsh > "${fpath[1]}/_%[1]s" -# macOS: -$ %[1]s completion zsh > /usr/local/share/zsh/site-functions/_%[1]s - -You will need to start a new shell for this setup to take effect. -`, c.Root().Name()), - Args: NoArgs, - ValidArgsFunction: NoFileCompletions, - RunE: func(cmd *Command, args []string) error { - if noDesc { - return cmd.Root().GenZshCompletionNoDesc(out) - } - return cmd.Root().GenZshCompletion(out) - }, - } - if haveNoDescFlag { - zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) - } - - fish := &Command{ - Use: "fish", - Short: fmt.Sprintf(shortDesc, "fish"), - Long: fmt.Sprintf(` -Generate the autocompletion script for the fish shell. - -To load completions in your current shell session: -$ %[1]s completion fish | source - -To load completions for every new session, execute once: -$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish - -You will need to start a new shell for this setup to take effect. -`, c.Root().Name()), - Args: NoArgs, - ValidArgsFunction: NoFileCompletions, - RunE: func(cmd *Command, args []string) error { - return cmd.Root().GenFishCompletion(out, !noDesc) - }, - } - if haveNoDescFlag { - fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) - } - - powershell := &Command{ - Use: "powershell", - Short: fmt.Sprintf(shortDesc, "powershell"), - Long: fmt.Sprintf(` -Generate the autocompletion script for powershell. - -To load completions in your current shell session: -PS C:\> %[1]s completion powershell | Out-String | Invoke-Expression - -To load completions for every new session, add the output of the above command -to your powershell profile. -`, c.Root().Name()), - Args: NoArgs, - ValidArgsFunction: NoFileCompletions, - RunE: func(cmd *Command, args []string) error { - if noDesc { - return cmd.Root().GenPowerShellCompletion(out) - } - return cmd.Root().GenPowerShellCompletionWithDesc(out) - - }, - } - if haveNoDescFlag { - powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) - } - - completionCmd.AddCommand(bash, zsh, fish, powershell) -} - func findFlag(cmd *Command, name string) *pflag.Flag { flagSet := cmd.Flags() if len(name) == 1 {