Merge remote-tracking branch 'origin/master' into v1

This commit is contained in:
Jesse Szwedko 2017-04-24 11:44:41 -07:00
commit f6e8084112
12 changed files with 217 additions and 161 deletions

2
.flake8 Normal file
View File

@ -0,0 +1,2 @@
[flake8]
max-line-length = 120

View File

@ -7,12 +7,9 @@ cache:
- node_modules - node_modules
go: go:
- 1.2.x
- 1.3.x
- 1.4.2
- 1.5.x
- 1.6.x - 1.6.x
- 1.7.x - 1.7.x
- 1.8.x
- master - master
matrix: matrix:
@ -23,10 +20,12 @@ matrix:
os: osx os: osx
- go: 1.7.x - go: 1.7.x
os: osx os: osx
- go: 1.8.x
os: osx
before_script: before_script:
- go get github.com/urfave/gfmrun/... || true - 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 - if [ ! -f node_modules/.bin/markdown-toc ] ; then
npm install markdown-toc ; npm install markdown-toc ;
fi fi

View File

@ -455,13 +455,13 @@ error.
Flags for the application and commands are shown in the order they are defined. 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` However, it's possible to sort them from outside this library by using `FlagsByName`
with `sort`. or `CommandsByName` with `sort`.
For example this: For example this:
<!-- { <!-- {
"args": ["&#45;&#45;help"], "args": ["&#45;&#45;help"],
"output": "Load configuration from FILE\n.*Language for the greeting.*" "output": "add a task to the list\n.*complete a task on the list\n.*\n\n.*\n.*Load configuration from FILE\n.*Language for the greeting.*"
} --> } -->
``` go ``` go
package main 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.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
app.Run(os.Args) app.Run(os.Args)
} }
@ -940,16 +960,13 @@ SUPPORT: support@awesometown.example.com
cli.AppHelpTemplate = `NAME: cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
[command options]{{end}} {{if
.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if len .Authors}} {{if len .Authors}}
AUTHOR(S): AUTHOR:
{{range .Authors}}{{ . }}{{end}} {{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}} {{end}}{{if .Commands}}
COMMANDS: COMMANDS:
{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t" {{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS: GLOBAL OPTIONS:
{{range .VisibleFlags}}{{.}} {{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{if .Copyright }} {{end}}{{end}}{{if .Copyright }}

View File

@ -59,7 +59,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []interface{}{"hello", "world"},
}) })
expect(t, c.StringSlice("test"), []string{"hello", "world"}) expect(t, c.StringSlice("test"), []string{"hello", "world"})
} }
@ -68,7 +68,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []interface{}{"hello", "world"},
ContextValueString: "ohno", ContextValueString: "ohno",
}) })
expect(t, c.StringSlice("test"), []string{"ohno"}) expect(t, c.StringSlice("test"), []string{"ohno"})
@ -78,7 +78,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []interface{}{"hello", "world"},
EnvVarName: "TEST", EnvVarName: "TEST",
EnvVarValue: "oh,no", EnvVarValue: "oh,no",
}) })
@ -89,7 +89,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []interface{}{1, 2},
}) })
expect(t, c.IntSlice("test"), []int{1, 2}) expect(t, c.IntSlice("test"), []int{1, 2})
} }
@ -98,7 +98,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []interface{}{1, 2},
ContextValueString: "3", ContextValueString: "3",
}) })
expect(t, c.IntSlice("test"), []int{3}) expect(t, c.IntSlice("test"), []int{3})
@ -108,7 +108,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []interface{}{1, 2},
EnvVarName: "TEST", EnvVarName: "TEST",
EnvVarValue: "3,4", EnvVarValue: "3,4",
}) })

View File

