diff --git a/opal-task/cmd/completion.go b/opal-task/cmd/completion.go new file mode 100644 index 0000000..7f29d16 --- /dev/null +++ b/opal-task/cmd/completion.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var completionCmd = &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate shell completions", + Long: `Generate shell completion scripts for opal. + +To load completions: + +Bash: + $ source <(opal completion bash) + # To load on startup, add to ~/.bashrc: + $ echo 'source <(opal completion bash)' >> ~/.bashrc + +Zsh: + $ source <(opal completion zsh) + # To load on startup, add to ~/.zshrc: + $ echo 'source <(opal completion zsh)' >> ~/.zshrc + +Fish: + $ opal completion fish | source + # To load on startup: + $ opal completion fish > ~/.config/fish/completions/opal.fish`, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + rootCmd.GenBashCompletion(os.Stdout) + case "zsh": + rootCmd.GenZshCompletion(os.Stdout) + case "fish": + rootCmd.GenFishCompletion(os.Stdout, true) + case "powershell": + rootCmd.GenPowerShellCompletionWithDesc(os.Stdout) + default: + fmt.Fprintf(os.Stderr, "Unknown shell: %s\n", args[0]) + os.Exit(1) + } + }, +} diff --git a/opal-task/cmd/completions.go b/opal-task/cmd/completions.go new file mode 100644 index 0000000..9d602cf --- /dev/null +++ b/opal-task/cmd/completions.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "fmt" + + "git.jnss.me/joakim/opal/internal/engine" + "github.com/spf13/cobra" +) + +// taskFilterCompletion provides dynamic completions for task filter arguments. +// Suggests +tag and project:name completions from the database. +func taskFilterCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var completions []string + + tags, err := engine.GetAllTags() + if err == nil { + for _, tag := range tags { + completions = append(completions, fmt.Sprintf("+%s", tag)) + } + } + + projects, err := engine.GetAllProjects() + if err == nil { + for _, proj := range projects { + completions = append(completions, fmt.Sprintf("project:%s", proj)) + } + } + + // Add known attribute keys + for key := range engine.ValidAttributeKeys { + completions = append(completions, fmt.Sprintf("%s:", key)) + } + + return completions, cobra.ShellCompDirectiveNoFileComp +} + +func init() { + // Register dynamic completions for commands that accept filters + addCmd.ValidArgsFunction = taskFilterCompletion + doneCmd.ValidArgsFunction = taskFilterCompletion + deleteCmd.ValidArgsFunction = taskFilterCompletion + modifyCmd.ValidArgsFunction = taskFilterCompletion + startCmd.ValidArgsFunction = taskFilterCompletion + stopCmd.ValidArgsFunction = taskFilterCompletion + editCmd.ValidArgsFunction = taskFilterCompletion + infoCmd.ValidArgsFunction = taskFilterCompletion + annotateCmd.ValidArgsFunction = taskFilterCompletion + denotateCmd.ValidArgsFunction = taskFilterCompletion + logCmd.ValidArgsFunction = taskFilterCompletion +} diff --git a/opal-task/cmd/root.go b/opal-task/cmd/root.go index e49c349..1818122 100644 --- a/opal-task/cmd/root.go +++ b/opal-task/cmd/root.go @@ -34,7 +34,7 @@ var commandNames = []string{ "add", "done", "modify", "delete", "start", "stop", "count", "projects", "tags", "info", "edit", "server", "sync", "reports", "setup", - "version", "annotate", "denotate", "undo", "log", + "version", "annotate", "denotate", "undo", "log", "completion", } // Report names (dynamically populated) @@ -222,31 +222,62 @@ func init() { initializeApp() } - // Add regular subcommands + // Command groups for organized help output + rootCmd.AddGroup( + &cobra.Group{ID: "task", Title: "Task Commands:"}, + &cobra.Group{ID: "report", Title: "Reports:"}, + &cobra.Group{ID: "other", Title: "Other:"}, + ) + + // Task commands + addCmd.GroupID = "task" + doneCmd.GroupID = "task" + modifyCmd.GroupID = "task" + deleteCmd.GroupID = "task" + startCmd.GroupID = "task" + stopCmd.GroupID = "task" + editCmd.GroupID = "task" + infoCmd.GroupID = "task" + annotateCmd.GroupID = "task" + denotateCmd.GroupID = "task" + undoCmd.GroupID = "task" + logCmd.GroupID = "task" + rootCmd.AddCommand(addCmd) rootCmd.AddCommand(doneCmd) rootCmd.AddCommand(modifyCmd) rootCmd.AddCommand(deleteCmd) rootCmd.AddCommand(startCmd) rootCmd.AddCommand(stopCmd) - rootCmd.AddCommand(countCmd) - rootCmd.AddCommand(projectsCmd) - rootCmd.AddCommand(tagsCmd) rootCmd.AddCommand(infoCmd) rootCmd.AddCommand(editCmd) - rootCmd.AddCommand(reportsCmd) - rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(annotateCmd) rootCmd.AddCommand(denotateCmd) rootCmd.AddCommand(undoCmd) rootCmd.AddCommand(logCmd) + // Other commands + countCmd.GroupID = "other" + projectsCmd.GroupID = "other" + tagsCmd.GroupID = "other" + reportsCmd.GroupID = "other" + versionCmd.GroupID = "other" + completionCmd.GroupID = "other" + + rootCmd.AddCommand(countCmd) + rootCmd.AddCommand(projectsCmd) + rootCmd.AddCommand(tagsCmd) + rootCmd.AddCommand(reportsCmd) + rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(completionCmd) + // Enable --version flag on root command rootCmd.Version = Version // Add report commands dynamically reportCommands := CreateReportCommands() for _, cmd := range reportCommands { + cmd.GroupID = "report" rootCmd.AddCommand(cmd) } } diff --git a/opal-task/cmd/server.go b/opal-task/cmd/server.go index b18fa77..a51af3c 100644 --- a/opal-task/cmd/server.go +++ b/opal-task/cmd/server.go @@ -198,6 +198,7 @@ Examples: } func init() { + serverCmd.GroupID = "other" rootCmd.AddCommand(serverCmd) serverCmd.AddCommand(serverStartCmd) serverCmd.AddCommand(keygenCmd) diff --git a/opal-task/cmd/setup.go b/opal-task/cmd/setup.go index 8b2e57e..983b60e 100644 --- a/opal-task/cmd/setup.go +++ b/opal-task/cmd/setup.go @@ -49,6 +49,7 @@ Examples: } func init() { + setupCmd.GroupID = "other" rootCmd.AddCommand(setupCmd) setupCmd.Flags().BoolVar(&showSystemdFlag, "show-systemd", false, "Show systemd service template") diff --git a/opal-task/cmd/sync.go b/opal-task/cmd/sync.go index 9b97501..2005647 100644 --- a/opal-task/cmd/sync.go +++ b/opal-task/cmd/sync.go @@ -391,6 +391,7 @@ Examples: } func init() { + syncCmd.GroupID = "other" rootCmd.AddCommand(syncCmd) syncCmd.AddCommand(syncInitCmd)