diff --git a/README.md b/README.md index d9371cf..214e543 100644 --- a/README.md +++ b/README.md @@ -255,8 +255,8 @@ Initialization must also occur for these flags. Below is an example initializing command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) ``` -The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context. -It will then use that file name to initialize the yaml input source for any flags that are defined on that command. +The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context. +It will then use that file name to initialize the yaml input source for any flags that are defined on that command. As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. Currently only YAML files are supported but developers can add support for other input sources by implementing the @@ -265,20 +265,20 @@ altsrc.InputSourceContext for their given sources. Here is a more complete sample of a command using YAML support: ``` go - command := &cli.Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(c *cli.Context) { - // Action to run - }, - Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, - } - command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - err := command.Run(c) + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + // Action to run + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + err := command.Run(c) ``` ### Subcommands @@ -339,19 +339,19 @@ E.g. ```go ... - app.Commands = []cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "template", - }, - { - Name: "remove", - Category: "template", - }, - } + app.Commands = []cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + } ... ``` @@ -368,6 +368,28 @@ COMMANDS: ... ``` +### Exit code + +It is your responsibility to call `os.Exit` with the exit code returned by +`app.Run`, e.g.: + +```go +package main + +import ( + "os" + "github.com/codegangsta/cli" +) + +func main() { + exitCode, err := cli.NewApp().Run(os.Args) + if err != nil { + log.Println(err) + } + os.Exit(exitCode) +} +``` + ### Bash Completion You can enable completion commands by setting the `EnableBashCompletion` diff --git a/altsrc/flag.go b/altsrc/flag.go index f13ffb4..36ffa57 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -38,28 +38,28 @@ func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSource // InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new // input source based on the func provided. If there is no error it will then apply the new input source to any flags // that are supported by the input source -func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) func(context *cli.Context) error { - return func(context *cli.Context) error { +func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFn { + return func(context *cli.Context) (int, error) { inputSource, err := createInputSource() if err != nil { - return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error()) + return cli.DefaultErrorExitCode, fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error()) } - return ApplyInputSourceValues(context, inputSource, flags) + return cli.DefaultSuccessExitCode, ApplyInputSourceValues(context, inputSource, flags) } } // InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new // input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is // no error it will then apply the new input source to any flags that are supported by the input source -func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) func(context *cli.Context) error { - return func(context *cli.Context) error { +func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFn { + return func(context *cli.Context) (int, error) { inputSource, err := createInputSource(context) if err != nil { - return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error()) + return cli.DefaultErrorExitCode, fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error()) } - return ApplyInputSourceValues(context, inputSource, flags) + return cli.DefaultSuccessExitCode, ApplyInputSourceValues(context, inputSource, flags) } } diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 275bc64..6909729 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -29,16 +29,17 @@ func TestCommandYamlFileTest(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { val := c.Int("test") expect(t, val, 15) + return 0 }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test"}), cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - err := command.Run(c) + _, err := command.Run(c) expect(t, err, nil) } @@ -61,9 +62,10 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { val := c.Int("test") expect(t, val, 10) + return 0 }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), @@ -71,7 +73,7 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - err := command.Run(c) + _, err := command.Run(c) expect(t, err, nil) } @@ -92,9 +94,10 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { val := c.Int("test") expect(t, val, 7) + return 0 }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test"}), @@ -102,7 +105,7 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - err := command.Run(c) + _, err := command.Run(c) expect(t, err, nil) } @@ -123,9 +126,10 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { val := c.Int("test") expect(t, val, 15) + return 0 }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), @@ -133,7 +137,7 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - err := command.Run(c) + _, err := command.Run(c) expect(t, err, nil) } @@ -157,16 +161,17 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { val := c.Int("test") expect(t, val, 11) + return 0 }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - err := command.Run(c) + _, err := command.Run(c) expect(t, err, nil) } diff --git a/app.go b/app.go index bd20a2d..267387c 100644 --- a/app.go +++ b/app.go @@ -10,6 +10,15 @@ import ( "time" ) +var ( + // DefaultSuccessExitCode is the default for use with os.Exit intended to + // indicate success + DefaultSuccessExitCode = 0 + // DefaultErrorExitCode is the default for use with os.Exit intended to + // indicate an error + DefaultErrorExitCode = 1 +) + // 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 { @@ -38,17 +47,17 @@ type App struct { // Populate on app startup, only gettable throught method Categories() categories CommandCategories // An action to execute when the bash-completion flag is set - BashComplete func(context *Context) + BashComplete BashCompleteFn // An action to execute before any subcommands are run, but after the context is ready // If a non-nil error is returned, no subcommands are run - Before func(context *Context) error + Before BeforeFn // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics - After func(context *Context) error + After AfterFn // The action to execute when no subcommands are specified - Action func(context *Context) + Action ActionFn // Execute this function if the proper command cannot be found - CommandNotFound func(context *Context, command string) + CommandNotFound CommandNotFoundFn // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. // This function is able to replace the original error messages. // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. @@ -93,7 +102,7 @@ func NewApp() *App { } // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination -func (a *App) Run(arguments []string) (err error) { +func (a *App) Run(arguments []string) (ec int, err error) { if a.Author != "" || a.Email != "" { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } @@ -139,52 +148,57 @@ func (a *App) Run(arguments []string) (err error) { if nerr != nil { fmt.Fprintln(a.Writer, nerr) ShowAppHelp(context) - return nerr + return DefaultErrorExitCode, nerr } if checkCompletions(context) { - return nil + return DefaultSuccessExitCode, nil } if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) - return err + if err != nil { + return DefaultErrorExitCode, err + } + return DefaultSuccessExitCode, err } else { fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowAppHelp(context) - return err + return DefaultErrorExitCode, err } } if !a.HideHelp && checkHelp(context) { ShowAppHelp(context) - return nil + return DefaultSuccessExitCode, nil } if !a.HideVersion && checkVersion(context) { ShowVersion(context) - return nil + return DefaultSuccessExitCode, nil } if a.After != nil { defer func() { - if afterErr := a.After(context); afterErr != nil { + afterEc, afterErr := a.After(context) + if afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) } else { err = afterErr } } + ec = afterEc }() } if a.Before != nil { - err = a.Before(context) + ec, err = a.Before(context) if err != nil { fmt.Fprintf(a.Writer, "%v\n\n", err) ShowAppHelp(context) - return err + return ec, err } } @@ -198,20 +212,19 @@ func (a *App) Run(arguments []string) (err error) { } // Run default Action - a.Action(context) - return nil + return a.Action(context), nil } // Another entry point to the cli app, takes care of passing arguments and error handling func (a *App) RunAndExitOnError() { - if err := a.Run(os.Args); err != nil { + if exitCode, err := a.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) - os.Exit(1) + os.Exit(exitCode) } } // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags -func (a *App) RunAsSubcommand(ctx *Context) (err error) { +func (a *App) RunAsSubcommand(ctx *Context) (ec int, err error) { // append help to commands if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { @@ -251,37 +264,40 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } else { ShowCommandHelp(ctx, context.Args().First()) } - return nerr + return DefaultErrorExitCode, nerr } if checkCompletions(context) { - return nil + return DefaultSuccessExitCode, nil } if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - return err + if err != nil { + return DefaultErrorExitCode, err + } + return DefaultSuccessExitCode, err } else { fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowSubcommandHelp(context) - return err + return DefaultErrorExitCode, err } } if len(a.Commands) > 0 { if checkSubcommandHelp(context) { - return nil + return DefaultSuccessExitCode, nil } } else { if checkCommandHelp(ctx, context.Args().First()) { - return nil + return DefaultSuccessExitCode, nil } } if a.After != nil { defer func() { - afterErr := a.After(context) + afterEc, afterErr := a.After(context) if afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) @@ -289,13 +305,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { err = afterErr } } + ec = afterEc }() } if a.Before != nil { - err := a.Before(context) + ec, err = a.Before(context) if err != nil { - return err + return ec, err } } @@ -309,9 +326,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } // Run default Action - a.Action(context) - - return nil + return a.Action(context), nil } // Returns the named command on App. Returns nil if the command does not exist diff --git a/app_test.go b/app_test.go index ebf26c7..d0f06f0 100644 --- a/app_test.go +++ b/app_test.go @@ -22,8 +22,9 @@ func ExampleApp_Run() { app.Flags = []Flag{ StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Action = func(c *Context) { + app.Action = func(c *Context) int { fmt.Printf("Hello %v\n", c.String("name")) + return 0 } app.UsageText = "app [first_arg] [second_arg]" app.Author = "Harrison" @@ -58,8 +59,9 @@ func ExampleApp_Run_subcommand() { Usage: "Name of the person to greet", }, }, - Action: func(c *Context) { + Action: func(c *Context) int { fmt.Println("Hello,", c.String("name")) + return 0 }, }, }, @@ -86,8 +88,9 @@ func ExampleApp_Run_help() { Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *Context) { + Action: func(c *Context) int { fmt.Printf("i like to describe things") + return 0 }, }, } @@ -116,15 +119,17 @@ func ExampleApp_Run_bashComplete() { Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *Context) { + Action: func(c *Context) int { fmt.Printf("i like to describe things") + return 0 }, }, { Name: "next", Usage: "next example", Description: "more stuff to see when generating bash completion", - Action: func(c *Context) { + Action: func(c *Context) int { fmt.Printf("the next example") + return 0 }, }, } @@ -142,14 +147,17 @@ func TestApp_Run(t *testing.T) { s := "" app := NewApp() - app.Action = func(c *Context) { + app.Action = func(c *Context) int { s = s + c.Args().First() + return 0 } - err := app.Run([]string{"command", "foo"}) + ec, err := app.Run([]string{"command", "foo"}) expect(t, err, nil) - err = app.Run([]string{"command", "bar"}) + expect(t, ec, 0) + ec, err = app.Run([]string{"command", "bar"}) expect(t, err, nil) + expect(t, ec, 0) expect(t, s, "foobar") } @@ -188,9 +196,10 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { Flags: []Flag{ StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *Context) { + Action: func(c *Context) int { parsedOption = c.String("option") firstArg = c.Args().First() + return 0 }, } app.Commands = []Command{command} @@ -208,8 +217,9 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { a.Commands = []Command{ { Name: "foo", - Action: func(c *Context) { + Action: func(c *Context) int { context = c + return 0 }, Flags: []Flag{ StringFlag{ @@ -218,7 +228,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { Usage: "language for the greeting", }, }, - Before: func(_ *Context) error { return nil }, + Before: func(_ *Context) (int, error) { return 0, nil }, }, } a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) @@ -237,9 +247,10 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { Flags: []Flag{ StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *Context) { + Action: func(c *Context) int { parsedOption = c.String("option") args = c.Args() + return 0 }, } app.Commands = []Command{command} @@ -258,8 +269,9 @@ func TestApp_CommandWithDash(t *testing.T) { app := NewApp() command := Command{ Name: "cmd", - Action: func(c *Context) { + Action: func(c *Context) int { args = c.Args() + return 0 }, } app.Commands = []Command{command} @@ -276,8 +288,9 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { app := NewApp() command := Command{ Name: "cmd", - Action: func(c *Context) { + Action: func(c *Context) int { args = c.Args() + return 0 }, } app.Commands = []Command{command} @@ -296,8 +309,9 @@ func TestApp_Float64Flag(t *testing.T) { app.Flags = []Flag{ Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, } - app.Action = func(c *Context) { + app.Action = func(c *Context) int { meters = c.Float64("height") + return 0 } app.Run([]string{"", "--height", "1.93"}) @@ -316,11 +330,12 @@ func TestApp_ParseSliceFlags(t *testing.T) { IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"}, StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"}, }, - Action: func(c *Context) { + Action: func(c *Context) int { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") parsedOption = c.String("option") firstArg = c.Args().First() + return 0 }, } app.Commands = []Command{command} @@ -373,9 +388,10 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { IntSliceFlag{Name: "a", Usage: "set numbers"}, StringSliceFlag{Name: "str", Usage: "set strings"}, }, - Action: func(c *Context) { + Action: func(c *Context) int { parsedIntSlice = c.IntSlice("a") parsedStringSlice = c.StringSlice("str") + return 0 }, } app.Commands = []Command{command} @@ -427,7 +443,7 @@ func TestApp_SetStdout(t *testing.T) { app.Name = "test" app.Writer = w - err := app.Run([]string{"help"}) + _, err := app.Run([]string{"help"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -445,21 +461,22 @@ func TestApp_BeforeFunc(t *testing.T) { app := NewApp() - app.Before = func(c *Context) error { + app.Before = func(c *Context) (int, error) { beforeRun = true s := c.String("opt") if s == "fail" { - return beforeError + return DefaultErrorExitCode, beforeError } - return nil + return DefaultSuccessExitCode, nil } app.Commands = []Command{ Command{ Name: "sub", - Action: func(c *Context) { + Action: func(c *Context) int { subcommandRun = true + return DefaultSuccessExitCode }, }, } @@ -469,7 +486,7 @@ func TestApp_BeforeFunc(t *testing.T) { } // run with the Before() func succeeding - err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + ec, err := app.Run([]string{"command", "--opt", "succeed", "sub"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -483,11 +500,15 @@ func TestApp_BeforeFunc(t *testing.T) { t.Errorf("Subcommand not executed when expected") } + if ec != DefaultSuccessExitCode { + t.Errorf("Expected exit code to be %d but got %d", DefaultSuccessExitCode, ec) + } + // reset beforeRun, subcommandRun = false, false // run with the Before() func failing - err = app.Run([]string{"command", "--opt", "fail", "sub"}) + ec, err = app.Run([]string{"command", "--opt", "fail", "sub"}) // should be the same error produced by the Before func if err != beforeError { @@ -502,6 +523,9 @@ func TestApp_BeforeFunc(t *testing.T) { t.Errorf("Subcommand executed when NOT expected") } + if ec != DefaultErrorExitCode { + t.Errorf("Expected exit code to be %d but got %d", DefaultErrorExitCode, ec) + } } func TestApp_AfterFunc(t *testing.T) { @@ -511,21 +535,22 @@ func TestApp_AfterFunc(t *testing.T) { app := NewApp() - app.After = func(c *Context) error { + app.After = func(c *Context) (int, error) { afterRun = true s := c.String("opt") if s == "fail" { - return afterError + return DefaultErrorExitCode, afterError } - return nil + return DefaultSuccessExitCode, nil } app.Commands = []Command{ Command{ Name: "sub", - Action: func(c *Context) { + Action: func(c *Context) int { subcommandRun = true + return DefaultSuccessExitCode }, }, } @@ -535,7 +560,7 @@ func TestApp_AfterFunc(t *testing.T) { } // run with the After() func succeeding - err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + ec, err := app.Run([]string{"command", "--opt", "succeed", "sub"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -549,11 +574,15 @@ func TestApp_AfterFunc(t *testing.T) { t.Errorf("Subcommand not executed when expected") } + if ec != DefaultSuccessExitCode { + t.Errorf("Expected exit code to be %d but got %d", DefaultSuccessExitCode, ec) + } + // reset afterRun, subcommandRun = false, false // run with the Before() func failing - err = app.Run([]string{"command", "--opt", "fail", "sub"}) + ec, err = app.Run([]string{"command", "--opt", "fail", "sub"}) // should be the same error produced by the Before func if err != afterError { @@ -567,6 +596,10 @@ func TestApp_AfterFunc(t *testing.T) { if subcommandRun == false { t.Errorf("Subcommand not executed when expected") } + + if ec != DefaultErrorExitCode { + t.Errorf("Expected exit code to be %d but got %d", DefaultErrorExitCode, ec) + } } func TestAppNoHelpFlag(t *testing.T) { @@ -579,7 +612,7 @@ func TestAppNoHelpFlag(t *testing.T) { app := NewApp() app.Writer = ioutil.Discard - err := app.Run([]string{"test", "-h"}) + _, err := app.Run([]string{"test", "-h"}) if err != flag.ErrHelp { t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) @@ -636,8 +669,9 @@ func TestAppCommandNotFound(t *testing.T) { app.Commands = []Command{ Command{ Name: "bar", - Action: func(c *Context) { + Action: func(c *Context) int { subcommandRun = true + return 0 }, }, } @@ -655,9 +689,10 @@ func TestGlobalFlag(t *testing.T) { app.Flags = []Flag{ StringFlag{Name: "global, g", Usage: "global"}, } - app.Action = func(c *Context) { + app.Action = func(c *Context) int { globalFlag = c.GlobalString("global") globalFlagSet = c.GlobalIsSet("global") + return 0 } app.Run([]string{"command", "-g", "foo"}) expect(t, globalFlag, "foo") @@ -683,13 +718,14 @@ func TestGlobalFlagsInSubcommands(t *testing.T) { Subcommands: []Command{ { Name: "bar", - Action: func(c *Context) { + Action: func(c *Context) int { if c.GlobalBool("debug") { subcommandRun = true } if c.GlobalBool("parent") { parentFlag = true } + return 0 }, }, }, @@ -731,7 +767,7 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } app.Commands = []Command{cmd} - err := app.Run(flagSet) + _, err := app.Run(flagSet) if err != nil { t.Error(err) @@ -772,7 +808,7 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } app.Commands = []Command{cmd} - err := app.Run([]string{"command", "foo", "bar", "--help"}) + _, err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { t.Error(err) } @@ -803,7 +839,7 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { } app.Commands = []Command{cmd} - err := app.Run([]string{"command", "foo", "bar", "--help"}) + _, err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { t.Error(err) } @@ -834,7 +870,7 @@ func TestApp_Run_CommandHelpName(t *testing.T) { } app.Commands = []Command{cmd} - err := app.Run([]string{"command", "foo", "bar", "--help"}) + _, err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { t.Error(err) } @@ -865,7 +901,7 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { } app.Commands = []Command{cmd} - err := app.Run([]string{"command", "foo", "--help"}) + _, err := app.Run([]string{"command", "foo", "--help"}) if err != nil { t.Error(err) } @@ -891,11 +927,12 @@ func TestApp_Run_Help(t *testing.T) { app.Name = "boom" app.Usage = "make an explosive entrance" app.Writer = buf - app.Action = func(c *Context) { + app.Action = func(c *Context) int { buf.WriteString("boom I say!") + return 0 } - err := app.Run(args) + _, err := app.Run(args) if err != nil { t.Error(err) } @@ -922,11 +959,12 @@ func TestApp_Run_Version(t *testing.T) { app.Usage = "make an explosive entrance" app.Version = "0.1.0" app.Writer = buf - app.Action = func(c *Context) { + app.Action = func(c *Context) int { buf.WriteString("boom I say!") + return 0 } - err := app.Run(args) + _, err := app.Run(args) if err != nil { t.Error(err) } @@ -991,11 +1029,11 @@ func TestApp_Run_Categories(t *testing.T) { func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() - app.Action = func(c *Context) {} - app.Before = func(c *Context) error { return fmt.Errorf("before error") } - app.After = func(c *Context) error { return fmt.Errorf("after error") } + app.Action = func(c *Context) int { return 0 } + app.Before = func(c *Context) (int, error) { return 1, fmt.Errorf("before error") } + app.After = func(c *Context) (int, error) { return 2, fmt.Errorf("after error") } - err := app.Run([]string{"foo"}) + ec, err := app.Run([]string{"foo"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } @@ -1006,6 +1044,10 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { if !strings.Contains(err.Error(), "after error") { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } + + if ec != 2 { + t.Errorf("Expected exit code to be %d but got %d", 2, ec) + } } func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { @@ -1018,12 +1060,12 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { }, }, Name: "bar", - Before: func(c *Context) error { return fmt.Errorf("before error") }, - After: func(c *Context) error { return fmt.Errorf("after error") }, + Before: func(c *Context) (int, error) { return 1, fmt.Errorf("before error") }, + After: func(c *Context) (int, error) { return 2, fmt.Errorf("after error") }, }, } - err := app.Run([]string{"foo", "bar"}) + ec, err := app.Run([]string{"foo", "bar"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } @@ -1034,6 +1076,10 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { if !strings.Contains(err.Error(), "after error") { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } + + if ec != 2 { + t.Errorf("Expected exit code to be %d but got %d", 2, ec) + } } func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { @@ -1056,7 +1102,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { }, } - err := app.Run([]string{"foo", "--flag=wrong"}) + _, err := app.Run([]string{"foo", "--flag=wrong"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } @@ -1086,7 +1132,7 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { }, } - err := app.Run([]string{"foo", "--flag=wrong", "bar"}) + _, err := app.Run([]string{"foo", "--flag=wrong", "bar"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } diff --git a/command.go b/command.go index 1a05b54..b9db123 100644 --- a/command.go +++ b/command.go @@ -26,15 +26,15 @@ type Command struct { // The category the command is part of Category string // The function to call when checking for bash command completions - BashComplete func(context *Context) + BashComplete BashCompleteFn // An action to execute before any sub-subcommands are run, but after the context is ready // If a non-nil error is returned, no sub-subcommands are run - Before func(context *Context) error - // An action to execute after any subcommands are run, but before the subcommand has finished + Before BeforeFn + // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics - After func(context *Context) error + After AfterFn // The function to call when this command is invoked - Action func(context *Context) + Action ActionFn // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. // This function is able to replace the original error messages. // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. @@ -65,7 +65,7 @@ func (c Command) FullName() string { type Commands []Command // Invokes the command given the context, parses ctx.Args() to generate command-specific flags -func (c Command) Run(ctx *Context) (err error) { +func (c Command) Run(ctx *Context) (ec int, err error) { if len(c.Subcommands) > 0 { return c.startApp(ctx) } @@ -126,12 +126,15 @@ func (c Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { err := c.OnUsageError(ctx, err) - return err + if err != nil { + return DefaultErrorExitCode, err + } + return DefaultSuccessExitCode, err } else { fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - return err + return DefaultErrorExitCode, err } } @@ -140,44 +143,47 @@ func (c Command) Run(ctx *Context) (err error) { fmt.Fprintln(ctx.App.Writer, nerr) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - return nerr + return DefaultErrorExitCode, nerr } + context := NewContext(ctx.App, set, ctx) if checkCommandCompletions(context, c.Name) { - return nil + return DefaultSuccessExitCode, nil } if checkCommandHelp(context, c.Name) { - return nil + return DefaultSuccessExitCode, nil } if c.After != nil { defer func() { - afterErr := c.After(context) + afterEc, afterErr := c.After(context) if afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) } else { err = afterErr } + + ec = afterEc } }() } if c.Before != nil { - err := c.Before(context) + ec, err = c.Before(context) if err != nil { fmt.Fprintln(ctx.App.Writer, err) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - return err + return ec, err } } context.Command = c - c.Action(context) - return nil + ec = c.Action(context) + return ec, err } func (c Command) Names() []string { @@ -200,7 +206,7 @@ func (c Command) HasName(name string) bool { return false } -func (c Command) startApp(ctx *Context) error { +func (c Command) startApp(ctx *Context) (int, error) { app := NewApp() // set the name and usage diff --git a/command_test.go b/command_test.go index 827da1d..96b20de 100644 --- a/command_test.go +++ b/command_test.go @@ -34,12 +34,12 @@ func TestCommandFlagParsing(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(_ *Context) {}, + Action: func(_ *Context) int { return 0 }, } command.SkipFlagParsing = c.skipFlagParsing - err := command.Run(context) + _, err := command.Run(context) expect(t, err, c.expectedErr) expect(t, []string(context.Args()), c.testArgs) @@ -50,13 +50,17 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Commands = []Command{ Command{ - Name: "bar", - Before: func(c *Context) error { return fmt.Errorf("before error") }, - After: func(c *Context) error { return fmt.Errorf("after error") }, + Name: "bar", + Before: func(c *Context) (int, error) { + return 1, fmt.Errorf("before error") + }, + After: func(c *Context) (int, error) { + return 1, fmt.Errorf("after error") + }, }, } - err := app.Run([]string{"foo", "bar"}) + _, err := app.Run([]string{"foo", "bar"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } @@ -73,7 +77,7 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() app.Commands = []Command{ Command{ - Name: "bar", + Name: "bar", Flags: []Flag{ IntFlag{Name: "flag"}, }, @@ -86,7 +90,7 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { }, } - err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + _, err := app.Run([]string{"foo", "bar", "--flag=wrong"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } diff --git a/flag_test.go b/flag_test.go index 3caa70a..a68e543 100644 --- a/flag_test.go +++ b/flag_test.go @@ -4,9 +4,9 @@ import ( "fmt" "os" "reflect" + "runtime" "strings" "testing" - "runtime" ) var boolFlagTests = []struct { @@ -64,7 +64,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_FOO%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -120,7 +120,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_QWWX%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%q does not end with" + expectedSuffix, output) + t.Errorf("%q does not end with"+expectedSuffix, output) } } } @@ -157,7 +157,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAR%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -194,7 +194,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAR%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -238,7 +238,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_SMURF%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%q does not end with" + expectedSuffix, output) + t.Errorf("%q does not end with"+expectedSuffix, output) } } } @@ -275,7 +275,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAZ%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -313,7 +313,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_ZAP%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -323,13 +323,14 @@ func TestParseMultiString(t *testing.T) { Flags: []Flag{ StringFlag{Name: "serve, s"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.String("serve") != "10" { t.Errorf("main name not set") } if ctx.String("s") != "10" { t.Errorf("short name not set") } + return 0 }, }).Run([]string{"run", "-s", "10"}) } @@ -343,10 +344,11 @@ func TestParseDestinationString(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if dest != "10" { t.Errorf("expected destination String 10") } + return 0 }, } a.Run([]string{"run", "--dest", "10"}) @@ -359,13 +361,14 @@ func TestParseMultiStringFromEnv(t *testing.T) { Flags: []Flag{ StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.String("count") != "20" { t.Errorf("main name not set") } if ctx.String("c") != "20" { t.Errorf("short name not set") } + return 0 }, }).Run([]string{"run"}) } @@ -377,13 +380,14 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { Flags: []Flag{ StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.String("count") != "20" { t.Errorf("main name not set") } if ctx.String("c") != "20" { t.Errorf("short name not set") } + return 0 }, }).Run([]string{"run"}) } @@ -393,13 +397,14 @@ func TestParseMultiStringSlice(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "serve, s", Value: &StringSlice{}}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { t.Errorf("short name not set") } + return 0 }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -412,13 +417,14 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { 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 0 }, }).Run([]string{"run"}) } @@ -431,13 +437,14 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { 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 0 }, }).Run([]string{"run"}) } @@ -447,13 +454,14 @@ func TestParseMultiInt(t *testing.T) { Flags: []Flag{ IntFlag{Name: "serve, s"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Int("serve") != 10 { t.Errorf("main name not set") } if ctx.Int("s") != 10 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "-s", "10"}) @@ -468,10 +476,11 @@ func TestParseDestinationInt(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if dest != 10 { t.Errorf("expected destination Int 10") } + return 0 }, } a.Run([]string{"run", "--dest", "10"}) @@ -484,13 +493,14 @@ func TestParseMultiIntFromEnv(t *testing.T) { Flags: []Flag{ IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } if ctx.Int("t") != 10 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run"}) @@ -503,13 +513,14 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { Flags: []Flag{ IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } if ctx.Int("t") != 10 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run"}) @@ -520,13 +531,14 @@ func TestParseMultiIntSlice(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "serve, s", Value: &IntSlice{}}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { 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 0 }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -539,13 +551,14 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { 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 0 }, }).Run([]string{"run"}) } @@ -558,13 +571,14 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { 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 0 }, }).Run([]string{"run"}) } @@ -574,13 +588,14 @@ func TestParseMultiFloat64(t *testing.T) { Flags: []Flag{ Float64Flag{Name: "serve, s"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Float64("serve") != 10.2 { t.Errorf("main name not set") } if ctx.Float64("s") != 10.2 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "-s", "10.2"}) @@ -595,10 +610,11 @@ func TestParseDestinationFloat64(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if dest != 10.2 { t.Errorf("expected destination Float64 10.2") } + return 0 }, } a.Run([]string{"run", "--dest", "10.2"}) @@ -611,13 +627,14 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { Flags: []Flag{ Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } if ctx.Float64("t") != 15.5 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run"}) @@ -630,13 +647,14 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { Flags: []Flag{ Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } if ctx.Float64("t") != 15.5 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run"}) @@ -647,13 +665,14 @@ func TestParseMultiBool(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "serve, s"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Bool("serve") != true { t.Errorf("main name not set") } if ctx.Bool("s") != true { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "--serve"}) @@ -668,10 +687,11 @@ func TestParseDestinationBool(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if dest != true { t.Errorf("expected destination Bool true") } + return 0 }, } a.Run([]string{"run", "--dest"}) @@ -684,13 +704,14 @@ func TestParseMultiBoolFromEnv(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Bool("debug") != true { t.Errorf("main name not set from env") } if ctx.Bool("d") != true { t.Errorf("short name not set from env") } + return 0 }, } a.Run([]string{"run"}) @@ -703,13 +724,14 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Bool("debug") != true { t.Errorf("main name not set from env") } if ctx.Bool("d") != true { t.Errorf("short name not set from env") } + return 0 }, } a.Run([]string{"run"}) @@ -720,13 +742,14 @@ func TestParseMultiBoolT(t *testing.T) { Flags: []Flag{ BoolTFlag{Name: "serve, s"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.BoolT("serve") != true { t.Errorf("main name not set") } if ctx.BoolT("s") != true { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "--serve"}) @@ -741,10 +764,11 @@ func TestParseDestinationBoolT(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if dest != true { t.Errorf("expected destination BoolT true") } + return 0 }, } a.Run([]string{"run", "--dest"}) @@ -757,13 +781,14 @@ func TestParseMultiBoolTFromEnv(t *testing.T) { Flags: []Flag{ BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.BoolT("debug") != false { t.Errorf("main name not set from env") } if ctx.BoolT("d") != false { t.Errorf("short name not set from env") } + return 0 }, } a.Run([]string{"run"}) @@ -776,13 +801,14 @@ func TestParseMultiBoolTFromEnvCascade(t *testing.T) { Flags: []Flag{ BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.BoolT("debug") != false { t.Errorf("main name not set from env") } if ctx.BoolT("d") != false { t.Errorf("short name not set from env") } + return 0 }, } a.Run([]string{"run"}) @@ -811,13 +837,14 @@ func TestParseGeneric(t *testing.T) { Flags: []Flag{ GenericFlag{Name: "serve, s", Value: &Parser{}}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "-s", "10,20"}) @@ -830,13 +857,14 @@ func TestParseGenericFromEnv(t *testing.T) { Flags: []Flag{ GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) { t.Errorf("short name not set from env") } + return 0 }, } a.Run([]string{"run"}) @@ -849,10 +877,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) { Flags: []Flag{ GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { t.Errorf("value not set from env") } + return 0 }, } a.Run([]string{"run"}) diff --git a/funcs.go b/funcs.go new file mode 100644 index 0000000..48909c1 --- /dev/null +++ b/funcs.go @@ -0,0 +1,18 @@ +package cli + +// An action to execute when the bash-completion flag is set +type BashCompleteFn func(*Context) + +// An action to execute before any subcommands are run, but after the context is ready +// If a non-nil error is returned, no subcommands are run +type BeforeFn func(*Context) (int, error) + +// An action to execute after any subcommands are run, but after the subcommand has finished +// It is run even if Action() panics +type AfterFn func(*Context) (int, error) + +// The action to execute when no subcommands are specified +type ActionFn func(*Context) int + +// Execute this function if the proper command cannot be found +type CommandNotFoundFn func(*Context, string) diff --git a/help.go b/help.go index adf157d..b6a190d 100644 --- a/help.go +++ b/help.go @@ -78,13 +78,14 @@ var helpCommand = Command{ Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *Context) { + Action: func(c *Context) int { args := c.Args() if args.Present() { ShowCommandHelp(c, args.First()) } else { ShowAppHelp(c) } + return 0 }, } @@ -93,13 +94,14 @@ var helpSubcommand = Command{ Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *Context) { + Action: func(c *Context) int { args := c.Args() if args.Present() { ShowCommandHelp(c, args.First()) } else { ShowSubcommandHelp(c) } + return 0 }, } diff --git a/help_test.go b/help_test.go index 0821f48..260800d 100644 --- a/help_test.go +++ b/help_test.go @@ -66,10 +66,11 @@ func Test_Help_Custom_Flags(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "foo, h"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Bool("h") != true { t.Errorf("custom help flag not set") } + return 0 }, } output := new(bytes.Buffer) @@ -95,10 +96,11 @@ func Test_Version_Custom_Flags(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "foo, v"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) int { if ctx.Bool("v") != true { t.Errorf("custom version flag not set") } + return 0 }, } output := new(bytes.Buffer)