diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d089c0..f73065c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ arguments](https://github.com/codegangsta/cli/issues/355) when the user attempted to mix flags and arguments. Given the trade-offs we removed support for this reordering. +- adapter code for deprecated `Action` func signature +- deprecated `App.Author`, `App.Email`, and `Command.ShortName` fields ## [Unreleased] - (1.x series) ### Added diff --git a/app.go b/app.go index 7c9b958..066ea7a 100644 --- a/app.go +++ b/app.go @@ -6,26 +6,10 @@ import ( "io/ioutil" "os" "path/filepath" - "reflect" "sort" "time" ) -var ( - changeLogURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md" - appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) - runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) - - contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." - - errNonFuncAction = NewExitError("ERROR invalid Action type. "+ - fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+ - fmt.Sprintf("See %s", appActionDeprecationURL), 2) - errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+ - fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+ - fmt.Sprintf("See %s", appActionDeprecationURL), 2) -) - // App is the main structure of a cli application. It is recommended that // an app be created with the cli.NewApp() function type App struct { @@ -62,10 +46,7 @@ type App struct { // It is run even if Action() panics After AfterFunc // The action to execute when no subcommands are specified - Action interface{} - // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind - // of deprecation period has passed, maybe? - + Action ActionFunc // Execute this function if the proper command cannot be found CommandNotFound CommandNotFoundFunc // Execute this function if an usage error occurs @@ -76,10 +57,6 @@ type App struct { Authors []Author // Copyright of the binary if any Copyright string - // Name of Author (Note: Use App.Authors, this is deprecated) - Author string - // Email of Author (Note: Use App.Authors, this is deprecated) - Email string // Writer writer to write output to Writer io.Writer // ErrWriter writes error output @@ -126,10 +103,6 @@ func (a *App) Setup() { a.didSetup = true - if a.Author != "" || a.Email != "" { - a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) - } - newCmds := []Command{} for _, c := range a.Commands { if c.HelpName == "" { @@ -238,23 +211,12 @@ func (a *App) Run(arguments []string) (err error) { } // Run default Action - err = HandleAction(a.Action, context) + err = a.Action(context) HandleExitCoder(err) return err } -// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling -func (a *App) RunAndExitOnError() { - fmt.Fprintf(a.errWriter(), - "DEPRECATED cli.App.RunAndExitOnError. %s See %s\n", - contactSysadmin, runAndExitOnErrorDeprecationURL) - if err := a.Run(os.Args); err != nil { - fmt.Fprintln(a.errWriter(), err) - OsExiter(1) - } -} - // RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to // generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { @@ -358,7 +320,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } // Run default Action - err = HandleAction(a.Action, context) + err = a.Action(context) HandleExitCoder(err) return err @@ -456,43 +418,3 @@ func (a Author) String() string { return fmt.Sprintf("%v %v", a.Name, e) } - -// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an -// ActionFunc, a func with the legacy signature for Action, or some other -// invalid thing. If it's an ActionFunc or a func with the legacy signature for -// Action, the func is run! -func HandleAction(action interface{}, context *Context) (err error) { - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case error: - err = r.(error) - default: - err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2) - } - } - }() - - if reflect.TypeOf(action).Kind() != reflect.Func { - return errNonFuncAction - } - - vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) - - if len(vals) == 0 { - fmt.Fprintf(ErrWriter, - "DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n", - contactSysadmin, appActionDeprecationURL) - return nil - } - - if len(vals) > 1 { - return errInvalidActionSignature - } - - if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok { - return retErr - } - - return err -} diff --git a/app_test.go b/app_test.go index 5b9d9f3..8d81a5d 100644 --- a/app_test.go +++ b/app_test.go @@ -31,9 +31,7 @@ func ExampleApp_Run() { return nil } app.UsageText = "app [first_arg] [second_arg]" - app.Author = "Harrison" - app.Email = "harrison@lolwut.com" - app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} app.Run(os.Args) // Output: // Hello Jeremy @@ -1357,75 +1355,3 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { t.Errorf("Expect an intercepted error, but got \"%v\"", err) } } - -func TestHandleAction_WithNonFuncAction(t *testing.T) { - app := NewApp() - app.Action = 42 - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { - t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { - app := NewApp() - app.Action = func() string { return "" } - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR unknown Action error") { - t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { - app := NewApp() - app.Action = func(_ *Context) (int, error) { return 0, nil } - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action signature") { - t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} diff --git a/command.go b/command.go index 376e222..7a77953 100644 --- a/command.go +++ b/command.go @@ -11,8 +11,6 @@ import ( type Command struct { // The name of the command Name string - // short name of the command. Typically one character (deprecated, use `Aliases`) - ShortName string // A list of aliases for the command Aliases []string // A short description of the usage of this command @@ -34,10 +32,7 @@ type Command struct { // It is run even if Action() panics After AfterFunc // The function to call when this command is invoked - Action interface{} - // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind - // of deprecation period has passed, maybe? - + Action ActionFunc // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc // List of child commands @@ -151,7 +146,7 @@ func (c Command) Run(ctx *Context) (err error) { } context.Command = c - err = HandleAction(c.Action, context) + err = c.Action(context) if err != nil { HandleExitCoder(err) @@ -162,15 +157,10 @@ func (c Command) Run(ctx *Context) (err error) { // Names returns the names including short names and aliases. func (c Command) Names() []string { names := []string{c.Name} - - if c.ShortName != "" { - names = append(names, c.ShortName) - } - return append(names, c.Aliases...) } -// HasName returns true if Command.Name or Command.ShortName matches given name +// HasName returns true if Command.Name matches given name func (c Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { @@ -208,8 +198,6 @@ func (c Command) startApp(ctx *Context) error { app.Version = ctx.App.Version app.HideVersion = ctx.App.HideVersion app.Compiled = ctx.App.Compiled - app.Author = ctx.App.Author - app.Email = ctx.App.Email app.Writer = ctx.App.Writer app.categories = CommandCategories{} diff --git a/flag_test.go b/flag_test.go index f914da3..f163468 100644 --- a/flag_test.go +++ b/flag_test.go @@ -391,7 +391,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { expected := []string{"10", "20"} if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) { t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) @@ -399,6 +399,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { if !reflect.DeepEqual(ctx.StringSlice("s"), expected) { t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) } + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -408,13 +409,14 @@ func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { t.Errorf("main name not set: %v", ctx.StringSlice("serve")) } if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"9", "2"}) { t.Errorf("short name not set: %v", ctx.StringSlice("s")) } + return nil }, }).Run([]string{"run"}) } @@ -427,13 +429,14 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } @@ -466,13 +469,14 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } @@ -596,13 +600,14 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -612,13 +617,14 @@ func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.IntSlice("s"), []int{9, 2}) { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run"}) } @@ -631,13 +637,14 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } diff --git a/help_test.go b/help_test.go index a664865..d19bc4c 100644 --- a/help_test.go +++ b/help_test.go @@ -121,7 +121,7 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { c := NewContext(app, set, nil) - err := helpCommand.Action.(func(*Context) error)(c) + err := helpCommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil") @@ -149,7 +149,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { c := NewContext(app, set, nil) - err := helpSubcommand.Action.(func(*Context) error)(c) + err := helpSubcommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil")