Updated interaction between custom CLI syntax and Cobra flags

This commit is contained in:
2026-02-19 18:31:30 +01:00
parent cd77443a07
commit acab4333a7
2 changed files with 98 additions and 27 deletions
+62 -27
View File
@@ -12,9 +12,10 @@ import (
// ParsedArgs represents preprocessed command arguments
type ParsedArgs struct {
Command string
Filters []string
Modifiers []string
Command string
Filters []string
Modifiers []string
CmdArgIndex int // position of command in os.Args[1:], -1 if not found
}
// Context key for parsed args
@@ -84,28 +85,29 @@ func Execute() error {
if len(os.Args) > 1 {
firstArg := os.Args[1]
if firstArg == "-h" || firstArg == "--help" || firstArg == "help" {
// Let Cobra handle help - skip preprocessing
return rootCmd.Execute()
}
}
// Preprocess arguments BEFORE Cobra routing
// Preprocess arguments (read-only scan — os.Args is never mutated)
if len(os.Args) > 1 {
parsed := preprocessArgs(os.Args[1:])
// Store in context for commands to use
ctx := context.WithValue(context.Background(), parsedArgsKey, parsed)
rootCmd.SetContext(ctx)
// Rewrite os.Args for Cobra based on parsed command
// This allows Cobra to route to the correct command
if parsed.Command != "list" || len(parsed.Filters) > 0 || len(parsed.Modifiers) > 0 {
// Reconstruct args: [command, ...filters, ...modifiers]
newArgs := []string{os.Args[0], parsed.Command}
newArgs = append(newArgs, parsed.Filters...)
newArgs = append(newArgs, parsed.Modifiers...)
os.Args = newArgs
// Build clean args for Cobra via SetArgs (os.Args stays untouched).
if parsed.CmdArgIndex >= 0 {
i := parsed.CmdArgIndex + 1 // offset for binary name in os.Args
cmdAndAfter := os.Args[i:] // command + subcommands + their flags
preCmdFlags := collectFlags(os.Args[1:i]) // persistent flags before command
cobraArgs := make([]string, 0, len(cmdAndAfter)+len(preCmdFlags))
cobraArgs = append(cobraArgs, cmdAndAfter...)
cobraArgs = append(cobraArgs, preCmdFlags...)
rootCmd.SetArgs(cobraArgs)
}
// CmdArgIndex == -1: no command found, don't call SetArgs.
// Cobra processes os.Args naturally → root command → default report.
}
return rootCmd.Execute()
@@ -127,9 +129,10 @@ func getParsedArgs(cmd *cobra.Command) *ParsedArgs {
func preprocessArgs(args []string) *ParsedArgs {
if len(args) == 0 {
return &ParsedArgs{
Command: "list", // Default command
Filters: []string{},
Modifiers: []string{},
Command: "list", // Default command
Filters: []string{},
Modifiers: []string{},
CmdArgIndex: -1,
}
}
@@ -167,9 +170,10 @@ func preprocessArgs(args []string) *ParsedArgs {
// If no command found, treat as filters for default list command
if cmdIdx == -1 {
return &ParsedArgs{
Command: "list",
Filters: stripFlags(args),
Modifiers: []string{},
Command: "list",
Filters: stripFlags(args),
Modifiers: []string{},
CmdArgIndex: -1,
}
}
@@ -183,16 +187,18 @@ func preprocessArgs(args []string) *ParsedArgs {
// Determine how to interpret right args
if commandsWithModifiers[cmdName] {
return &ParsedArgs{
Command: cmdName,
Filters: leftArgs,
Modifiers: rightArgs,
Command: cmdName,
Filters: leftArgs,
Modifiers: rightArgs,
CmdArgIndex: cmdIdx,
}
} else {
allFilters := append(leftArgs, rightArgs...)
return &ParsedArgs{
Command: cmdName,
Filters: allFilters,
Modifiers: []string{},
Command: cmdName,
Filters: allFilters,
Modifiers: []string{},
CmdArgIndex: cmdIdx,
}
}
}
@@ -208,6 +214,35 @@ func stripFlags(args []string) []string {
return result
}
// collectFlags extracts flag arguments (with their values) from a slice.
// Uses Cobra's persistent flag registry to determine if a flag takes a value.
func collectFlags(args []string) []string {
var flags []string
for i := 0; i < len(args); i++ {
if !strings.HasPrefix(args[i], "-") {
continue
}
flags = append(flags, args[i])
// If flag uses = syntax, value is already included
if strings.Contains(args[i], "=") {
continue
}
// Check if this flag takes a value argument
if i+1 < len(args) {
name := strings.TrimLeft(args[i], "-")
f := rootCmd.PersistentFlags().Lookup(name)
if f == nil && len(name) == 1 {
f = rootCmd.PersistentFlags().ShorthandLookup(name)
}
if f != nil && f.Value.Type() != "bool" {
i++
flags = append(flags, args[i])
}
}
}
return flags
}
func init() {
// Add persistent flags for directory overrides
rootCmd.PersistentFlags().StringVar(&configDirFlag, "config-dir", "",