diff --git a/cmd/completion.go b/cmd/completion.go new file mode 100644 index 0000000..8f850c1 --- /dev/null +++ b/cmd/completion.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +const shellPowerShell = "powershell" + +func newCompletionCmd() *cobra.Command { + return &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate shell completion scripts", + Args: cobra.ExactArgs(1), + ValidArgs: []string{"bash", "zsh", "fish", shellPowerShell}, + RunE: func(cmd *cobra.Command, args []string) error { + out := cmd.OutOrStdout() + root := cmd.Root() + + switch shell := strings.ToLower(args[0]); shell { + case "bash": + return root.GenBashCompletion(out) + case "zsh": + return root.GenZshCompletion(out) + case "fish": + return root.GenFishCompletion(out, true) + case shellPowerShell: + return root.GenPowerShellCompletion(out) + default: + return fmt.Errorf("unsupported shell %q (supported: bash, zsh, fish, %s)", args[0], shellPowerShell) + } + }, + } +} diff --git a/cmd/completion_test.go b/cmd/completion_test.go new file mode 100644 index 0000000..e01613b --- /dev/null +++ b/cmd/completion_test.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "bytes" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCompletionCmd_GeneratesPowerShell(t *testing.T) { + root := &cobra.Command{Use: "app"} + root.AddCommand(newCompletionCmd()) + + var out bytes.Buffer + root.SetOut(&out) + root.SetErr(&out) + root.SetArgs([]string{"completion", "powershell"}) + + err := root.Execute() + + require.NoError(t, err) + assert.Contains(t, out.String(), "Register-ArgumentCompleter") +} + +func TestCompletionCmd_RejectsUnsupportedShell(t *testing.T) { + root := &cobra.Command{Use: "app"} + root.AddCommand(newCompletionCmd()) + + root.SetArgs([]string{"completion", "xonsh"}) + + err := root.Execute() + + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported shell") +} + +func TestRootRegistersCompletionCommand(t *testing.T) { + root, cleanup := newRootCommand() + defer cleanup() + + completionCmd, _, err := root.Find([]string{"completion"}) + + require.NoError(t, err) + require.NotNil(t, completionCmd) + assert.Equal(t, "completion", completionCmd.Name()) +} diff --git a/cmd/root.go b/cmd/root.go index 9040d58..481b0da 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -357,6 +357,7 @@ Environment: rootCmd.AddCommand(newRunCmd()) rootCmd.AddCommand(newReportCmd()) rootCmd.AddCommand(newConfigCmd()) + rootCmd.AddCommand(newCompletionCmd()) // cleanup releases profiling resources. It is idempotent — safe to call // multiple times (subsequent calls are no-ops). diff --git a/docs/completions.md b/docs/completions.md new file mode 100644 index 0000000..32f4d24 --- /dev/null +++ b/docs/completions.md @@ -0,0 +1,12 @@ +# Shell completions + +Generate a completion script for your shell: + +```bash +grut completion bash +grut completion zsh +grut completion fish +grut completion powershell +``` + +Write the generated script to the location your shell expects, then reload the shell.