diff --git a/cmd/completion/completion.go b/cmd/completion/completion.go new file mode 100644 index 0000000..8b37e1c --- /dev/null +++ b/cmd/completion/completion.go @@ -0,0 +1,36 @@ +package completion + +import ( + "morpherctl/internal/completion" + + "github.com/spf13/cobra" +) + +var CompletionCmd = &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate shell completion script", + Long: `Generate shell completion script for morpherctl. + +The completion script supports bash, zsh, fish, and powershell. +To load completions in your current shell session: + +Bash: + $ source <(morpherctl completion bash) + +Zsh: + $ source <(morpherctl completion zsh) + +Fish: + $ morpherctl completion fish | source + +PowerShell: + PS> morpherctl completion powershell | Out-String | Invoke-Expression + +To load completions for every new session, write to a file and source in your shell's config file e.g. ~/.bashrc or ~/.zshrc.`, + ValidArgs: completion.GetSupportedShells(), + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + RunE: func(cmd *cobra.Command, args []string) error { + shell := args[0] + return completion.GenerateCompletion(cmd, shell) + }, +} diff --git a/cmd/root.go b/cmd/root.go index 3c7b941..9a355ba 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ import ( "github.com/spf13/cobra" + "morpherctl/cmd/completion" "morpherctl/cmd/config" "morpherctl/cmd/controller" "morpherctl/cmd/version" @@ -31,4 +32,5 @@ func init() { rootCmd.AddCommand(version.VersionCmd) rootCmd.AddCommand(config.ConfigCmd) rootCmd.AddCommand(controller.ControllerCmd) + rootCmd.AddCommand(completion.CompletionCmd) } diff --git a/internal/completion/completion.go b/internal/completion/completion.go new file mode 100644 index 0000000..8056047 --- /dev/null +++ b/internal/completion/completion.go @@ -0,0 +1,44 @@ +package completion + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +// GenerateCompletion generates shell completion script for the given shell. +func GenerateCompletion(cmd *cobra.Command, shell string) error { + switch shell { + case "bash": + if err := cmd.Root().GenBashCompletion(os.Stdout); err != nil { + return fmt.Errorf("failed to generate bash completion: %w", err) + } + return nil + case "zsh": + if err := cmd.Root().GenZshCompletion(os.Stdout); err != nil { + return fmt.Errorf("failed to generate zsh completion: %w", err) + } + return nil + case "fish": + if err := cmd.Root().GenFishCompletion(os.Stdout, true); err != nil { + return fmt.Errorf("failed to generate fish completion: %w", err) + } + return nil + case "powershell": + if err := cmd.Root().GenPowerShellCompletion(os.Stdout); err != nil { + return fmt.Errorf("failed to generate powershell completion: %w", err) + } + return nil + default: + if err := cmd.Help(); err != nil { + return fmt.Errorf("failed to display help: %w", err) + } + return nil + } +} + +// GetSupportedShells returns the list of supported shell types. +func GetSupportedShells() []string { + return []string{"bash", "zsh", "fish", "powershell"} +} diff --git a/internal/completion/completion_test.go b/internal/completion/completion_test.go new file mode 100644 index 0000000..c51f506 --- /dev/null +++ b/internal/completion/completion_test.go @@ -0,0 +1,65 @@ +package completion + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +func TestGenerateCompletion(t *testing.T) { + // Create a root command for testing. + rootCmd := &cobra.Command{Use: "test"} + rootCmd.AddCommand(&cobra.Command{Use: "subcommand"}) + + tests := []struct { + name string + shell string + expectError bool + }{ + { + name: "bash completion", + shell: "bash", + expectError: false, + }, + { + name: "zsh completion", + shell: "zsh", + expectError: false, + }, + { + name: "fish completion", + shell: "fish", + expectError: false, + }, + { + name: "powershell completion", + shell: "powershell", + expectError: false, + }, + { + name: "invalid shell", + shell: "invalid", + expectError: false, // Help() doesn't return an error. + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := GenerateCompletion(rootCmd, tt.shell) + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGetSupportedShells(t *testing.T) { + shells := GetSupportedShells() + expectedShells := []string{"bash", "zsh", "fish", "powershell"} + + assert.ElementsMatch(t, expectedShells, shells) + assert.Len(t, shells, 4) +}