diff --git a/app.go b/app.go index 5c616e6f51..a88dc49d31 100644 --- a/app.go +++ b/app.go @@ -305,6 +305,10 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } } + if err = runFlagActions(context, a.Flags); err != nil { + return err + } + args := context.Args() if args.Present() { name := args.First() @@ -426,6 +430,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } + if err = runFlagActions(context, a.Flags); err != nil { + return err + } + args := context.Args() if args.Present() { name := args.First() @@ -506,6 +514,24 @@ func (a *App) handleExitCoder(context *Context, err error) { } } +func runFlagActions(c *Context, fs []Flag) error { + for _, f := range fs { + isSet := false + for _, name := range f.Names() { + if c.IsSet(name) { + isSet = true + break + } + } + if isSet { + if err := f.RunAction(c); err != nil { + return err + } + } + } + return nil +} + // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name diff --git a/app_test.go b/app_test.go index 76e211d681..0872db7d58 100644 --- a/app_test.go +++ b/app_test.go @@ -2034,6 +2034,10 @@ func (c *customBoolFlag) Apply(set *flag.FlagSet) error { return nil } +func (c *customBoolFlag) RunAction(*Context) error { + return nil +} + func (c *customBoolFlag) IsSet() bool { return false } @@ -2253,3 +2257,66 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { t.Errorf("expected a.Writer to be os.Stdout") } } + +func TestFlagAction(t *testing.T) { + r := []string{} + actionFunc := func(c *Context, s string) error { + r = append(r, s) + return nil + } + + app := &App{ + Name: "command", + Writer: io.Discard, + Flags: []Flag{&StringFlag{Name: "flag", Action: actionFunc}}, + Commands: []*Command{ + { + Name: "command1", + Flags: []Flag{&StringFlag{Name: "flag1", Aliases: []string{"f1"}, Action: actionFunc}}, + Subcommands: []*Command{ + { + Name: "command2", + Flags: []Flag{&StringFlag{Name: "flag2", Action: actionFunc}}, + }, + }, + }, + }, + } + + tests := []struct { + args []string + exp []string + }{ + { + args: []string{"command", "--flag=f"}, + exp: []string{"f"}, + }, + { + args: []string{"command", "command1", "-f1=f1", "command2"}, + exp: []string{"f1"}, + }, + { + args: []string{"command", "command1", "-f1=f1", "command2", "--flag2=f2"}, + exp: []string{"f1", "f2"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-flag1=f1"}, + exp: []string{"f", "f1"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-f1=f1"}, + exp: []string{"f", "f1"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-f1=f1", "command2", "--flag2=f2"}, + exp: []string{"f", "f1", "f2"}, + }, + } + + for _, test := range tests { + r = []string{} + err := app.Run(test.args) + expect(t, err, nil) + expect(t, r, test.exp) + } +} diff --git a/command.go b/command.go index 3477686bb3..a5efbc749d 100644 --- a/command.go +++ b/command.go @@ -155,6 +155,10 @@ func (c *Command) Run(ctx *Context) (err error) { } } + if err = runFlagActions(context, c.Flags); err != nil { + return err + } + if c.Action == nil { c.Action = helpSubcommand.Action } diff --git a/flag.go b/flag.go index 49682f2994..a7ad939dc6 100644 --- a/flag.go +++ b/flag.go @@ -94,6 +94,7 @@ type Flag interface { Apply(*flag.FlagSet) error Names() []string IsSet() bool + RunAction(*Context) error } // RequiredFlag is an interface that allows us to mark flags as required diff --git a/flag_bool.go b/flag_bool.go index 8bd582094f..17c69befec 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -19,6 +19,7 @@ type BoolFlag struct { DefaultText string Destination *bool HasBeenSet bool + Action func(*Context, bool) error } // IsSet returns whether or not the flag has been set through env or file @@ -63,6 +64,15 @@ func (f *BoolFlag) IsVisible() bool { return !f.Hidden } +// RunAction executes flag action if set +func (f *BoolFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Bool(f.Name)) + } + + return nil +} + // Apply populates the flag given the flag set and environment func (f *BoolFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { diff --git a/flag_duration.go b/flag_duration.go index 28f3978051..addcd23e14 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -19,6 +19,7 @@ type DurationFlag struct { DefaultText string Destination *time.Duration HasBeenSet bool + Action func(*Context, time.Duration) error } // IsSet returns whether or not the flag has been set through env or file @@ -88,6 +89,15 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *DurationFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Duration(f.Name)) + } + + return nil +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { diff --git a/flag_float64.go b/flag_float64.go index 10fb8dfc1c..4398dd2ab2 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -19,6 +19,7 @@ type Float64Flag struct { DefaultText string Destination *float64 HasBeenSet bool + Action func(*Context, float64) error } // IsSet returns whether or not the flag has been set through env or file @@ -89,6 +90,15 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *Float64Flag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Float64(f.Name)) + } + + return nil +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 385732e17c..593f7f6ca5 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -85,6 +85,7 @@ type Float64SliceFlag struct { Value *Float64Slice DefaultText string HasBeenSet bool + Action func(*Context, []float64) error } // IsSet returns whether or not the flag has been set through env or file @@ -162,6 +163,15 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *Float64SliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Float64Slice(f.Name)) + } + + return nil +} + // Float64Slice looks up the value of a local Float64SliceFlag, returns // nil if not found func (c *Context) Float64Slice(name string) []float64 { diff --git a/flag_generic.go b/flag_generic.go index fdf586d127..a1040f3efe 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -24,6 +24,7 @@ type GenericFlag struct { Value Generic DefaultText string HasBeenSet bool + Action func(*Context, interface{}) error } // IsSet returns whether or not the flag has been set through env or file @@ -91,6 +92,15 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *GenericFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Generic(f.Name)) + } + + return nil +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { diff --git a/flag_int.go b/flag_int.go index 1ebe3569c1..8d963cc2bb 100644 --- a/flag_int.go +++ b/flag_int.go @@ -19,6 +19,7 @@ type IntFlag struct { DefaultText string Destination *int HasBeenSet bool + Action func(*Context, int) error } // IsSet returns whether or not the flag has been set through env or file @@ -89,6 +90,15 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *IntFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Int(f.Name)) + } + + return nil +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { diff --git a/flag_int64.go b/flag_int64.go index ecf0e9ef6f..044e511547 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -19,6 +19,7 @@ type Int64Flag struct { DefaultText string Destination *int64 HasBeenSet bool + Action func(*Context, int64) error } // IsSet returns whether or not the flag has been set through env or file @@ -88,6 +89,15 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *Int64Flag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Int64(f.Name)) + } + + return nil +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index f5c6939639..2338dff188 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -86,6 +86,7 @@ type Int64SliceFlag struct { Value *Int64Slice DefaultText string HasBeenSet bool + Action func(*Context, []int64) error } // IsSet returns whether or not the flag has been set through env or file @@ -161,6 +162,15 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *Int64SliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Int64Slice(f.Name)) + } + + return nil +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { diff --git a/flag_int_slice.go b/flag_int_slice.go index 94c668e9f1..6c427c1d6f 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -97,6 +97,7 @@ type IntSliceFlag struct { Value *IntSlice DefaultText string HasBeenSet bool + Action func(*Context, []int) error } // IsSet returns whether or not the flag has been set through env or file @@ -172,6 +173,15 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *IntSliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.IntSlice(f.Name)) + } + + return nil +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { diff --git a/flag_path.go b/flag_path.go index b3aa9191d3..6bd08af82a 100644 --- a/flag_path.go +++ b/flag_path.go @@ -15,6 +15,7 @@ type PathFlag struct { DefaultText string Destination *string HasBeenSet bool + Action func(*Context, string) error } // IsSet returns whether or not the flag has been set through env or file @@ -77,6 +78,15 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *PathFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Path(f.Name)) + } + + return nil +} + // Path looks up the value of a local PathFlag, returns // "" if not found func (c *Context) Path(name string) string { diff --git a/flag_string.go b/flag_string.go index aad4c43a9c..0073ebc4e3 100644 --- a/flag_string.go +++ b/flag_string.go @@ -16,6 +16,7 @@ type StringFlag struct { DefaultText string Destination *string HasBeenSet bool + Action func(*Context, string) error } // IsSet returns whether or not the flag has been set through env or file @@ -78,6 +79,15 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *StringFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.String(f.Name)) + } + + return nil +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { diff --git a/flag_string_slice.go b/flag_string_slice.go index 5269643105..e216f337e0 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -82,6 +82,7 @@ type StringSliceFlag struct { DefaultText string HasBeenSet bool Destination *StringSlice + Action func(*Context, []string) error } // IsSet returns whether or not the flag has been set through env or file @@ -173,6 +174,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *StringSliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.StringSlice(f.Name)) + } + + return nil +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { diff --git a/flag_timestamp.go b/flag_timestamp.go index 7458a79b6a..09c77edbc2 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -72,6 +72,7 @@ type TimestampFlag struct { DefaultText string HasBeenSet bool Destination *Timestamp + Action func(*Context, *time.Time) error } // IsSet returns whether or not the flag has been set through env or file @@ -151,6 +152,15 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *TimestampFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Timestamp(f.Name)) + } + + return nil +} + // Timestamp gets the timestamp from a flag name func (c *Context) Timestamp(name string) *time.Time { if fs := c.lookupFlagSet(name); fs != nil { diff --git a/flag_uint.go b/flag_uint.go index 23a70a7f90..1b8dd57863 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -19,6 +19,7 @@ type UintFlag struct { DefaultText string Destination *uint HasBeenSet bool + Action func(*Context, uint) error } // IsSet returns whether or not the flag has been set through env or file @@ -82,6 +83,15 @@ func (f *UintFlag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *UintFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Uint(f.Name)) + } + + return nil +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *UintFlag) GetValue() string { diff --git a/flag_uint64.go b/flag_uint64.go index a2df024e18..49d1653a12 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -19,6 +19,7 @@ type Uint64Flag struct { DefaultText string Destination *uint64 HasBeenSet bool + Action func(*Context, uint64) error } // IsSet returns whether or not the flag has been set through env or file @@ -82,6 +83,15 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) error { return nil } +// RunAction executes flag action if set +func (f *Uint64Flag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Uint64(f.Name)) + } + + return nil +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Uint64Flag) GetValue() string {