diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6deafc2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 diff --git a/.travis.yml b/.travis.yml index 94836d7..890e185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,9 @@ cache: - node_modules go: -- 1.2.x -- 1.3.x -- 1.4.2 -- 1.5.x - 1.6.x - 1.7.x +- 1.8.x - master matrix: @@ -23,10 +20,12 @@ matrix: os: osx - go: 1.7.x os: osx + - go: 1.8.x + os: osx before_script: - go get github.com/urfave/gfmrun/... || true -- go get golang.org/x/tools/... || true +- go get golang.org/x/tools/cmd/goimports - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi diff --git a/README.md b/README.md index bb5f61e..2bbbd8e 100644 --- a/README.md +++ b/README.md @@ -455,13 +455,13 @@ error. Flags for the application and commands are shown in the order they are defined. However, it's possible to sort them from outside this library by using `FlagsByName` -with `sort`. +or `CommandsByName` with `sort`. For example this: ``` go package main @@ -488,7 +488,27 @@ func main() { }, } + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + } + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) app.Run(os.Args) } @@ -940,16 +960,13 @@ SUPPORT: support@awesometown.example.com cli.AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command -[command options]{{end}} {{if -.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{if len .Authors}} -AUTHOR(S): +AUTHOR: {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t" -}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range .VisibleFlags}}{{.}} {{end}}{{end}}{{if .Copyright }} diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 9e9c96d..cd18294 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -59,7 +59,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), FlagName: "test", - MapValue: []string{"hello", "world"}, + MapValue: []interface{}{"hello", "world"}, }) expect(t, c.StringSlice("test"), []string{"hello", "world"}) } @@ -68,7 +68,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), FlagName: "test", - MapValue: []string{"hello", "world"}, + MapValue: []interface{}{"hello", "world"}, ContextValueString: "ohno", }) expect(t, c.StringSlice("test"), []string{"ohno"}) @@ -78,7 +78,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), FlagName: "test", - MapValue: []string{"hello", "world"}, + MapValue: []interface{}{"hello", "world"}, EnvVarName: "TEST", EnvVarValue: "oh,no", }) @@ -89,7 +89,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), FlagName: "test", - MapValue: []int{1, 2}, + MapValue: []interface{}{1, 2}, }) expect(t, c.IntSlice("test"), []int{1, 2}) } @@ -98,7 +98,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), FlagName: "test", - MapValue: []int{1, 2}, + MapValue: []interface{}{1, 2}, ContextValueString: "3", }) expect(t, c.IntSlice("test"), []int{3}) @@ -108,7 +108,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), FlagName: "test", - MapValue: []int{1, 2}, + MapValue: []interface{}{1, 2}, EnvVarName: "TEST", EnvVarValue: "3,4", }) diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index b720995..b3169e0 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -130,45 +130,59 @@ func (fsm *MapInputSource) String(name string) (string, error) { // StringSlice returns an []string from the map if it exists otherwise returns nil func (fsm *MapInputSource) StringSlice(name string) ([]string, error) { otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.([]string) - if !isType { - return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue) + if !exists { + otherGenericValue, exists = nestedVal(name, fsm.valueMap) + if !exists { + return nil, nil } - return otherValue, nil - } - nestedGenericValue, exists := nestedVal(name, fsm.valueMap) - if exists { - otherValue, isType := nestedGenericValue.([]string) - if !isType { - return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue) - } - return otherValue, nil } - return nil, nil + otherValue, isType := otherGenericValue.([]interface{}) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue) + } + + var stringSlice = make([]string, 0, len(otherValue)) + for i, v := range otherValue { + stringValue, isType := v.(string) + + if !isType { + return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "string", v) + } + + stringSlice = append(stringSlice, stringValue) + } + + return stringSlice, nil } // IntSlice returns an []int from the map if it exists otherwise returns nil func (fsm *MapInputSource) IntSlice(name string) ([]int, error) { otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.([]int) - if !isType { - return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue) + if !exists { + otherGenericValue, exists = nestedVal(name, fsm.valueMap) + if !exists { + return nil, nil } - return otherValue, nil - } - nestedGenericValue, exists := nestedVal(name, fsm.valueMap) - if exists { - otherValue, isType := nestedGenericValue.([]int) - if !isType { - return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue) - } - return otherValue, nil } - return nil, nil + otherValue, isType := otherGenericValue.([]interface{}) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue) + } + + var intSlice = make([]int, 0, len(otherValue)) + for i, v := range otherValue { + intValue, isType := v.(int) + + if !isType { + return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "int", v) + } + + intSlice = append(intSlice, intValue) + } + + return intSlice, nil } // Generic returns an cli.Generic from the map if it exists otherwise returns nil diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 39c124f..37870fc 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -57,8 +57,8 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { } else { return nil, err } - case reflect.Array: - fallthrough // [todo] - Support array type + case reflect.Array, reflect.Slice: + ret[key] = val.([]interface{}) default: return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind()) } diff --git a/command.go b/command.go index 2628fbf..40ebdb6 100644 --- a/command.go +++ b/command.go @@ -61,6 +61,20 @@ type Command struct { commandNamePath []string } +type CommandsByName []Command + +func (c CommandsByName) Len() int { + return len(c) +} + +func (c CommandsByName) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c CommandsByName) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + // FullName returns the full name of the command. // For subcommands this ensures that parent commands are part of the command path func (c Command) FullName() string { @@ -230,11 +244,9 @@ func (c Command) startApp(ctx *Context) error { app.HelpName = app.Name } - if c.Description != "" { - app.Usage = c.Description - } else { - app.Usage = c.Usage - } + app.Usage = c.Usage + app.Description = c.Description + app.ArgsUsage = c.ArgsUsage // set CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound @@ -250,6 +262,7 @@ func (c Command) startApp(ctx *Context) error { app.Author = ctx.App.Author app.Email = ctx.App.Email app.Writer = ctx.App.Writer + app.ErrWriter = ctx.App.ErrWriter app.categories = CommandCategories{} for _, command := range c.Subcommands { diff --git a/command_test.go b/command_test.go index 5e0e8de..5710184 100644 --- a/command_test.go +++ b/command_test.go @@ -153,3 +153,32 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { t.Errorf("Expect an intercepted error, but got \"%v\"", err) } } + +func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { + app := NewApp() + app.ErrWriter = ioutil.Discard + app.Commands = []Command{ + { + Name: "bar", + Usage: "this is for testing", + Subcommands: []Command{ + { + Name: "baz", + Usage: "this is for testing", + Action: func(c *Context) error { + if c.App.ErrWriter != ioutil.Discard { + return fmt.Errorf("ErrWriter not passed") + } + + return nil + }, + }, + }, + }, + } + + err := app.Run([]string{"foo", "bar", "baz"}) + if err != nil { + t.Fatal(err) + } +} diff --git a/errors.go b/errors.go index 0206ff4..562b295 100644 --- a/errors.go +++ b/errors.go @@ -74,7 +74,7 @@ func (ee *ExitError) ExitCode() int { // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if // so prints the error to stderr (if it is non-empty) and calls OsExiter with the // given exit code. If the given error is a MultiError, then this func is -// called on all members of the Errors slice. +// called on all members of the Errors slice and calls OsExiter with the last exit code. func HandleExitCoder(err error) { if err == nil { return @@ -93,18 +93,23 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.Errors { - HandleExitCoder(merr) - } + code := handleMultiError(multiErr) + OsExiter(code) return } +} - if err.Error() != "" { - if _, ok := err.(ErrorFormatter); ok { - fmt.Fprintf(ErrWriter, "%+v\n", err) +func handleMultiError(multiErr MultiError) int { + code := 1 + for _, merr := range multiErr.Errors { + if multiErr2, ok := merr.(MultiError); ok { + code = handleMultiError(multiErr2) } else { - fmt.Fprintln(ErrWriter, err) + fmt.Fprintln(ErrWriter, merr) + if exitErr, ok := merr.(ExitCoder); ok { + code = exitErr.ExitCode() + } } } - OsExiter(1) + return code } diff --git a/errors_test.go b/errors_test.go index 131bd38..9b609c5 100644 --- a/errors_test.go +++ b/errors_test.go @@ -12,8 +12,10 @@ func TestHandleExitCoder_nil(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() @@ -29,8 +31,10 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() @@ -46,66 +50,23 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() exitErr := NewExitError("galactic perimeter breach", 9) - err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) + exitErr2 := NewExitError("last ExitCoder", 11) + err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2) HandleExitCoder(err) - expect(t, exitCode, 9) + expect(t, exitCode, 11) expect(t, called, true) } -func TestHandleExitCoder_ErrorWithMessage(t *testing.T) { - exitCode := 0 - called := false - - OsExiter = func(rc int) { - exitCode = rc - called = true - } - ErrWriter = &bytes.Buffer{} - - defer func() { - OsExiter = fakeOsExiter - ErrWriter = fakeErrWriter - }() - - err := errors.New("gourd havens") - HandleExitCoder(err) - - expect(t, exitCode, 1) - expect(t, called, true) - expect(t, ErrWriter.(*bytes.Buffer).String(), "gourd havens\n") -} - -func TestHandleExitCoder_ErrorWithoutMessage(t *testing.T) { - exitCode := 0 - called := false - - OsExiter = func(rc int) { - exitCode = rc - called = true - } - ErrWriter = &bytes.Buffer{} - - defer func() { - OsExiter = fakeOsExiter - ErrWriter = fakeErrWriter - }() - - err := errors.New("") - HandleExitCoder(err) - - expect(t, exitCode, 1) - expect(t, called, true) - expect(t, ErrWriter.(*bytes.Buffer).String(), "") -} - // make a stub to not import pkg/errors type ErrorWithFormat struct { error @@ -123,7 +84,9 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { called := false OsExiter = func(rc int) { - called = true + if !called { + called = true + } } ErrWriter = &bytes.Buffer{} @@ -132,7 +95,7 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { ErrWriter = fakeErrWriter }() - err := NewErrorWithFormat("I am formatted") + err := NewExitError(NewErrorWithFormat("I am formatted"), 1) HandleExitCoder(err) expect(t, called, true) @@ -143,7 +106,9 @@ func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { called := false OsExiter = func(rc int) { - called = true + if !called { + called = true + } } ErrWriter = &bytes.Buffer{} diff --git a/flag_test.go b/flag_test.go index 0dd8654..1ccb639 100644 --- a/flag_test.go +++ b/flag_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "reflect" + "regexp" "runtime" "strings" "testing" @@ -31,57 +32,57 @@ func TestBoolFlagHelpOutput(t *testing.T) { func TestFlagsFromEnv(t *testing.T) { var flagTests = []struct { - input string - output interface{} - flag Flag - err error + input string + output interface{} + flag Flag + errRegexp string }{ - {"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"foobar", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Errorf(`could not parse foobar as bool value for flag debug: strconv.ParseBool: parsing "foobar": invalid syntax`)}, + {"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"foobar", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, - {"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"foobar", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Errorf(`could not parse foobar as bool value for flag debug: strconv.ParseBool: parsing "foobar": invalid syntax`)}, + {"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"foobar", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, - {"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, nil}, - {"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Errorf(`could not parse foobar as duration for flag time: time: invalid duration foobar`)}, + {"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, ""}, + {"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)}, - {"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"foobar", 0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as float64 value for flag seconds: strconv.ParseFloat: parsing "foobar": invalid syntax`)}, + {"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"foobar", 0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)}, - {"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as int value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, - {"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + {"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, + {"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, - {"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as int value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, - {"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + {"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, + {"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, - {"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2,2", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2,2 as int slice value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, - {"foobar", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + {"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2,2", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)}, + {"foobar", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)}, - {"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2,2", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2,2 as int64 slice value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, - {"foobar", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int64 slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + {"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2,2", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)}, + {"foobar", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)}, - {"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, nil}, + {"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, ""}, - {"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, nil}, + {"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, ""}, - {"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as uint value for flag seconds: strconv.ParseUint: parsing "1.2": invalid syntax`)}, - {"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)}, + {"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)}, + {"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)}, - {"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as uint64 value for flag seconds: strconv.ParseUint: parsing "1.2": invalid syntax`)}, - {"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint64 value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)}, + {"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)}, + {"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)}, - {"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, nil}, + {"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, ""}, } for _, test := range flagTests { @@ -98,8 +99,19 @@ func TestFlagsFromEnv(t *testing.T) { } err := a.Run([]string{"run"}) - if !reflect.DeepEqual(test.err, err) { - t.Errorf("expected error %s, got error %s", test.err, err) + + if test.errRegexp != "" { + if err == nil { + t.Errorf("expected error to match %s, got none", test.errRegexp) + } else { + if matched, _ := regexp.MatchString(test.errRegexp, err.Error()); !matched { + t.Errorf("expected error to match %s, got error %s", test.errRegexp, err) + } + } + } else { + if err != nil && test.errRegexp == "" { + t.Errorf("expected no error got %s", err) + } } } } diff --git a/help.go b/help.go index c8c1aee..d00e4da 100644 --- a/help.go +++ b/help.go @@ -64,7 +64,7 @@ OPTIONS: // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} USAGE: {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}