feat: add shell completions, command grouping, and dynamic completions
Add completion command for bash/zsh/fish/powershell generation. Organize help text using Cobra command groups (Task Commands, Reports, Other). Register dynamic ValidArgsFunction on filter-accepting commands to suggest +tag and project:name completions from the database. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+38
-7
@@ -34,7 +34,7 @@ var commandNames = []string{
|
|||||||
"add", "done", "modify", "delete",
|
"add", "done", "modify", "delete",
|
||||||
"start", "stop", "count", "projects", "tags",
|
"start", "stop", "count", "projects", "tags",
|
||||||
"info", "edit", "server", "sync", "reports", "setup",
|
"info", "edit", "server", "sync", "reports", "setup",
|
||||||
"version", "annotate", "denotate", "undo", "log",
|
"version", "annotate", "denotate", "undo", "log", "completion",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report names (dynamically populated)
|
// Report names (dynamically populated)
|
||||||
@@ -222,31 +222,62 @@ func init() {
|
|||||||
initializeApp()
|
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(addCmd)
|
||||||
rootCmd.AddCommand(doneCmd)
|
rootCmd.AddCommand(doneCmd)
|
||||||
rootCmd.AddCommand(modifyCmd)
|
rootCmd.AddCommand(modifyCmd)
|
||||||
rootCmd.AddCommand(deleteCmd)
|
rootCmd.AddCommand(deleteCmd)
|
||||||
rootCmd.AddCommand(startCmd)
|
rootCmd.AddCommand(startCmd)
|
||||||
rootCmd.AddCommand(stopCmd)
|
rootCmd.AddCommand(stopCmd)
|
||||||
rootCmd.AddCommand(countCmd)
|
|
||||||
rootCmd.AddCommand(projectsCmd)
|
|
||||||
rootCmd.AddCommand(tagsCmd)
|
|
||||||
rootCmd.AddCommand(infoCmd)
|
rootCmd.AddCommand(infoCmd)
|
||||||
rootCmd.AddCommand(editCmd)
|
rootCmd.AddCommand(editCmd)
|
||||||
rootCmd.AddCommand(reportsCmd)
|
|
||||||
rootCmd.AddCommand(versionCmd)
|
|
||||||
rootCmd.AddCommand(annotateCmd)
|
rootCmd.AddCommand(annotateCmd)
|
||||||
rootCmd.AddCommand(denotateCmd)
|
rootCmd.AddCommand(denotateCmd)
|
||||||
rootCmd.AddCommand(undoCmd)
|
rootCmd.AddCommand(undoCmd)
|
||||||
rootCmd.AddCommand(logCmd)
|
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
|
// Enable --version flag on root command
|
||||||
rootCmd.Version = Version
|
rootCmd.Version = Version
|
||||||
|
|
||||||
// Add report commands dynamically
|
// Add report commands dynamically
|
||||||
reportCommands := CreateReportCommands()
|
reportCommands := CreateReportCommands()
|
||||||
for _, cmd := range reportCommands {
|
for _, cmd := range reportCommands {
|
||||||
|
cmd.GroupID = "report"
|
||||||
rootCmd.AddCommand(cmd)
|
rootCmd.AddCommand(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
serverCmd.GroupID = "other"
|
||||||
rootCmd.AddCommand(serverCmd)
|
rootCmd.AddCommand(serverCmd)
|
||||||
serverCmd.AddCommand(serverStartCmd)
|
serverCmd.AddCommand(serverStartCmd)
|
||||||
serverCmd.AddCommand(keygenCmd)
|
serverCmd.AddCommand(keygenCmd)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
setupCmd.GroupID = "other"
|
||||||
rootCmd.AddCommand(setupCmd)
|
rootCmd.AddCommand(setupCmd)
|
||||||
|
|
||||||
setupCmd.Flags().BoolVar(&showSystemdFlag, "show-systemd", false, "Show systemd service template")
|
setupCmd.Flags().BoolVar(&showSystemdFlag, "show-systemd", false, "Show systemd service template")
|
||||||
|
|||||||
@@ -391,6 +391,7 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
syncCmd.GroupID = "other"
|
||||||
rootCmd.AddCommand(syncCmd)
|
rootCmd.AddCommand(syncCmd)
|
||||||
|
|
||||||
syncCmd.AddCommand(syncInitCmd)
|
syncCmd.AddCommand(syncInitCmd)
|
||||||
|
|||||||
Reference in New Issue
Block a user