From 14366f7030b89bce4ccd084eecfd5f52b8a96c7c Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Tue, 15 Feb 2022 23:49:41 +0800 Subject: [PATCH 1/6] feat: flag action --- app.go | 26 +++++++++++++++++ app_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++ command.go | 4 +++ flag.go | 1 + flag_bool.go | 9 ++++++ flag_duration.go | 9 ++++++ flag_float64.go | 9 ++++++ flag_float64_slice.go | 9 ++++++ flag_generic.go | 9 ++++++ flag_int.go | 9 ++++++ flag_int64.go | 9 ++++++ flag_int64_slice.go | 9 ++++++ flag_int_slice.go | 33 +++++++++++++++++++++ flag_path.go | 9 ++++++ flag_string.go | 9 ++++++ flag_string_slice.go | 9 ++++++ flag_timestamp.go | 9 ++++++ flag_uint.go | 9 ++++++ flag_uint64.go | 9 ++++++ 19 files changed, 257 insertions(+) diff --git a/app.go b/app.go index 2ffacd512c..2a291ae40a 100644 --- a/app.go +++ b/app.go @@ -342,6 +342,10 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } } + if err = runFlagActions(cCtx, a.Flags); err != nil { + return err + } + var c *Command args := cCtx.Args() if args.Present() { @@ -523,6 +527,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } + if err = runFlagActions(cCtx, a.Flags); err != nil { + return err + } + args := cCtx.Args() if args.Present() { name := args.First() @@ -646,6 +654,24 @@ func (a *App) argsWithDefaultCommand(oldArgs Args) Args { return oldArgs } +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 64316fc79e..9cd067eebc 100644 --- a/app_test.go +++ b/app_test.go @@ -2357,6 +2357,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 } @@ -2576,3 +2580,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 13b79de46d..cd8ea91c70 100644 --- a/command.go +++ b/command.go @@ -165,6 +165,10 @@ func (c *Command) Run(ctx *Context) (err error) { } } + if err = runFlagActions(cCtx, c.Flags); err != nil { + return err + } + if c.Action == nil { c.Action = helpSubcommand.Action } diff --git a/flag.go b/flag.go index 050bb4b1d1..a1e37fc5a0 100644 --- a/flag.go +++ b/flag.go @@ -92,6 +92,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 cb937ae65d..0be27e3aa6 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -92,6 +92,15 @@ func (f *BoolFlag) GetEnvVars() []string { return f.EnvVars } +// 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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_duration.go b/flag_duration.go index 5178c6ae12..31db4102e6 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -70,6 +70,15 @@ func (f *DurationFlag) Get(ctx *Context) time.Duration { return ctx.Duration(f.Name) } +// 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 (cCtx *Context) Duration(name string) time.Duration { diff --git a/flag_float64.go b/flag_float64.go index 2d31739bc6..bce26c1958 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -70,6 +70,15 @@ func (f *Float64Flag) Get(ctx *Context) float64 { return ctx.Float64(f.Name) } +// 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 (cCtx *Context) Float64(name string) float64 { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index e4aff73da0..2cb5e4adfa 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -181,6 +181,15 @@ func (f *Float64SliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// 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 (cCtx *Context) Float64Slice(name string) []float64 { diff --git a/flag_generic.go b/flag_generic.go index 6a19aef36c..5034728c42 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -73,6 +73,15 @@ func (f *GenericFlag) Get(ctx *Context) interface{} { return ctx.Generic(f.Name) } +// 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 (cCtx *Context) Generic(name string) interface{} { diff --git a/flag_int.go b/flag_int.go index 0f5c403b3d..af98e936fb 100644 --- a/flag_int.go +++ b/flag_int.go @@ -71,6 +71,15 @@ func (f *IntFlag) Get(ctx *Context) int { return ctx.Int(f.Name) } +// 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 (cCtx *Context) Int(name string) int { diff --git a/flag_int64.go b/flag_int64.go index a392275def..ebe46d21fc 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -70,6 +70,15 @@ func (f *Int64Flag) Get(ctx *Context) int64 { return ctx.Int64(f.Name) } +// 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 (cCtx *Context) Int64(name string) int64 { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index ead4e77570..d4a11b6a81 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -179,6 +179,15 @@ func (f *Int64SliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// 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 (cCtx *Context) Int64Slice(name string) []int64 { diff --git a/flag_int_slice.go b/flag_int_slice.go index b40e0d8d1a..68ce483650 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -92,6 +92,29 @@ func (i *IntSlice) Get() interface{} { return *i } +<<<<<<< HEAD +======= +// IntSliceFlag is a flag with type *IntSlice +type IntSliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + 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 +func (f *IntSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +>>>>>>> e132f01 (feat: flag action) // String returns a readable representation of this value // (for usage defaults) func (f *IntSliceFlag) String() string { @@ -174,9 +197,19 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { return nil } +<<<<<<< HEAD // Get returns the flag’s value in the given Context. func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) +======= +// 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 +>>>>>>> e132f01 (feat: flag action) } func (f *IntSliceFlag) stringify() string { diff --git a/flag_path.go b/flag_path.go index 7c87a8900d..911819db94 100644 --- a/flag_path.go +++ b/flag_path.go @@ -67,6 +67,15 @@ func (f *PathFlag) Get(ctx *Context) string { return ctx.Path(f.Name) } +// 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 (cCtx *Context) Path(name string) string { diff --git a/flag_string.go b/flag_string.go index c8da38f92d..b7163ba6f6 100644 --- a/flag_string.go +++ b/flag_string.go @@ -65,6 +65,15 @@ func (f *StringFlag) Get(ctx *Context) string { return ctx.String(f.Name) } +// 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 (cCtx *Context) String(name string) string { diff --git a/flag_string_slice.go b/flag_string_slice.go index 9d69342db1..7b46a24742 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -171,6 +171,15 @@ func (f *StringSliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// 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 (cCtx *Context) StringSlice(name string) []string { diff --git a/flag_timestamp.go b/flag_timestamp.go index 16f42dd011..17bc8d7571 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -148,6 +148,15 @@ func (f *TimestampFlag) Get(ctx *Context) *time.Time { return ctx.Timestamp(f.Name) } +// 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 (cCtx *Context) Timestamp(name string) *time.Time { if fs := cCtx.lookupFlagSet(name); fs != nil { diff --git a/flag_uint.go b/flag_uint.go index d25ff73ad7..f9acb6d06c 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -46,6 +46,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 975c73393b..09590d7353 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -46,6 +46,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 { From 619958c3d2ef6d024d4498744247288c867ea04a Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sun, 1 May 2022 00:24:13 +0800 Subject: [PATCH 2/6] Add ActionableFlag interface instead of modifying Flag interface directly --- app.go | 6 ++++-- flag.go | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 2a291ae40a..9f72f1b4f3 100644 --- a/app.go +++ b/app.go @@ -664,8 +664,10 @@ func runFlagActions(c *Context, fs []Flag) error { } } if isSet { - if err := f.RunAction(c); err != nil { - return err + if af, ok := f.(ActionableFlag); ok { + if err := af.RunAction(c); err != nil { + return err + } } } } diff --git a/flag.go b/flag.go index a1e37fc5a0..7b5ec498c7 100644 --- a/flag.go +++ b/flag.go @@ -83,6 +83,12 @@ func (f FlagsByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } +// ActionableFlag is an interface that wraps Flag interface and RunAction operation. +type ActionableFlag interface { + Flag + RunAction(*Context) error +} + // Flag is a common interface related to parsing flags in cli. // For more advanced flag parsing techniques, it is recommended that // this interface be implemented. @@ -92,7 +98,6 @@ 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 From 602471bf21013af36e31bdd4ea33534d53bf61ca Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sun, 1 May 2022 02:49:01 +0800 Subject: [PATCH 3/6] Add more tests about flag-level action --- app_test.go | 253 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 224 insertions(+), 29 deletions(-) diff --git a/app_test.go b/app_test.go index 9cd067eebc..13f40e10fa 100644 --- a/app_test.go +++ b/app_test.go @@ -9,8 +9,10 @@ import ( "io/ioutil" "os" "reflect" + "strconv" "strings" "testing" + "time" ) var ( @@ -2581,65 +2583,258 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { } } +type stringGeneric struct { + value string +} + +func (s *stringGeneric) Set(value string) error { + s.value = value + return nil +} + +func (s *stringGeneric) String() string { + return s.value +} + func TestFlagAction(t *testing.T) { - r := []string{} - actionFunc := func(c *Context, s string) error { - r = append(r, s) - return nil + stringFlag := &StringFlag{ + Name: "f_string", + Action: func(c *Context, v string) error { + c.App.Writer.Write([]byte(v + " ")) + return nil + }, } - app := &App{ - Name: "command", - Writer: io.Discard, - Flags: []Flag{&StringFlag{Name: "flag", Action: actionFunc}}, + Name: "app", Commands: []*Command{ { - Name: "command1", - Flags: []Flag{&StringFlag{Name: "flag1", Aliases: []string{"f1"}, Action: actionFunc}}, + Name: "c1", + Flags: []Flag{stringFlag}, + Action: func(ctx *Context) error { return nil }, Subcommands: []*Command{ { - Name: "command2", - Flags: []Flag{&StringFlag{Name: "flag2", Action: actionFunc}}, + Name: "sub1", + Action: func(ctx *Context) error { return nil }, + Flags: []Flag{stringFlag}, }, }, }, }, + Flags: []Flag{ + stringFlag, + &StringSliceFlag{ + Name: "f_string_slice", + Action: func(c *Context, v []string) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &BoolFlag{ + Name: "f_bool", + Action: func(c *Context, v bool) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%t ", v))) + return nil + }, + }, + &DurationFlag{ + Name: "f_duration", + Action: func(c *Context, v time.Duration) error { + c.App.Writer.Write([]byte(v.String() + " ")) + return nil + }, + }, + &Float64Flag{ + Name: "f_float64", + Action: func(c *Context, v float64) error { + c.App.Writer.Write([]byte(strconv.FormatFloat(v, 'f', -1, 64) + " ")) + return nil + }, + }, + &Float64SliceFlag{ + Name: "f_float64_slice", + Action: func(c *Context, v []float64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &GenericFlag{ + Name: "f_generic", + Value: new(stringGeneric), + Action: func(c *Context, v interface{}) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &IntFlag{ + Name: "f_int", + Action: func(c *Context, v int) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &IntSliceFlag{ + Name: "f_int_slice", + Action: func(c *Context, v []int) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Int64Flag{ + Name: "f_int64", + Action: func(c *Context, v int64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Int64SliceFlag{ + Name: "f_int64_slice", + Action: func(c *Context, v []int64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &PathFlag{ + Name: "f_path", + Action: func(c *Context, v string) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &TimestampFlag{ + Name: "f_timestamp", + Layout: "2006-01-02 15:04:05", + Action: func(c *Context, v *time.Time) error { + c.App.Writer.Write([]byte(v.Format(time.RFC3339) + " ")) + return nil + }, + }, + &UintFlag{ + Name: "f_uint", + Action: func(c *Context, v uint) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Uint64Flag{ + Name: "f_uint64", + Action: func(c *Context, v uint64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + }, + Action: func(ctx *Context) error { return nil }, } tests := []struct { + name string args []string - exp []string + exp string }{ { - args: []string{"command", "--flag=f"}, - exp: []string{"f"}, + name: "flag_empty", + args: []string{"app"}, + exp: "", + }, + { + name: "flag_string", + args: []string{"app", "--f_string=string"}, + exp: "string ", + }, + { + name: "flag_string_slice", + args: []string{"app", "--f_string_slice=s1,s2,s3"}, + exp: "[s1 s2 s3] ", + }, + { + name: "flag_bool", + args: []string{"app", "--f_bool"}, + exp: "true ", + }, + { + name: "flag_duration", + args: []string{"app", "--f_duration=1h30m20s"}, + exp: "1h30m20s ", }, { - args: []string{"command", "command1", "-f1=f1", "command2"}, - exp: []string{"f1"}, + name: "flag_float64", + args: []string{"app", "--f_float64=3.14159"}, + exp: "3.14159 ", }, { - args: []string{"command", "command1", "-f1=f1", "command2", "--flag2=f2"}, - exp: []string{"f1", "f2"}, + name: "flag_float64_slice", + args: []string{"app", "--f_float64_slice=1.1,2.2,3.3"}, + exp: "[1.1 2.2 3.3] ", }, { - args: []string{"command", "--flag=f", "command1", "-flag1=f1"}, - exp: []string{"f", "f1"}, + name: "flag_generic", + args: []string{"app", "--f_generic=1"}, + exp: "1 ", }, { - args: []string{"command", "--flag=f", "command1", "-f1=f1"}, - exp: []string{"f", "f1"}, + name: "flag_int", + args: []string{"app", "--f_int=1"}, + exp: "1 ", }, { - args: []string{"command", "--flag=f", "command1", "-f1=f1", "command2", "--flag2=f2"}, - exp: []string{"f", "f1", "f2"}, + name: "flag_int_slice", + args: []string{"app", "--f_int_slice=1,2,3"}, + exp: "[1 2 3] ", + }, + { + name: "flag_int64", + args: []string{"app", "--f_int64=1"}, + exp: "1 ", + }, + { + name: "flag_int64_slice", + args: []string{"app", "--f_int64_slice=1,2,3"}, + exp: "[1 2 3] ", + }, + { + name: "flag_path", + args: []string{"app", "--f_path=/root"}, + exp: "/root ", + }, + { + name: "flag_timestamp", + args: []string{"app", "--f_timestamp", "2022-05-01 02:26:20"}, + exp: "2022-05-01T02:26:20Z ", + }, + { + name: "flag_uint", + args: []string{"app", "--f_uint=1"}, + exp: "1 ", + }, + { + name: "flag_uint64", + args: []string{"app", "--f_uint64=1"}, + exp: "1 ", + }, + { + name: "command_flag", + args: []string{"app", "c1", "--f_string=c1"}, + exp: "c1 ", + }, + { + name: "subCommand_flag", + args: []string{"app", "c1", "sub1", "--f_string=sub1"}, + exp: "sub1 ", + }, + { + name: "mixture", + args: []string{"app", "--f_string=app", "--f_uint=1", "--f_int_slice=1,2,3", "--f_duration=1h30m20s", "c1", "--f_string=c1", "sub1", "--f_string=sub1"}, + exp: "app 1h30m20s [1 2 3] 1 c1 sub1 ", }, } for _, test := range tests { - r = []string{} - err := app.Run(test.args) - expect(t, err, nil) - expect(t, r, test.exp) + t.Run(test.name, func(t *testing.T) { + buf := new(bytes.Buffer) + app.Writer = buf + err := app.Run(test.args) + expect(t, err, nil) + expect(t, buf.String(), test.exp) + }) } } From b33117618a27c54936a1286f94e368e026ded5f8 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sun, 22 May 2022 23:07:10 +0800 Subject: [PATCH 4/6] Rebase main, update flag-spec.yaml to add Action field --- cmd/urfave-cli-genflags/go.sum | 3 -- flag-spec.yaml | 41 +++++++++++++++++++++++++++ flag_int_slice.go | 28 ++---------------- godoc-current.txt | 52 ++++++++++++++++++++++++++++++++++ zz_generated.flags.go | 34 ++++++++++++++++++++++ 5 files changed, 129 insertions(+), 29 deletions(-) diff --git a/cmd/urfave-cli-genflags/go.sum b/cmd/urfave-cli-genflags/go.sum index 98211271c8..e59916dc6f 100644 --- a/cmd/urfave-cli-genflags/go.sum +++ b/cmd/urfave-cli-genflags/go.sum @@ -1,5 +1,3 @@ -github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -10,7 +8,6 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/flag-spec.yaml b/flag-spec.yaml index 5c480bb09f..76480ba47c 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -7,47 +7,77 @@ flag_types: - name: Count type: int pointer: true + - name: Action + type: "func(*Context, bool) error" float64: + struct_fields: + - name: Action + type: "func(*Context, float64) error" Float64Slice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []float64) error" int: struct_fields: - name: Base type: int + - name: Action + type: "func(*Context, int) error" IntSlice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []int) error" int64: struct_fields: - name: Base type: int + - name: Action + type: "func(*Context, int64) error" Int64Slice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []int64) error" uint: struct_fields: - name: Base type: int + - name: Action + type: "func(*Context, uint) error" UintSlice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []uint) error" uint64: struct_fields: - name: Base type: int + - name: Action + type: "func(*Context, uint64) error" Uint64Slice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []uint64) error" string: struct_fields: - name: TakesFile type: bool + - name: Action + type: "func(*Context, string) error" StringSlice: value_pointer: true skip_interfaces: @@ -55,7 +85,12 @@ flag_types: struct_fields: - name: TakesFile type: bool + - name: Action + type: "func(*Context, []string) error" time.Duration: + struct_fields: + - name: Action + type: "func(*Context, time.Duration) error" Timestamp: value_pointer: true struct_fields: @@ -63,12 +98,18 @@ flag_types: type: string - name: Timezone type: "*time.Location" + - name: Action + type: "func(*Context, *time.Time) error" Generic: no_destination_pointer: true struct_fields: - name: TakesFile type: bool + - name: Action + type: "func(*Context, interface{}) error" Path: struct_fields: - name: TakesFile type: bool + - name: Action + type: "func(*Context, Path) error" diff --git a/flag_int_slice.go b/flag_int_slice.go index 68ce483650..2cabe7202f 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -92,29 +92,6 @@ func (i *IntSlice) Get() interface{} { return *i } -<<<<<<< HEAD -======= -// IntSliceFlag is a flag with type *IntSlice -type IntSliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - 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 -func (f *IntSliceFlag) IsSet() bool { - return f.HasBeenSet -} - ->>>>>>> e132f01 (feat: flag action) // String returns a readable representation of this value // (for usage defaults) func (f *IntSliceFlag) String() string { @@ -197,11 +174,11 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { return nil } -<<<<<<< HEAD // Get returns the flag’s value in the given Context. func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) -======= +} + // RunAction executes flag action if set func (f *IntSliceFlag) RunAction(c *Context) error { if f.Action != nil { @@ -209,7 +186,6 @@ func (f *IntSliceFlag) RunAction(c *Context) error { } return nil ->>>>>>> e132f01 (feat: flag action) } func (f *IntSliceFlag) stringify() string { diff --git a/godoc-current.txt b/godoc-current.txt index fbeeb5fb8b..d41dc97a19 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -250,6 +250,13 @@ TYPES type ActionFunc func(*Context) error ActionFunc is the action to execute when no subcommands are specified +type ActionableFlag interface { + Flag + RunAction(*Context) error +} + ActionableFlag is an interface that wraps Flag interface and RunAction + operation. + type AfterFunc func(*Context) error AfterFunc is an action to execute after any subcommands are run, but after the subcommand has finished it is run even if Action() panics @@ -491,6 +498,9 @@ func (f *BoolFlag) IsVisible() bool func (f *BoolFlag) Names() []string Names returns the names of the flag +func (f *BoolFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *BoolFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -798,6 +808,9 @@ func (f *DurationFlag) IsVisible() bool func (f *DurationFlag) Names() []string Names returns the names of the flag +func (f *DurationFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *DurationFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -971,6 +984,9 @@ func (f *Float64Flag) IsVisible() bool func (f *Float64Flag) Names() []string Names returns the names of the flag +func (f *Float64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Float64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -1056,6 +1072,9 @@ func (f *Float64SliceFlag) IsVisible() bool func (f *Float64SliceFlag) Names() []string Names returns the names of the flag +func (f *Float64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Float64SliceFlag) SetDestination(slice []float64) func (f *Float64SliceFlag) SetValue(slice []float64) @@ -1129,6 +1148,9 @@ func (f *GenericFlag) IsVisible() bool func (f *GenericFlag) Names() []string Names returns the names of the flag +func (f *GenericFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *GenericFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1191,6 +1213,9 @@ func (f *Int64Flag) IsVisible() bool func (f *Int64Flag) Names() []string Names returns the names of the flag +func (f *Int64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Int64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -1276,6 +1301,9 @@ func (f *Int64SliceFlag) IsVisible() bool func (f *Int64SliceFlag) Names() []string Names returns the names of the flag +func (f *Int64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Int64SliceFlag) SetDestination(slice []int64) func (f *Int64SliceFlag) SetValue(slice []int64) @@ -1342,6 +1370,9 @@ func (f *IntFlag) IsVisible() bool func (f *IntFlag) Names() []string Names returns the names of the flag +func (f *IntFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *IntFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1431,6 +1462,9 @@ func (f *IntSliceFlag) IsVisible() bool func (f *IntSliceFlag) Names() []string Names returns the names of the flag +func (f *IntSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *IntSliceFlag) SetDestination(slice []int) func (f *IntSliceFlag) SetValue(slice []int) @@ -1531,6 +1565,9 @@ func (f *PathFlag) IsVisible() bool func (f *PathFlag) Names() []string Names returns the names of the flag +func (f *PathFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *PathFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1668,6 +1705,9 @@ func (f *StringFlag) IsVisible() bool func (f *StringFlag) Names() []string Names returns the names of the flag +func (f *StringFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *StringFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1755,6 +1795,9 @@ func (f *StringSliceFlag) IsVisible() bool func (f *StringSliceFlag) Names() []string Names returns the names of the flag +func (f *StringSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *StringSliceFlag) SetDestination(slice []string) func (f *StringSliceFlag) SetValue(slice []string) @@ -1856,6 +1899,9 @@ func (f *TimestampFlag) IsVisible() bool func (f *TimestampFlag) Names() []string Names returns the names of the flag +func (f *TimestampFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *TimestampFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1918,6 +1964,9 @@ func (f *Uint64Flag) IsVisible() bool func (f *Uint64Flag) Names() []string Names returns the names of the flag +func (f *Uint64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Uint64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -2063,6 +2112,9 @@ func (f *UintFlag) IsVisible() bool func (f *UintFlag) Names() []string Names returns the names of the flag +func (f *UintFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *UintFlag) String() string String returns a readable representation of this value (for usage defaults) diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 034ef35dad..0309a28689 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -22,6 +22,8 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []float64) error } // IsSet returns whether or not the flag has been set through env or file @@ -64,6 +66,8 @@ type GenericFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, interface{}) error } // String returns a readable representation of this value (for usage defaults) @@ -109,6 +113,8 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int64) error } // IsSet returns whether or not the flag has been set through env or file @@ -149,6 +155,8 @@ type IntSliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int) error } // IsSet returns whether or not the flag has been set through env or file @@ -191,6 +199,8 @@ type PathFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, Path) error } // String returns a readable representation of this value (for usage defaults) @@ -238,6 +248,8 @@ type StringSliceFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, []string) error } // IsSet returns whether or not the flag has been set through env or file @@ -282,6 +294,8 @@ type TimestampFlag struct { Layout string Timezone *time.Location + + Action func(*Context, *time.Time) error } // String returns a readable representation of this value (for usage defaults) @@ -327,6 +341,8 @@ type Uint64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []uint64) error } // IsSet returns whether or not the flag has been set through env or file @@ -367,6 +383,8 @@ type UintSliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []uint) error } // IsSet returns whether or not the flag has been set through env or file @@ -409,6 +427,8 @@ type BoolFlag struct { EnvVars []string Count *int + + Action func(*Context, bool) error } // String returns a readable representation of this value (for usage defaults) @@ -454,6 +474,8 @@ type Float64Flag struct { Aliases []string EnvVars []string + + Action func(*Context, float64) error } // String returns a readable representation of this value (for usage defaults) @@ -501,6 +523,8 @@ type IntFlag struct { EnvVars []string Base int + + Action func(*Context, int) error } // String returns a readable representation of this value (for usage defaults) @@ -548,6 +572,8 @@ type Int64Flag struct { EnvVars []string Base int + + Action func(*Context, int64) error } // String returns a readable representation of this value (for usage defaults) @@ -595,6 +621,8 @@ type StringFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, string) error } // String returns a readable representation of this value (for usage defaults) @@ -640,6 +668,8 @@ type DurationFlag struct { Aliases []string EnvVars []string + + Action func(*Context, time.Duration) error } // String returns a readable representation of this value (for usage defaults) @@ -687,6 +717,8 @@ type UintFlag struct { EnvVars []string Base int + + Action func(*Context, uint) error } // String returns a readable representation of this value (for usage defaults) @@ -734,6 +766,8 @@ type Uint64Flag struct { EnvVars []string Base int + + Action func(*Context, uint64) error } // String returns a readable representation of this value (for usage defaults) From 7570aace82e2edb184fe47a5579216acb4911d5b Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Fri, 16 Sep 2022 00:42:24 +0900 Subject: [PATCH 5/6] make v2approve --- testdata/godoc-v2.x.txt | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index fbeeb5fb8b..d41dc97a19 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -250,6 +250,13 @@ TYPES type ActionFunc func(*Context) error ActionFunc is the action to execute when no subcommands are specified +type ActionableFlag interface { + Flag + RunAction(*Context) error +} + ActionableFlag is an interface that wraps Flag interface and RunAction + operation. + type AfterFunc func(*Context) error AfterFunc is an action to execute after any subcommands are run, but after the subcommand has finished it is run even if Action() panics @@ -491,6 +498,9 @@ func (f *BoolFlag) IsVisible() bool func (f *BoolFlag) Names() []string Names returns the names of the flag +func (f *BoolFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *BoolFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -798,6 +808,9 @@ func (f *DurationFlag) IsVisible() bool func (f *DurationFlag) Names() []string Names returns the names of the flag +func (f *DurationFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *DurationFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -971,6 +984,9 @@ func (f *Float64Flag) IsVisible() bool func (f *Float64Flag) Names() []string Names returns the names of the flag +func (f *Float64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Float64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -1056,6 +1072,9 @@ func (f *Float64SliceFlag) IsVisible() bool func (f *Float64SliceFlag) Names() []string Names returns the names of the flag +func (f *Float64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Float64SliceFlag) SetDestination(slice []float64) func (f *Float64SliceFlag) SetValue(slice []float64) @@ -1129,6 +1148,9 @@ func (f *GenericFlag) IsVisible() bool func (f *GenericFlag) Names() []string Names returns the names of the flag +func (f *GenericFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *GenericFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1191,6 +1213,9 @@ func (f *Int64Flag) IsVisible() bool func (f *Int64Flag) Names() []string Names returns the names of the flag +func (f *Int64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Int64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -1276,6 +1301,9 @@ func (f *Int64SliceFlag) IsVisible() bool func (f *Int64SliceFlag) Names() []string Names returns the names of the flag +func (f *Int64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Int64SliceFlag) SetDestination(slice []int64) func (f *Int64SliceFlag) SetValue(slice []int64) @@ -1342,6 +1370,9 @@ func (f *IntFlag) IsVisible() bool func (f *IntFlag) Names() []string Names returns the names of the flag +func (f *IntFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *IntFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1431,6 +1462,9 @@ func (f *IntSliceFlag) IsVisible() bool func (f *IntSliceFlag) Names() []string Names returns the names of the flag +func (f *IntSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *IntSliceFlag) SetDestination(slice []int) func (f *IntSliceFlag) SetValue(slice []int) @@ -1531,6 +1565,9 @@ func (f *PathFlag) IsVisible() bool func (f *PathFlag) Names() []string Names returns the names of the flag +func (f *PathFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *PathFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1668,6 +1705,9 @@ func (f *StringFlag) IsVisible() bool func (f *StringFlag) Names() []string Names returns the names of the flag +func (f *StringFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *StringFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1755,6 +1795,9 @@ func (f *StringSliceFlag) IsVisible() bool func (f *StringSliceFlag) Names() []string Names returns the names of the flag +func (f *StringSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *StringSliceFlag) SetDestination(slice []string) func (f *StringSliceFlag) SetValue(slice []string) @@ -1856,6 +1899,9 @@ func (f *TimestampFlag) IsVisible() bool func (f *TimestampFlag) Names() []string Names returns the names of the flag +func (f *TimestampFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *TimestampFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1918,6 +1964,9 @@ func (f *Uint64Flag) IsVisible() bool func (f *Uint64Flag) Names() []string Names returns the names of the flag +func (f *Uint64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Uint64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -2063,6 +2112,9 @@ func (f *UintFlag) IsVisible() bool func (f *UintFlag) Names() []string Names returns the names of the flag +func (f *UintFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *UintFlag) String() string String returns a readable representation of this value (for usage defaults) From 47f6782a902c671f3629001c1603bb75eab3152a Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sat, 17 Sep 2022 16:05:26 +0900 Subject: [PATCH 6/6] Add more test cases --- app_test.go | 147 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 7 deletions(-) diff --git a/app_test.go b/app_test.go index 13f40e10fa..f3cbb1edaf 100644 --- a/app_test.go +++ b/app_test.go @@ -2600,6 +2600,9 @@ func TestFlagAction(t *testing.T) { stringFlag := &StringFlag{ Name: "f_string", Action: func(c *Context, v string) error { + if v == "" { + return fmt.Errorf("empty string") + } c.App.Writer.Write([]byte(v + " ")) return nil }, @@ -2622,9 +2625,15 @@ func TestFlagAction(t *testing.T) { }, Flags: []Flag{ stringFlag, + &StringFlag{ + Name: "f_no_action", + }, &StringSliceFlag{ Name: "f_string_slice", Action: func(c *Context, v []string) error { + if v[0] == "err" { + return fmt.Errorf("error string slice") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2632,6 +2641,9 @@ func TestFlagAction(t *testing.T) { &BoolFlag{ Name: "f_bool", Action: func(c *Context, v bool) error { + if !v { + return fmt.Errorf("value is false") + } c.App.Writer.Write([]byte(fmt.Sprintf("%t ", v))) return nil }, @@ -2639,6 +2651,9 @@ func TestFlagAction(t *testing.T) { &DurationFlag{ Name: "f_duration", Action: func(c *Context, v time.Duration) error { + if v == 0 { + return fmt.Errorf("empty duration") + } c.App.Writer.Write([]byte(v.String() + " ")) return nil }, @@ -2646,6 +2661,9 @@ func TestFlagAction(t *testing.T) { &Float64Flag{ Name: "f_float64", Action: func(c *Context, v float64) error { + if v < 0 { + return fmt.Errorf("negative float64") + } c.App.Writer.Write([]byte(strconv.FormatFloat(v, 'f', -1, 64) + " ")) return nil }, @@ -2653,6 +2671,9 @@ func TestFlagAction(t *testing.T) { &Float64SliceFlag{ Name: "f_float64_slice", Action: func(c *Context, v []float64) error { + if len(v) > 0 && v[0] < 0 { + return fmt.Errorf("invalid float64 slice") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2661,6 +2682,14 @@ func TestFlagAction(t *testing.T) { Name: "f_generic", Value: new(stringGeneric), Action: func(c *Context, v interface{}) error { + fmt.Printf("%T %v\n", v, v) + switch vv := v.(type) { + case *stringGeneric: + if vv.value == "" { + return fmt.Errorf("generic value not set") + } + } + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2668,6 +2697,9 @@ func TestFlagAction(t *testing.T) { &IntFlag{ Name: "f_int", Action: func(c *Context, v int) error { + if v < 0 { + return fmt.Errorf("negative int") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2675,6 +2707,9 @@ func TestFlagAction(t *testing.T) { &IntSliceFlag{ Name: "f_int_slice", Action: func(c *Context, v []int) error { + if len(v) > 0 && v[0] < 0 { + return fmt.Errorf("invalid int slice") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2682,6 +2717,9 @@ func TestFlagAction(t *testing.T) { &Int64Flag{ Name: "f_int64", Action: func(c *Context, v int64) error { + if v < 0 { + return fmt.Errorf("negative int64") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2689,6 +2727,9 @@ func TestFlagAction(t *testing.T) { &Int64SliceFlag{ Name: "f_int64_slice", Action: func(c *Context, v []int64) error { + if len(v) > 0 && v[0] < 0 { + return fmt.Errorf("invalid int64 slice") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2696,6 +2737,9 @@ func TestFlagAction(t *testing.T) { &PathFlag{ Name: "f_path", Action: func(c *Context, v string) error { + if v == "" { + return fmt.Errorf("empty path") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2704,6 +2748,9 @@ func TestFlagAction(t *testing.T) { Name: "f_timestamp", Layout: "2006-01-02 15:04:05", Action: func(c *Context, v *time.Time) error { + if v.IsZero() { + return fmt.Errorf("zero timestamp") + } c.App.Writer.Write([]byte(v.Format(time.RFC3339) + " ")) return nil }, @@ -2711,6 +2758,9 @@ func TestFlagAction(t *testing.T) { &UintFlag{ Name: "f_uint", Action: func(c *Context, v uint) error { + if v == 0 { + return fmt.Errorf("zero uint") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2718,6 +2768,9 @@ func TestFlagAction(t *testing.T) { &Uint64Flag{ Name: "f_uint64", Action: func(c *Context, v uint64) error { + if v == 0 { + return fmt.Errorf("zero uint64") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2729,88 +2782,164 @@ func TestFlagAction(t *testing.T) { tests := []struct { name string args []string + err error exp string }{ - { - name: "flag_empty", - args: []string{"app"}, - exp: "", - }, { name: "flag_string", args: []string{"app", "--f_string=string"}, exp: "string ", }, + { + name: "flag_string_error", + args: []string{"app", "--f_string="}, + err: fmt.Errorf("empty string"), + }, { name: "flag_string_slice", args: []string{"app", "--f_string_slice=s1,s2,s3"}, exp: "[s1 s2 s3] ", }, + { + name: "flag_string_slice_error", + args: []string{"app", "--f_string_slice=err"}, + err: fmt.Errorf("error string slice"), + }, { name: "flag_bool", args: []string{"app", "--f_bool"}, exp: "true ", }, + { + name: "flag_bool_error", + args: []string{"app", "--f_bool=false"}, + err: fmt.Errorf("value is false"), + }, { name: "flag_duration", args: []string{"app", "--f_duration=1h30m20s"}, exp: "1h30m20s ", }, + { + name: "flag_duration_error", + args: []string{"app", "--f_duration=0"}, + err: fmt.Errorf("empty duration"), + }, { name: "flag_float64", args: []string{"app", "--f_float64=3.14159"}, exp: "3.14159 ", }, + { + name: "flag_float64_error", + args: []string{"app", "--f_float64=-1"}, + err: fmt.Errorf("negative float64"), + }, { name: "flag_float64_slice", args: []string{"app", "--f_float64_slice=1.1,2.2,3.3"}, exp: "[1.1 2.2 3.3] ", }, + { + name: "flag_float64_slice_error", + args: []string{"app", "--f_float64_slice=-1"}, + err: fmt.Errorf("invalid float64 slice"), + }, { name: "flag_generic", args: []string{"app", "--f_generic=1"}, exp: "1 ", }, + { + name: "flag_generic_error", + args: []string{"app", "--f_generic="}, + err: fmt.Errorf("generic value not set"), + }, { name: "flag_int", args: []string{"app", "--f_int=1"}, exp: "1 ", }, + { + name: "flag_int_error", + args: []string{"app", "--f_int=-1"}, + err: fmt.Errorf("negative int"), + }, { name: "flag_int_slice", args: []string{"app", "--f_int_slice=1,2,3"}, exp: "[1 2 3] ", }, + { + name: "flag_int_slice_error", + args: []string{"app", "--f_int_slice=-1"}, + err: fmt.Errorf("invalid int slice"), + }, { name: "flag_int64", args: []string{"app", "--f_int64=1"}, exp: "1 ", }, + { + name: "flag_int64_error", + args: []string{"app", "--f_int64=-1"}, + err: fmt.Errorf("negative int64"), + }, { name: "flag_int64_slice", args: []string{"app", "--f_int64_slice=1,2,3"}, exp: "[1 2 3] ", }, + { + name: "flag_int64_slice", + args: []string{"app", "--f_int64_slice=-1"}, + err: fmt.Errorf("invalid int64 slice"), + }, { name: "flag_path", args: []string{"app", "--f_path=/root"}, exp: "/root ", }, + { + name: "flag_path_error", + args: []string{"app", "--f_path="}, + err: fmt.Errorf("empty path"), + }, { name: "flag_timestamp", args: []string{"app", "--f_timestamp", "2022-05-01 02:26:20"}, exp: "2022-05-01T02:26:20Z ", }, + { + name: "flag_timestamp_error", + args: []string{"app", "--f_timestamp", "0001-01-01 00:00:00"}, + err: fmt.Errorf("zero timestamp"), + }, { name: "flag_uint", args: []string{"app", "--f_uint=1"}, exp: "1 ", }, + { + name: "flag_uint_error", + args: []string{"app", "--f_uint=0"}, + err: fmt.Errorf("zero uint"), + }, { name: "flag_uint64", args: []string{"app", "--f_uint64=1"}, exp: "1 ", }, + { + name: "flag_uint64_error", + args: []string{"app", "--f_uint64=0"}, + err: fmt.Errorf("zero uint64"), + }, + { + name: "flag_no_action", + args: []string{"app", "--f_no_action="}, + exp: "", + }, { name: "command_flag", args: []string{"app", "c1", "--f_string=c1"}, @@ -2833,8 +2962,12 @@ func TestFlagAction(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf err := app.Run(test.args) - expect(t, err, nil) - expect(t, buf.String(), test.exp) + if test.err != nil { + expect(t, err, test.err) + } else { + expect(t, err, nil) + expect(t, buf.String(), test.exp) + } }) } }