From d84d8cc838bb78b3a8e978d89e60dc8ca1d8d1ff Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 09:43:59 +0000 Subject: [PATCH] feat: add persistent CLI flag helpers --- docs/pkg/cli/commands.md | 5 +++ pkg/cli/command.go | 45 ++++++++++++++++++++++ pkg/cli/command_test.go | 81 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 pkg/cli/command_test.go diff --git a/docs/pkg/cli/commands.md b/docs/pkg/cli/commands.md index b917c8c..6486f48 100644 --- a/docs/pkg/cli/commands.md +++ b/docs/pkg/cli/commands.md @@ -85,6 +85,11 @@ Persistent flags are inherited by all subcommands: ```go cli.PersistentStringFlag(parentCmd, &dbPath, "db", "d", "", "Database path") cli.PersistentBoolFlag(parentCmd, &debug, "debug", "", false, "Debug mode") +cli.PersistentIntFlag(parentCmd, &retries, "retries", "r", 3, "Retry count") +cli.PersistentInt64Flag(parentCmd, &seed, "seed", "", 0, "Seed value") +cli.PersistentFloat64Flag(parentCmd, &ratio, "ratio", "", 1.0, "Scaling ratio") +cli.PersistentDurationFlag(parentCmd, &timeout, "timeout", "t", 30*time.Second, "Timeout") +cli.PersistentStringSliceFlag(parentCmd, &tags, "tag", "", nil, "Tags") ``` ## Args Validation diff --git a/pkg/cli/command.go b/pkg/cli/command.go index cc36d16..d172ef9 100644 --- a/pkg/cli/command.go +++ b/pkg/cli/command.go @@ -195,6 +195,51 @@ func PersistentBoolFlag(cmd *Command, ptr *bool, name, short string, def bool, u } } +// PersistentIntFlag adds a persistent integer flag (inherited by subcommands). +func PersistentIntFlag(cmd *Command, ptr *int, name, short string, def int, usage string) { + if short != "" { + cmd.PersistentFlags().IntVarP(ptr, name, short, def, usage) + } else { + cmd.PersistentFlags().IntVar(ptr, name, def, usage) + } +} + +// PersistentInt64Flag adds a persistent int64 flag (inherited by subcommands). +func PersistentInt64Flag(cmd *Command, ptr *int64, name, short string, def int64, usage string) { + if short != "" { + cmd.PersistentFlags().Int64VarP(ptr, name, short, def, usage) + } else { + cmd.PersistentFlags().Int64Var(ptr, name, def, usage) + } +} + +// PersistentFloat64Flag adds a persistent float64 flag (inherited by subcommands). +func PersistentFloat64Flag(cmd *Command, ptr *float64, name, short string, def float64, usage string) { + if short != "" { + cmd.PersistentFlags().Float64VarP(ptr, name, short, def, usage) + } else { + cmd.PersistentFlags().Float64Var(ptr, name, def, usage) + } +} + +// PersistentDurationFlag adds a persistent time.Duration flag (inherited by subcommands). +func PersistentDurationFlag(cmd *Command, ptr *time.Duration, name, short string, def time.Duration, usage string) { + if short != "" { + cmd.PersistentFlags().DurationVarP(ptr, name, short, def, usage) + } else { + cmd.PersistentFlags().DurationVar(ptr, name, def, usage) + } +} + +// PersistentStringSliceFlag adds a persistent string slice flag (inherited by subcommands). +func PersistentStringSliceFlag(cmd *Command, ptr *[]string, name, short string, def []string, usage string) { + if short != "" { + cmd.PersistentFlags().StringSliceVarP(ptr, name, short, def, usage) + } else { + cmd.PersistentFlags().StringSliceVar(ptr, name, def, usage) + } +} + // ───────────────────────────────────────────────────────────────────────────── // Command Configuration // ───────────────────────────────────────────────────────────────────────────── diff --git a/pkg/cli/command_test.go b/pkg/cli/command_test.go new file mode 100644 index 0000000..58a8402 --- /dev/null +++ b/pkg/cli/command_test.go @@ -0,0 +1,81 @@ +package cli + +import ( + "testing" + "time" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPersistentFlagHelpers_Good(t *testing.T) { + t.Run("persistent flags inherit through subcommands", func(t *testing.T) { + parent := NewGroup("parent", "Parent", "") + + var ( + str string + b bool + i int + i64 int64 + f64 float64 + dur time.Duration + slice []string + ) + + PersistentStringFlag(parent, &str, "name", "n", "default", "Name") + PersistentBoolFlag(parent, &b, "debug", "d", false, "Debug") + PersistentIntFlag(parent, &i, "count", "c", 1, "Count") + PersistentInt64Flag(parent, &i64, "seed", "", 2, "Seed") + PersistentFloat64Flag(parent, &f64, "ratio", "", 3.5, "Ratio") + PersistentDurationFlag(parent, &dur, "timeout", "t", 4*time.Second, "Timeout") + PersistentStringSliceFlag(parent, &slice, "tag", "", nil, "Tags") + + child := NewCommand("child", "Child", "", func(_ *Command, _ []string) error { + assert.Equal(t, "override", str) + assert.True(t, b) + assert.Equal(t, 9, i) + assert.Equal(t, int64(42), i64) + assert.InDelta(t, 7.25, f64, 1e-9) + assert.Equal(t, 15*time.Second, dur) + assert.Equal(t, []string{"alpha", "beta"}, slice) + return nil + }) + parent.AddCommand(child) + + parent.SetArgs([]string{ + "child", + "--name", "override", + "--debug", + "--count", "9", + "--seed", "42", + "--ratio", "7.25", + "--timeout", "15s", + "--tag", "alpha", + "--tag", "beta", + }) + + require.NoError(t, parent.Execute()) + }) + + t.Run("persistent helpers use short flags when provided", func(t *testing.T) { + parent := NewGroup("parent", "Parent", "") + var value int + PersistentIntFlag(parent, &value, "count", "c", 1, "Count") + + var seen bool + child := &cobra.Command{ + Use: "child", + RunE: func(_ *cobra.Command, _ []string) error { + seen = true + assert.Equal(t, 5, value) + return nil + }, + } + parent.AddCommand(child) + parent.SetArgs([]string{"child", "-c", "5"}) + + require.NoError(t, parent.Execute()) + assert.True(t, seen) + }) +}