Return an error when parsing environment variables for values fails
Currently cli silently (aside from IntSlice and Int64Slice which oddly printed directly to the error stream) ignores failures that occur when parsing environment variables for their value for flags that define environment variables. Instead, we should propogate up the error to the user. This is accomplished in a backwards compatible manner by adding a new, internal, interface which defines an applyWithError function that all flags here define. In v2, when we can modify the interface, we can drop this secondary interface and modify `Apply` to return an error.
This commit is contained in:
parent
a00c3f5872
commit
e367fafa3d
12
app.go
12
app.go
@ -174,7 +174,11 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
a.Setup()
|
||||
|
||||
// parse flags
|
||||
set := flagSet(a.Name, a.Flags)
|
||||
set, err := flagSet(a.Name, a.Flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(arguments[1:])
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
@ -293,7 +297,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
}
|
||||
|
||||
// parse flags
|
||||
set := flagSet(a.Name, a.Flags)
|
||||
set, err := flagSet(a.Name, a.Flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
|
24
app_test.go
24
app_test.go
@ -1523,7 +1523,11 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
|
||||
func TestHandleAction_WithNonFuncAction(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.Action = 42
|
||||
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
|
||||
fs, err := flagSet(app.Name, app.Flags)
|
||||
if err != nil {
|
||||
t.Errorf("error creating FlagSet: %s", err)
|
||||
}
|
||||
err = HandleAction(app.Action, NewContext(app, fs, nil))
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("expected to receive error from Run, got none")
|
||||
@ -1547,7 +1551,11 @@ func TestHandleAction_WithNonFuncAction(t *testing.T) {
|
||||
func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.Action = func() string { return "" }
|
||||
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
|
||||
fs, err := flagSet(app.Name, app.Flags)
|
||||
if err != nil {
|
||||
t.Errorf("error creating FlagSet: %s", err)
|
||||
}
|
||||
err = HandleAction(app.Action, NewContext(app, fs, nil))
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("expected to receive error from Run, got none")
|
||||
@ -1571,7 +1579,11 @@ func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
|
||||
func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.Action = func(_ *Context) (int, error) { return 0, nil }
|
||||
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
|
||||
fs, err := flagSet(app.Name, app.Flags)
|
||||
if err != nil {
|
||||
t.Errorf("error creating FlagSet: %s", err)
|
||||
}
|
||||
err = HandleAction(app.Action, NewContext(app, fs, nil))
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("expected to receive error from Run, got none")
|
||||
@ -1602,5 +1614,9 @@ func TestHandleAction_WithUnknownPanic(t *testing.T) {
|
||||
fn(ctx)
|
||||
return nil
|
||||
}
|
||||
HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
|
||||
fs, err := flagSet(app.Name, app.Flags)
|
||||
if err != nil {
|
||||
t.Errorf("error creating FlagSet: %s", err)
|
||||
}
|
||||
HandleAction(app.Action, NewContext(app, fs, nil))
|
||||
}
|
||||
|
@ -91,7 +91,10 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
c.Flags = append(c.Flags, BashCompletionFlag)
|
||||
}
|
||||
|
||||
set := flagSet(c.Name, c.Flags)
|
||||
set, err := flagSet(c.Name, c.Flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
set.SetOutput(ioutil.Discard)
|
||||
|
||||
if c.SkipFlagParsing {
|
||||
|
@ -147,6 +147,11 @@ func (c *Context) Parent() *Context {
|
||||
return c.parentContext
|
||||
}
|
||||
|
||||
// value returns the value of the flag coressponding to `name`
|
||||
func (c *Context) value(name string) interface{} {
|
||||
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
|
||||
}
|
||||
|
||||
// Args contains apps console arguments
|
||||
type Args []string
|
||||
|
||||
|
@ -184,7 +184,12 @@ func TestContext_IsSet(t *testing.T) {
|
||||
// XXX Corresponds to hack in context.IsSet for flags with EnvVar field
|
||||
// Should be moved to `flag_test` in v2
|
||||
func TestContext_IsSet_fromEnv(t *testing.T) {
|
||||
var timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet, passwordIsSet, pIsSet bool
|
||||
var (
|
||||
timeoutIsSet, tIsSet bool
|
||||
noEnvVarIsSet, nIsSet bool
|
||||
passwordIsSet, pIsSet bool
|
||||
unparsableIsSet, uIsSet bool
|
||||
)
|
||||
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||
@ -192,7 +197,8 @@ func TestContext_IsSet_fromEnv(t *testing.T) {
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||
Float64Flag{Name: "password, p", EnvVar: "APP_PASSWORD"},
|
||||
StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"},
|
||||
Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"},
|
||||
Float64Flag{Name: "no-env-var, n"},
|
||||
},
|
||||
Action: func(ctx *Context) error {
|
||||
@ -200,6 +206,8 @@ func TestContext_IsSet_fromEnv(t *testing.T) {
|
||||
tIsSet = ctx.IsSet("t")
|
||||
passwordIsSet = ctx.IsSet("password")
|
||||
pIsSet = ctx.IsSet("p")
|
||||
unparsableIsSet = ctx.IsSet("unparsable")
|
||||
uIsSet = ctx.IsSet("u")
|
||||
noEnvVarIsSet = ctx.IsSet("no-env-var")
|
||||
nIsSet = ctx.IsSet("n")
|
||||
return nil
|
||||
@ -212,6 +220,11 @@ func TestContext_IsSet_fromEnv(t *testing.T) {
|
||||
expect(t, pIsSet, true)
|
||||
expect(t, noEnvVarIsSet, false)
|
||||
expect(t, nIsSet, false)
|
||||
|
||||
os.Setenv("APP_UNPARSABLE", "foobar")
|
||||
a.Run([]string{"run"})
|
||||
expect(t, unparsableIsSet, false)
|
||||
expect(t, uIsSet, false)
|
||||
}
|
||||
|
||||
func TestContext_GlobalIsSet(t *testing.T) {
|
||||
@ -236,14 +249,22 @@ func TestContext_GlobalIsSet(t *testing.T) {
|
||||
// XXX Corresponds to hack in context.IsSet for flags with EnvVar field
|
||||
// Should be moved to `flag_test` in v2
|
||||
func TestContext_GlobalIsSet_fromEnv(t *testing.T) {
|
||||
var timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet bool
|
||||
var (
|
||||
timeoutIsSet, tIsSet bool
|
||||
noEnvVarIsSet, nIsSet bool
|
||||
passwordIsSet, pIsSet bool
|
||||
unparsableIsSet, uIsSet bool
|
||||
)
|
||||
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||
os.Setenv("APP_PASSWORD", "")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||
StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"},
|
||||
Float64Flag{Name: "no-env-var, n"},
|
||||
Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"},
|
||||
},
|
||||
Commands: []Command{
|
||||
{
|
||||
@ -251,6 +272,10 @@ func TestContext_GlobalIsSet_fromEnv(t *testing.T) {
|
||||
Action: func(ctx *Context) error {
|
||||
timeoutIsSet = ctx.GlobalIsSet("timeout")
|
||||
tIsSet = ctx.GlobalIsSet("t")
|
||||
passwordIsSet = ctx.GlobalIsSet("password")
|
||||
pIsSet = ctx.GlobalIsSet("p")
|
||||
unparsableIsSet = ctx.GlobalIsSet("unparsable")
|
||||
uIsSet = ctx.GlobalIsSet("u")
|
||||
noEnvVarIsSet = ctx.GlobalIsSet("no-env-var")
|
||||
nIsSet = ctx.GlobalIsSet("n")
|
||||
return nil
|
||||
@ -258,11 +283,22 @@ func TestContext_GlobalIsSet_fromEnv(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run", "hello"})
|
||||
if err := a.Run([]string{"run", "hello"}); err != nil {
|
||||
t.Logf("error running Run(): %+v", err)
|
||||
}
|
||||
expect(t, timeoutIsSet, true)
|
||||
expect(t, tIsSet, true)
|
||||
expect(t, passwordIsSet, true)
|
||||
expect(t, pIsSet, true)
|
||||
expect(t, noEnvVarIsSet, false)
|
||||
expect(t, nIsSet, false)
|
||||
|
||||
os.Setenv("APP_UNPARSABLE", "foobar")
|
||||
if err := a.Run([]string{"run"}); err != nil {
|
||||
t.Logf("error running Run(): %+v", err)
|
||||
}
|
||||
expect(t, unparsableIsSet, false)
|
||||
expect(t, uIsSet, false)
|
||||
}
|
||||
|
||||
func TestContext_NumFlags(t *testing.T) {
|
||||
|
225
flag.go
225
flag.go
@ -62,13 +62,29 @@ type Flag interface {
|
||||
GetName() string
|
||||
}
|
||||
|
||||
func flagSet(name string, flags []Flag) *flag.FlagSet {
|
||||
// errorableFlag is an interface that allows us to return errors during apply
|
||||
// it allows flags defined in this library to return errors in a fashion backwards compatible
|
||||
// TODO remove in v2 and modify the existing Flag interface to return errors
|
||||
type errorableFlag interface {
|
||||
Flag
|
||||
|
||||
applyWithError(*flag.FlagSet) error
|
||||
}
|
||||
|
||||
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
|
||||
for _, f := range flags {
|
||||
f.Apply(set)
|
||||
//TODO remove in v2 when errorableFlag is removed
|
||||
if f, ok := f.(errorableFlag); ok {
|
||||
if err := f.applyWithError(set); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
f.Apply(set)
|
||||
}
|
||||
}
|
||||
return set
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func eachName(longName string, fn func(string)) {
|
||||
@ -87,13 +103,22 @@ type Generic interface {
|
||||
|
||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||
// provided by the user for parsing by the flag
|
||||
// Ignores parsing errors
|
||||
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError takes the flagset and calls Set on the generic flag with the value
|
||||
// provided by the user for parsing by the flag
|
||||
func (f GenericFlag) applyWithError(set *flag.FlagSet) error {
|
||||
val := f.Value
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
val.Set(envVal)
|
||||
if err := val.Set(envVal); err != nil {
|
||||
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -102,9 +127,11 @@ func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringSlice is an opaque type for []string to satisfy flag.Value
|
||||
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
|
||||
type StringSlice []string
|
||||
|
||||
// Set appends the string value to the list of values
|
||||
@ -123,8 +150,19 @@ func (f *StringSlice) Value() []string {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Get returns the slice of strings set by this flag
|
||||
func (f *StringSlice) Get() interface{} {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f StringSliceFlag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
@ -132,7 +170,9 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
newVal := &StringSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
newVal.Set(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
break
|
||||
@ -146,9 +186,11 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IntSlice is an opaque type for []int to satisfy flag.Value
|
||||
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
||||
type IntSlice []int
|
||||
|
||||
// Set parses the value into an integer and appends it to the list of values
|
||||
@ -171,8 +213,19 @@ func (f *IntSlice) Value() []int {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Get returns the slice of ints set by this flag
|
||||
func (f *IntSlice) Get() interface{} {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f IntSliceFlag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
@ -180,9 +233,8 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
newVal := &IntSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
err := newVal.Set(s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(ErrWriter, err.Error())
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
@ -197,9 +249,11 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Int64Slice is an opaque type for []int to satisfy flag.Value
|
||||
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
||||
type Int64Slice []int64
|
||||
|
||||
// Set parses the value into an integer and appends it to the list of values
|
||||
@ -222,8 +276,19 @@ func (f *Int64Slice) Value() []int64 {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Get returns the slice of ints set by this flag
|
||||
func (f *Int64Slice) Get() interface{} {
|
||||
return *f
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f Int64SliceFlag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
@ -231,9 +296,8 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
|
||||
newVal := &Int64Slice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
err := newVal.Set(s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(ErrWriter, err.Error())
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
@ -248,10 +312,17 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f BoolFlag) applyWithError(set *flag.FlagSet) error {
|
||||
val := false
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
@ -263,9 +334,11 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err == nil {
|
||||
val = envValBool
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
val = envValBool
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -278,10 +351,18 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f BoolTFlag) applyWithError(set *flag.FlagSet) error {
|
||||
val := true
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
@ -293,10 +374,12 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err == nil {
|
||||
val = envValBool
|
||||
break
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
val = envValBool
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -308,10 +391,18 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f StringFlag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
@ -329,19 +420,28 @@ func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.String(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f IntFlag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err == nil {
|
||||
f.Value = int(envValInt)
|
||||
break
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
f.Value = int(envValInt)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -353,19 +453,29 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Int(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f Int64Flag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f Int64Flag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err == nil {
|
||||
f.Value = envValInt
|
||||
break
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValInt
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -377,19 +487,29 @@ func (f Int64Flag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Int64(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f UintFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f UintFlag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
||||
if err == nil {
|
||||
f.Value = uint(envValInt)
|
||||
break
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = uint(envValInt)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -401,19 +521,29 @@ func (f UintFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Uint(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f Uint64Flag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f Uint64Flag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
||||
if err == nil {
|
||||
f.Value = uint64(envValInt)
|
||||
break
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = uint64(envValInt)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -425,19 +555,29 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Uint64(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f DurationFlag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValDuration, err := time.ParseDuration(envVal)
|
||||
if err == nil {
|
||||
f.Value = envValDuration
|
||||
break
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValDuration
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -449,18 +589,29 @@ func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Duration(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
// Ignores errors
|
||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||
f.applyWithError(set)
|
||||
}
|
||||
|
||||
// applyWithError populates the flag given the flag set and environment
|
||||
func (f Float64Flag) applyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
||||
if err == nil {
|
||||
f.Value = float64(envValFloat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = float64(envValFloat)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -472,6 +623,8 @@ func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||
}
|
||||
set.Float64(name, f.Value, f.Usage)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func visibleFlags(fl []Flag) []Flag {
|
||||
|
83
flag_test.go
83
flag_test.go
@ -29,35 +29,78 @@ func TestBoolFlagHelpOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBoolFromEnv(t *testing.T) {
|
||||
var boolFlagTests = []struct {
|
||||
func TestFlagsFromEnv(t *testing.T) {
|
||||
var flagTests = []struct {
|
||||
input string
|
||||
output bool
|
||||
output interface{}
|
||||
flag Flag
|
||||
err error
|
||||
}{
|
||||
{"", false},
|
||||
{"1", true},
|
||||
{"false", false},
|
||||
{"true", true},
|
||||
{"", 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, 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`)},
|
||||
|
||||
{"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`)},
|
||||
|
||||
{"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", 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", 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,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", 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`)},
|
||||
|
||||
{"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, nil},
|
||||
|
||||
{"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, nil},
|
||||
|
||||
{"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", 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`)},
|
||||
|
||||
{"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, nil},
|
||||
}
|
||||
|
||||
for _, test := range boolFlagTests {
|
||||
for _, test := range flagTests {
|
||||
os.Clearenv()
|
||||
os.Setenv("DEBUG", test.input)
|
||||
os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input)
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
BoolFlag{Name: "debug, d", EnvVar: "DEBUG"},
|
||||
},
|
||||
Flags: []Flag{test.flag},
|
||||
Action: func(ctx *Context) error {
|
||||
if ctx.Bool("debug") != test.output {
|
||||
t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("debug"))
|
||||
}
|
||||
if ctx.Bool("d") != test.output {
|
||||
t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("d"))
|
||||
if !reflect.DeepEqual(ctx.value(test.flag.GetName()), test.output) {
|
||||
t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.value(test.flag.GetName()))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1100,6 +1143,10 @@ func (p *Parser) String() string {
|
||||
return fmt.Sprintf("%s,%s", p[0], p[1])
|
||||
}
|
||||
|
||||
func (p *Parser) Get() interface{} {
|
||||
return p
|
||||
}
|
||||
|
||||
func TestParseGeneric(t *testing.T) {
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
|
Loading…
Reference in New Issue
Block a user