diff --git a/command.go b/command.go index ceefc348b4..96c0bfdc52 100644 --- a/command.go +++ b/command.go @@ -541,6 +541,12 @@ func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) { return nil } + for _, flag := range cmd.Flags { + if err := flag.PostParse(); err != nil { + return err + } + } + if cmd.After != nil && !cmd.Root().shellCompletion { defer func() { if err := cmd.After(ctx, cmd); err != nil { diff --git a/command_test.go b/command_test.go index 1c87bce66c..7544bd09b5 100644 --- a/command_test.go +++ b/command_test.go @@ -2343,6 +2343,10 @@ func (c *customBoolFlag) GetUsage() string { return "usage" } +func (c *customBoolFlag) PostParse() error { + return nil +} + func (c *customBoolFlag) Apply(set *flag.FlagSet) error { set.String(c.Nombre, c.Nombre, "") return nil diff --git a/flag.go b/flag.go index 22a3315de9..a3514ef3a9 100644 --- a/flag.go +++ b/flag.go @@ -104,6 +104,8 @@ type ActionableFlag interface { type Flag interface { fmt.Stringer + PostParse() error + // Apply Flag settings to the given flag set Apply(*flag.FlagSet) error diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go index ecc090b478..90c90d2b0c 100644 --- a/flag_bool_with_inverse.go +++ b/flag_bool_with_inverse.go @@ -135,6 +135,20 @@ func (parent *BoolWithInverseFlag) inverseAliases() (aliases []string) { return } +func (parent *BoolWithInverseFlag) PostParse() error { + if parent.positiveFlag != nil { + if err := parent.positiveFlag.PostParse(); err != nil { + return err + } + } + if parent.negativeFlag != nil { + if err := parent.negativeFlag.PostParse(); err != nil { + return err + } + } + return nil +} + func (parent *BoolWithInverseFlag) Apply(set *flag.FlagSet) error { if parent.positiveFlag == nil { parent.initialize() diff --git a/flag_ext.go b/flag_ext.go index 64da59ea93..318f2c11b1 100644 --- a/flag_ext.go +++ b/flag_ext.go @@ -6,6 +6,10 @@ type extFlag struct { f *flag.Flag } +func (e *extFlag) PostParse() error { + return nil +} + func (e *extFlag) Apply(fs *flag.FlagSet) error { fs.Var(e.f.Value, e.f.Name, e.f.Usage) return nil diff --git a/flag_impl.go b/flag_impl.go index 152dc1fef8..838f828c3b 100644 --- a/flag_impl.go +++ b/flag_impl.go @@ -135,35 +135,42 @@ func (f *FlagBase[T, C, V]) TypeName() string { } } -// Apply populates the flag given the flag set and environment -func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error { - tracef("apply (flag=%[1]q)", f.Name) - - // TODO move this phase into a separate flag initialization function - // if flag has been applied previously then it would have already been set - // from env or file. So no need to apply the env set again. However - // lots of units tests prior to persistent flags assumed that the - // flag can be applied to different flag sets multiple times while still - // keeping the env set. - if !f.applied || f.Local { - newVal := f.Value +// PostParse populates the flag given the flag set and environment +func (f *FlagBase[T, C, V]) PostParse() error { + tracef("postparse (flag=%[1]q)", f.Name) + if !f.hasBeenSet { if val, source, found := f.Sources.LookupWithSource(); found { - tmpVal := f.creator.Create(f.Value, new(T), f.Config) if val != "" || reflect.TypeOf(f.Value).Kind() == reflect.String { - if err := tmpVal.Set(val); err != nil { + if err := f.value.Set(val); err != nil { return fmt.Errorf( "could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s", val, f.Value, source, f.Name, err, ) } } else if val == "" && reflect.TypeOf(f.Value).Kind() == reflect.Bool { - _ = tmpVal.Set("false") + _ = f.value.Set("false") } - newVal = tmpVal.Get().(T) f.hasBeenSet = true } + } + + return nil +} + +// Apply populates the flag given the flag set and environment +func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error { + tracef("apply (flag=%[1]q)", f.Name) + + // TODO move this phase into a separate flag initialization function + // if flag has been applied previously then it would have already been set + // from env or file. So no need to apply the env set again. However + // lots of units tests prior to persistent flags assumed that the + // flag can be applied to different flag sets multiple times while still + // keeping the env set. + if !f.applied || f.Local { + newVal := f.Value if f.Destination == nil { f.value = f.creator.Create(newVal, new(T), f.Config) diff --git a/flag_test.go b/flag_test.go index 7246c209c0..023becdd45 100644 --- a/flag_test.go +++ b/flag_test.go @@ -772,6 +772,8 @@ func TestStringSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { _ = fl.Apply(set) err := set.Parse(nil) + + _ = fl.PostParse() assert.NoError(t, err) assert.Equal(t, []string{"vincent van goat", "scape goat"}, set.Lookup("goat").Value.(flag.Getter).Get()) } @@ -785,6 +787,7 @@ func TestStringSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { set := flag.NewFlagSet("test", 0) _ = fl.Apply(set) err := set.Parse(nil) + _ = fl.PostParse() assert.NoError(t, err) assert.Equal(t, []string{"vincent van goat", "scape goat"}, set.Lookup("goat").Value.(flag.Getter).Get()) } @@ -798,6 +801,8 @@ func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) { _ = fl.Apply(set) err := set.Parse([]string{}) + + _ = fl.PostParse() assert.NoError(t, err) assert.Equal(t, defValue, dest) } @@ -1056,6 +1061,7 @@ func TestIntSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { r := require.New(t) r.NoError(fl.Apply(set)) r.NoError(set.Parse(nil)) + r.NoError(fl.PostParse()) r.Equal([]int64{1, 2}, set.Lookup("goat").Value.(flag.Getter).Get()) } @@ -1068,6 +1074,7 @@ func TestIntSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { r := require.New(t) r.NoError(fl.Apply(set)) r.NoError(set.Parse(nil)) + r.NoError(fl.PostParse()) r.Equal([]int64{3, 4}, val) r.Equal([]int64{1, 2}, set.Lookup("goat").Value.(flag.Getter).Get()) } @@ -1081,6 +1088,7 @@ func TestIntSliceFlagApply_DefaultValueWithDestination(t *testing.T) { _ = fl.Apply(set) err := set.Parse([]string{}) + assert.NoError(t, fl.PostParse()) assert.NoError(t, err) assert.Equal(t, defValue, dest) } @@ -1184,6 +1192,7 @@ func TestUintSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { r.NoError(fl.Apply(set)) r.NoError(set.Parse(nil)) + r.NoError(fl.PostParse()) r.Equal([]uint64{1, 2}, set.Lookup("goat").Value.(flag.Getter).Get().([]uint64)) } @@ -1195,6 +1204,7 @@ func TestUintSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { r := require.New(t) r.NoError(fl.Apply(set)) r.NoError(set.Parse(nil)) + r.NoError(fl.PostParse()) r.Equal([]uint64{3, 4}, val.Value()) r.Equal([]uint64{1, 2}, set.Lookup("goat").Value.(flag.Getter).Get().([]uint64)) } @@ -1209,6 +1219,7 @@ func TestUintSliceFlagApply_DefaultValueWithDestination(t *testing.T) { err := set.Parse([]string{}) assert.NoError(t, err) + assert.NoError(t, fl.PostParse()) assert.Equal(t, defValue, dest) } @@ -1328,6 +1339,7 @@ func TestUint64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { err := set.Parse(nil) assert.NoError(t, err) + assert.NoError(t, fl.PostParse()) assert.Equal(t, []uint64{1, 2}, set.Lookup("goat").Value.(flag.Getter).Get().([]uint64)) } @@ -1341,6 +1353,7 @@ func TestUint64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { _ = fl.Apply(set) err := set.Parse(nil) assert.NoError(t, err) + assert.NoError(t, fl.PostParse()) assert.Equal(t, []uint64{1, 2}, set.Lookup("goat").Value.(flag.Getter).Get().([]uint64)) } @@ -1354,6 +1367,7 @@ func TestUint64SliceFlagApply_DefaultValueWithDestination(t *testing.T) { err := set.Parse([]string{}) assert.NoError(t, err) + assert.NoError(t, fl.PostParse()) assert.Equal(t, defValue, dest) } @@ -1519,6 +1533,7 @@ func TestFloat64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { err := set.Parse(nil) assert.NoError(t, err) + assert.NoError(t, fl.PostParse()) assert.Equal(t, []float64{1, 2}, set.Lookup("goat").Value.(flag.Getter).Get().([]float64)) } @@ -1532,6 +1547,7 @@ func TestFloat64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { _ = fl.Apply(set) err := set.Parse(nil) assert.NoError(t, err) + assert.NoError(t, fl.PostParse()) assert.Equal(t, []float64{1, 2}, set.Lookup("goat").Value.(flag.Getter).Get().([]float64)) } @@ -3056,7 +3072,9 @@ func TestStringMapFlagApply_UsesEnvValues_noDefault(t *testing.T) { _ = fl.Apply(set) err := set.Parse(nil) + assert.NoError(t, err) + assert.NoError(t, fl.PostParse()) assert.Nil(t, val) assert.Equal(t, map[string]string{"vincent van goat": "scape goat"}, set.Lookup("goat").Value.(flag.Getter).Get()) } @@ -3071,6 +3089,7 @@ func TestStringMapFlagApply_UsesEnvValues_withDefault(t *testing.T) { _ = fl.Apply(set) err := set.Parse(nil) assert.NoError(t, err) + assert.NoError(t, fl.PostParse()) assert.Equal(t, map[string]string{`some default`: `values here`}, val) assert.Equal(t, map[string]string{"vincent van goat": "scape goat"}, set.Lookup("goat").Value.(flag.Getter).Get()) } diff --git a/godoc-current.txt b/godoc-current.txt index 46b227e1e3..999b136e77 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -285,6 +285,8 @@ func (parent *BoolWithInverseFlag) IsSet() bool func (parent *BoolWithInverseFlag) Names() []string +func (parent *BoolWithInverseFlag) PostParse() error + func (parent *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error func (parent *BoolWithInverseFlag) String() string @@ -627,6 +629,8 @@ type ExitErrHandlerFunc func(context.Context, *Command, error) type Flag interface { fmt.Stringer + PostParse() error + // Apply Flag settings to the given flag set Apply(*flag.FlagSet) error @@ -736,6 +740,9 @@ func (f *FlagBase[T, C, V]) IsVisible() bool func (f *FlagBase[T, C, V]) Names() []string Names returns the names of the flag +func (f *FlagBase[T, C, V]) PostParse() error + PostParse populates the flag given the flag set and environment + func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error RunAction executes flag action if set diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index 46b227e1e3..999b136e77 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -285,6 +285,8 @@ func (parent *BoolWithInverseFlag) IsSet() bool func (parent *BoolWithInverseFlag) Names() []string +func (parent *BoolWithInverseFlag) PostParse() error + func (parent *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error func (parent *BoolWithInverseFlag) String() string @@ -627,6 +629,8 @@ type ExitErrHandlerFunc func(context.Context, *Command, error) type Flag interface { fmt.Stringer + PostParse() error + // Apply Flag settings to the given flag set Apply(*flag.FlagSet) error @@ -736,6 +740,9 @@ func (f *FlagBase[T, C, V]) IsVisible() bool func (f *FlagBase[T, C, V]) Names() []string Names returns the names of the flag +func (f *FlagBase[T, C, V]) PostParse() error + PostParse populates the flag given the flag set and environment + func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error RunAction executes flag action if set