Skip to content

Commit

Permalink
Make the default completion accessible
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
marckhouzam committed Nov 3, 2021
1 parent f09e947 commit c29bd4d
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 150 deletions.
181 changes: 181 additions & 0 deletions 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)
}
150 changes: 0 additions & 150 deletions completions.go
Expand Up @@ -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 {
Expand Down

0 comments on commit c29bd4d

Please sign in to comment.