@ -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 // StringSlice returns an []string from the map if it exists otherwise returns nil
func (fsm *MapInputSource) StringSlice(name string) ([]string, error) { func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
otherGenericValue, exists := fsm.valueMap[name] otherGenericValue, exists := fsm.valueMap[name]
if exists { if !exists {
otherValue, isType := otherGenericValue.([]string) otherGenericValue, exists = nestedVal(name, fsm.valueMap)
if !isType { if !exists {
return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue) 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 // IntSlice returns an []int from the map if it exists otherwise returns nil
func (fsm *MapInputSource) IntSlice(name string) ([]int, error) { func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
otherGenericValue, exists := fsm.valueMap[name] otherGenericValue, exists := fsm.valueMap[name]
if exists { if !exists {
otherValue, isType := otherGenericValue.([]int) otherGenericValue, exists = nestedVal(name, fsm.valueMap)
if !isType { if !exists {
return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue) 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 // Generic returns an cli.Generic from the map if it exists otherwise returns nil

View File

@ -57,8 +57,8 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
} else { } else {
return nil, err return nil, err
} }
case reflect.Array: case reflect.Array, reflect.Slice:
fallthrough // [todo] - Support array type ret[key] = val.([]interface{})
default: default:
return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind()) return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind())
} }

View File

@ -61,6 +61,20 @@ type Command struct {
commandNamePath []string 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. // FullName returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path // For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string { func (c Command) FullName() string {
@ -230,11 +244,9 @@ func (c Command) startApp(ctx *Context) error {
app.HelpName = app.Name 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 // set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound
@ -250,6 +262,7 @@ func (c Command) startApp(ctx *Context) error {
app.Author = ctx.App.Author app.Author = ctx.App.Author
app.Email = ctx.App.Email app.Email = ctx.App.Email
app.Writer = ctx.App.Writer app.Writer = ctx.App.Writer
app.ErrWriter = ctx.App.ErrWriter
app.categories = CommandCategories{} app.categories = CommandCategories{}
for _, command := range c.Subcommands { for _, command := range c.Subcommands {

View File

@ -153,3 +153,32 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
t.Errorf("Expect an intercepted error, but got \"%v\"", err) 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)
}
}

View File

@ -74,7 +74,7 @@ func (ee *ExitError) ExitCode() int {
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if // 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 // 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 // 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) { func HandleExitCoder(err error) {
if err == nil { if err == nil {
return return
@ -93,18 +93,23 @@ func HandleExitCoder(err error) {
} }
if multiErr, ok := err.(MultiError); ok { if multiErr, ok := err.(MultiError); ok {
for _, merr := range multiErr.Errors { code := handleMultiError(multiErr)
HandleExitCoder(merr) OsExiter(code)
}
return return
} }
}
if err.Error() != "" {
if _, ok := err.(ErrorFormatter); ok { func handleMultiError(multiErr MultiError) int {
fmt.Fprintf(ErrWriter, "%+v\n", err) code := 1
} else { for _, merr := range multiErr.Errors {
fmt.Fprintln(ErrWriter, err) if multiErr2, ok := merr.(MultiError); ok {
} code = handleMultiError(multiErr2)
} } else {
OsExiter(1) fmt.Fprintln(ErrWriter, merr)
if exitErr, ok := merr.(ExitCoder); ok {
code = exitErr.ExitCode()
}
}
}
return code
} }

View File

@ -12,9 +12,11 @@ func TestHandleExitCoder_nil(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
if !called {
exitCode = rc exitCode = rc
called = true called = true
} }
}
defer func() { OsExiter = fakeOsExiter }() defer func() { OsExiter = fakeOsExiter }()
@ -29,9 +31,11 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
if !called {
exitCode = rc exitCode = rc
called = true called = true
} }
}
defer func() { OsExiter = fakeOsExiter }() defer func() { OsExiter = fakeOsExiter }()
@ -46,66 +50,23 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
if !called {
exitCode = rc exitCode = rc
called = true called = true
} }
}
defer func() { OsExiter = fakeOsExiter }() defer func() { OsExiter = fakeOsExiter }()
exitErr := NewExitError("galactic perimeter breach", 9) 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) HandleExitCoder(err)
expect(t, exitCode, 9) expect(t, exitCode, 11)
expect(t, called, true) 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 // make a stub to not import pkg/errors
type ErrorWithFormat struct { type ErrorWithFormat struct {
error error
@ -123,8 +84,10 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
if !called {
called = true called = true
} }
}
ErrWriter = &bytes.Buffer{} ErrWriter = &bytes.Buffer{}
defer func() { defer func() {
@ -132,7 +95,7 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) {
ErrWriter = fakeErrWriter ErrWriter = fakeErrWriter
}() }()
err := NewErrorWithFormat("I am formatted") err := NewExitError(NewErrorWithFormat("I am formatted"), 1)
HandleExitCoder(err) HandleExitCoder(err)
expect(t, called, true) expect(t, called, true)
@ -143,8 +106,10 @@ func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
if !called {
called = true called = true
} }
}
ErrWriter = &bytes.Buffer{} ErrWriter = &bytes.Buffer{}
defer func() { OsExiter = fakeOsExiter }() defer func() { OsExiter = fakeOsExiter }()

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"reflect" "reflect"
"regexp"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@ -34,54 +35,54 @@ func TestFlagsFromEnv(t *testing.T) {
input string input string
output interface{} output interface{}
flag Flag flag Flag
err error errRegexp string
}{ }{
{"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, {"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, {"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, {"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"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`)}, {"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}, {"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, {"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, {"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"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`)}, {"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}, {"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, ""},
{"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Errorf(`could not parse foobar as duration for flag time: time: invalid duration foobar`)}, {"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.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, {"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"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`)}, {"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", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"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`)}, {"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.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, {"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", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"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`)}, {"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.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, {"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", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"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`)}, {"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.Errorf(`could not parse foobar as int slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, {"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", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"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`)}, {"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.Errorf(`could not parse foobar as int64 slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, {"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", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"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`)}, {"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.Errorf(`could not parse foobar as uint value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)}, {"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", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"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`)}, {"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.Errorf(`could not parse foobar as uint64 value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)}, {"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 { for _, test := range flagTests {
@ -98,8 +99,19 @@ func TestFlagsFromEnv(t *testing.T) {
} }
err := a.Run([]string{"run"}) 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)
}
} }
} }
} }

View File

@ -64,7 +64,7 @@ OPTIONS:
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME: var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
USAGE: USAGE:
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}