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 <marc.khouzam@montreal.ca>
This commit is contained in:
Marc Khouzam 2021-11-02 21:21:33 -04:00
parent f09e947637
commit 3ae090bcba
2 changed files with 204 additions and 170 deletions

204
completionCmd.go Normal file
View file

@ -0,0 +1,204 @@
package cobra
import (
"fmt"
)
const (
// Constants for the completion command
compCmdName = "completion"
compCmdNoDescFlagName = "no-descriptions"
compCmdNoDescFlagDesc = "disable completion descriptions"
compCmdNoDescFlagDefault = false
shortDesc = "Generate the autocompletion script for %s"
)
// CompletionOptions are the options to control shell completion
type CompletionOptions struct {
// DisableDefaultCmd prevents Cobra from creating a default 'completion' command
DisableDefaultCmd bool
// DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag
// for shells that support completion descriptions
DisableNoDescFlag bool
// DisableDescriptions turns off all completion descriptions for shells
// that support them
DisableDescriptions bool
}
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 %[3]s)
To load completions for every new session, execute once:
Linux:
$ %[1]s %[2]s %[3]s > /etc/bash_completion.d/%[1]s
MacOS:
$ %[1]s %[2]s %[3]s > /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(), BashCompletionCmd.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 %[3]s > "${fpath[1]}/_%[1]s"
# macOS:
$ %[1]s %[2]s %[3]s > /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(), ZshCompletionCmd.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 %[3]s | source
To load completions for every new session, execute once:
$ %[1]s %[2]s %[3]s > ~/.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(), FishCompletionCmd.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 %[3]s | 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(), PwshCompletionCmd.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)
}

View file

@ -75,26 +75,6 @@ const (
ShellCompDirectiveDefault ShellCompDirective = 0
)
const (
// Constants for the completion command
compCmdName = "completion"
compCmdNoDescFlagName = "no-descriptions"
compCmdNoDescFlagDesc = "disable completion descriptions"
compCmdNoDescFlagDefault = false
)
// CompletionOptions are the options to control shell completion
type CompletionOptions struct {
// DisableDefaultCmd prevents Cobra from creating a default 'completion' command
DisableDefaultCmd bool
// DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag
// for shells that support completion descriptions
DisableNoDescFlag bool
// DisableDescriptions turns off all completion descriptions for shells
// that support them
DisableDescriptions bool
}
// NoFileCompletions can be used to disable file completion for commands that should
// not trigger file completions.
func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
@ -578,156 +558,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 {