diff --git a/.travis.yml b/.travis.yml index d36c22436f..9f61c62f64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,7 @@ os: - osx env: - GO111MODULE=on - GOPROXY=https://proxy.golang.org + GO111MODULE=on GOPROXY=https://proxy.golang.org cache: directories: @@ -30,6 +29,8 @@ script: - go run build.go test - go run build.go gfmrun docs/v1/manual.md - go run build.go toc docs/v1/manual.md + - go run build.go gfmrun docs/v2/manual.md + - go run build.go toc docs/v2/manual.md after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/altsrc/flag.go b/altsrc/flag.go index afb4ad44cc..31b8a04e87 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -2,11 +2,11 @@ package altsrc import ( "fmt" + "path/filepath" "strconv" - "strings" "syscall" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) // FlagInputSourceExtension is an extension interface of cli.Flag that @@ -65,15 +65,15 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context // ApplyInputSourceValue applies a generic value to the flagSet if required func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.Generic(f.GenericFlag.Name) if err != nil { return err } if value != nil { - eachName(f.Name, func(name string) { - _ = f.set.Set(f.Name, value.String()) - }) + for _, name := range f.Names() { + _ = f.set.Set(name, value.String()) + } } } } @@ -84,19 +84,19 @@ func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourc // ApplyInputSourceValue applies a StringSlice value to the flagSet if required func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.StringSlice(f.StringSliceFlag.Name) if err != nil { return err } if value != nil { - var sliceValue cli.StringSlice = value - eachName(f.Name, func(name string) { - underlyingFlag := f.set.Lookup(f.Name) + var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...)) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } - }) + } } } } @@ -106,19 +106,19 @@ func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputS // ApplyInputSourceValue applies a IntSlice value if required func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.IntSlice(f.IntSliceFlag.Name) if err != nil { return err } if value != nil { - var sliceValue cli.IntSlice = value - eachName(f.Name, func(name string) { - underlyingFlag := f.set.Lookup(f.Name) + var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...)) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } - }) + } } } } @@ -128,51 +128,61 @@ func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour // ApplyInputSourceValue applies a Bool value to the flagSet if required func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.Bool(f.BoolFlag.Name) if err != nil { return err } if value { - eachName(f.Name, func(name string) { - _ = f.set.Set(f.Name, strconv.FormatBool(value)) - }) + for _, name := range f.Names() { + _ = f.set.Set(name, strconv.FormatBool(value)) + } } } } return nil } -// ApplyInputSourceValue applies a BoolT value to the flagSet if required -func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +// ApplyInputSourceValue applies a String value to the flagSet if required +func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { - value, err := isc.BoolT(f.BoolTFlag.Name) + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { + value, err := isc.String(f.StringFlag.Name) if err != nil { return err } - if !value { - eachName(f.Name, func(name string) { - _ = f.set.Set(f.Name, strconv.FormatBool(value)) - }) + if value != "" { + for _, name := range f.Names() { + _ = f.set.Set(name, value) + } } } } return nil } -// ApplyInputSourceValue applies a String value to the flagSet if required -func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +// ApplyInputSourceValue applies a Path value to the flagSet if required +func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { - value, err := isc.String(f.StringFlag.Name) + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { + value, err := isc.String(f.PathFlag.Name) if err != nil { return err } if value != "" { - eachName(f.Name, func(name string) { - _ = f.set.Set(f.Name, value) - }) + for _, name := range f.Names() { + + if !filepath.IsAbs(value) && isc.Source() != "" { + basePathAbs, err := filepath.Abs(isc.Source()) + if err != nil { + return err + } + + value = filepath.Join(filepath.Dir(basePathAbs), value) + } + + _ = f.set.Set(name, value) + } } } } @@ -182,15 +192,15 @@ func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSource // ApplyInputSourceValue applies a int value to the flagSet if required func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Int(f.IntFlag.Name) if err != nil { return err } if value > 0 { - eachName(f.Name, func(name string) { - _ = f.set.Set(f.Name, strconv.FormatInt(int64(value), 10)) - }) + for _, name := range f.Names() { + _ = f.set.Set(name, strconv.FormatInt(int64(value), 10)) + } } } } @@ -200,15 +210,15 @@ func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCon // ApplyInputSourceValue applies a Duration value to the flagSet if required func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Duration(f.DurationFlag.Name) if err != nil { return err } if value > 0 { - eachName(f.Name, func(name string) { - _ = f.set.Set(f.Name, value.String()) - }) + for _, name := range f.Names() { + _ = f.set.Set(name, value.String()) + } } } } @@ -218,25 +228,24 @@ func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour // ApplyInputSourceValue applies a Float64 value to the flagSet if required func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Float64(f.Float64Flag.Name) if err != nil { return err } if value > 0 { floatStr := float64ToString(value) - eachName(f.Name, func(name string) { - _ = f.set.Set(f.Name, floatStr) - }) + for _, name := range f.Names() { + _ = f.set.Set(name, floatStr) + } } } } return nil } -func isEnvVarSet(envVars string) bool { - for _, envVar := range strings.Split(envVars, ",") { - envVar = strings.TrimSpace(envVar) +func isEnvVarSet(envVars []string) bool { + for _, envVar := range envVars { if _, ok := syscall.Getenv(envVar); ok { // TODO: Can't use this for bools as // set means that it was true or false based on @@ -251,11 +260,3 @@ func isEnvVarSet(envVars string) bool { func float64ToString(f float64) string { return fmt.Sprintf("%v", f) } - -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} diff --git a/altsrc/flag_generated.go b/altsrc/flag_generated.go index e568872143..29572a6c85 100644 --- a/altsrc/flag_generated.go +++ b/altsrc/flag_generated.go @@ -4,343 +4,271 @@ package altsrc import ( "flag" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) // BoolFlag is the flag type that wraps cli.BoolFlag to allow // for other values to be specified type BoolFlag struct { - cli.BoolFlag + *cli.BoolFlag set *flag.FlagSet } // NewBoolFlag creates a new BoolFlag -func NewBoolFlag(fl cli.BoolFlag) *BoolFlag { - return &BoolFlag{ BoolFlag: fl, set: nil } +func NewBoolFlag(fl *cli.BoolFlag) *BoolFlag { + return &BoolFlag{BoolFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped BoolFlag.Apply -func (f *BoolFlag) Apply(set *flag.FlagSet) { +func (f *BoolFlag) Apply(set *flag.FlagSet) error { f.set = set - f.BoolFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped BoolFlag.ApplyWithError -func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.BoolFlag.ApplyWithError(set) -} - -// BoolTFlag is the flag type that wraps cli.BoolTFlag to allow -// for other values to be specified -type BoolTFlag struct { - cli.BoolTFlag - set *flag.FlagSet -} - -// NewBoolTFlag creates a new BoolTFlag -func NewBoolTFlag(fl cli.BoolTFlag) *BoolTFlag { - return &BoolTFlag{ BoolTFlag: fl, set: nil } -} - -// Apply saves the flagSet for later usage calls, then calls -// the wrapped BoolTFlag.Apply -func (f *BoolTFlag) Apply(set *flag.FlagSet) { - f.set = set - f.BoolTFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped BoolTFlag.ApplyWithError -func (f *BoolTFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.BoolTFlag.ApplyWithError(set) + return f.BoolFlag.Apply(set) } // DurationFlag is the flag type that wraps cli.DurationFlag to allow // for other values to be specified type DurationFlag struct { - cli.DurationFlag + *cli.DurationFlag set *flag.FlagSet } // NewDurationFlag creates a new DurationFlag -func NewDurationFlag(fl cli.DurationFlag) *DurationFlag { - return &DurationFlag{ DurationFlag: fl, set: nil } +func NewDurationFlag(fl *cli.DurationFlag) *DurationFlag { + return &DurationFlag{DurationFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped DurationFlag.Apply -func (f *DurationFlag) Apply(set *flag.FlagSet) { +func (f *DurationFlag) Apply(set *flag.FlagSet) error { f.set = set - f.DurationFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped DurationFlag.ApplyWithError -func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.DurationFlag.ApplyWithError(set) + return f.DurationFlag.Apply(set) } // Float64Flag is the flag type that wraps cli.Float64Flag to allow // for other values to be specified type Float64Flag struct { - cli.Float64Flag + *cli.Float64Flag set *flag.FlagSet } // NewFloat64Flag creates a new Float64Flag -func NewFloat64Flag(fl cli.Float64Flag) *Float64Flag { - return &Float64Flag{ Float64Flag: fl, set: nil } +func NewFloat64Flag(fl *cli.Float64Flag) *Float64Flag { + return &Float64Flag{Float64Flag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped Float64Flag.Apply -func (f *Float64Flag) Apply(set *flag.FlagSet) { - f.set = set - f.Float64Flag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped Float64Flag.ApplyWithError -func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error { +func (f *Float64Flag) Apply(set *flag.FlagSet) error { f.set = set - return f.Float64Flag.ApplyWithError(set) + return f.Float64Flag.Apply(set) } // GenericFlag is the flag type that wraps cli.GenericFlag to allow // for other values to be specified type GenericFlag struct { - cli.GenericFlag + *cli.GenericFlag set *flag.FlagSet } // NewGenericFlag creates a new GenericFlag -func NewGenericFlag(fl cli.GenericFlag) *GenericFlag { - return &GenericFlag{ GenericFlag: fl, set: nil } +func NewGenericFlag(fl *cli.GenericFlag) *GenericFlag { + return &GenericFlag{GenericFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped GenericFlag.Apply -func (f *GenericFlag) Apply(set *flag.FlagSet) { - f.set = set - f.GenericFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped GenericFlag.ApplyWithError -func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error { +func (f *GenericFlag) Apply(set *flag.FlagSet) error { f.set = set - return f.GenericFlag.ApplyWithError(set) + return f.GenericFlag.Apply(set) } // Int64Flag is the flag type that wraps cli.Int64Flag to allow // for other values to be specified type Int64Flag struct { - cli.Int64Flag + *cli.Int64Flag set *flag.FlagSet } // NewInt64Flag creates a new Int64Flag -func NewInt64Flag(fl cli.Int64Flag) *Int64Flag { - return &Int64Flag{ Int64Flag: fl, set: nil } +func NewInt64Flag(fl *cli.Int64Flag) *Int64Flag { + return &Int64Flag{Int64Flag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped Int64Flag.Apply -func (f *Int64Flag) Apply(set *flag.FlagSet) { - f.set = set - f.Int64Flag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped Int64Flag.ApplyWithError -func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error { +func (f *Int64Flag) Apply(set *flag.FlagSet) error { f.set = set - return f.Int64Flag.ApplyWithError(set) + return f.Int64Flag.Apply(set) } // IntFlag is the flag type that wraps cli.IntFlag to allow // for other values to be specified type IntFlag struct { - cli.IntFlag + *cli.IntFlag set *flag.FlagSet } // NewIntFlag creates a new IntFlag -func NewIntFlag(fl cli.IntFlag) *IntFlag { - return &IntFlag{ IntFlag: fl, set: nil } +func NewIntFlag(fl *cli.IntFlag) *IntFlag { + return &IntFlag{IntFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped IntFlag.Apply -func (f *IntFlag) Apply(set *flag.FlagSet) { +func (f *IntFlag) Apply(set *flag.FlagSet) error { f.set = set - f.IntFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped IntFlag.ApplyWithError -func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.IntFlag.ApplyWithError(set) + return f.IntFlag.Apply(set) } // IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow // for other values to be specified type IntSliceFlag struct { - cli.IntSliceFlag + *cli.IntSliceFlag set *flag.FlagSet } // NewIntSliceFlag creates a new IntSliceFlag -func NewIntSliceFlag(fl cli.IntSliceFlag) *IntSliceFlag { - return &IntSliceFlag{ IntSliceFlag: fl, set: nil } +func NewIntSliceFlag(fl *cli.IntSliceFlag) *IntSliceFlag { + return &IntSliceFlag{IntSliceFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped IntSliceFlag.Apply -func (f *IntSliceFlag) Apply(set *flag.FlagSet) { +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { f.set = set - f.IntSliceFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped IntSliceFlag.ApplyWithError -func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.IntSliceFlag.ApplyWithError(set) + return f.IntSliceFlag.Apply(set) } // Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow // for other values to be specified type Int64SliceFlag struct { - cli.Int64SliceFlag + *cli.Int64SliceFlag set *flag.FlagSet } // NewInt64SliceFlag creates a new Int64SliceFlag -func NewInt64SliceFlag(fl cli.Int64SliceFlag) *Int64SliceFlag { - return &Int64SliceFlag{ Int64SliceFlag: fl, set: nil } +func NewInt64SliceFlag(fl *cli.Int64SliceFlag) *Int64SliceFlag { + return &Int64SliceFlag{Int64SliceFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped Int64SliceFlag.Apply -func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { f.set = set - f.Int64SliceFlag.Apply(set) + return f.Int64SliceFlag.Apply(set) +} + +// Float64SliceFlag is the flag type that wraps cli.Float64SliceFlag to allow +// for other values to be specified +type Float64SliceFlag struct { + *cli.Float64SliceFlag + set *flag.FlagSet } -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped Int64SliceFlag.ApplyWithError -func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { +// NewFloat64SliceFlag creates a new Float64SliceFlag +func NewFloat64SliceFlag(fl *cli.Float64SliceFlag) *Float64SliceFlag { + return &Float64SliceFlag{Float64SliceFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped Float64SliceFlag.Apply +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { f.set = set - return f.Int64SliceFlag.ApplyWithError(set) + return f.Float64SliceFlag.Apply(set) } // StringFlag is the flag type that wraps cli.StringFlag to allow // for other values to be specified type StringFlag struct { - cli.StringFlag + *cli.StringFlag set *flag.FlagSet } // NewStringFlag creates a new StringFlag -func NewStringFlag(fl cli.StringFlag) *StringFlag { - return &StringFlag{ StringFlag: fl, set: nil } +func NewStringFlag(fl *cli.StringFlag) *StringFlag { + return &StringFlag{StringFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped StringFlag.Apply -func (f *StringFlag) Apply(set *flag.FlagSet) { +func (f *StringFlag) Apply(set *flag.FlagSet) error { f.set = set - f.StringFlag.Apply(set) + return f.StringFlag.Apply(set) } -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped StringFlag.ApplyWithError -func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error { +// PathFlag is the flag type that wraps cli.PathFlag to allow +// for other values to be specified +type PathFlag struct { + *cli.PathFlag + set *flag.FlagSet +} + +// NewPathFlag creates a new PathFlag +func NewPathFlag(fl *cli.PathFlag) *PathFlag { + return &PathFlag{PathFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped PathFlag.Apply +func (f *PathFlag) Apply(set *flag.FlagSet) error { f.set = set - return f.StringFlag.ApplyWithError(set) + return f.PathFlag.Apply(set) } // StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow // for other values to be specified type StringSliceFlag struct { - cli.StringSliceFlag + *cli.StringSliceFlag set *flag.FlagSet } // NewStringSliceFlag creates a new StringSliceFlag -func NewStringSliceFlag(fl cli.StringSliceFlag) *StringSliceFlag { - return &StringSliceFlag{ StringSliceFlag: fl, set: nil } +func NewStringSliceFlag(fl *cli.StringSliceFlag) *StringSliceFlag { + return &StringSliceFlag{StringSliceFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped StringSliceFlag.Apply -func (f *StringSliceFlag) Apply(set *flag.FlagSet) { - f.set = set - f.StringSliceFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped StringSliceFlag.ApplyWithError -func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { f.set = set - return f.StringSliceFlag.ApplyWithError(set) + return f.StringSliceFlag.Apply(set) } // Uint64Flag is the flag type that wraps cli.Uint64Flag to allow // for other values to be specified type Uint64Flag struct { - cli.Uint64Flag + *cli.Uint64Flag set *flag.FlagSet } // NewUint64Flag creates a new Uint64Flag -func NewUint64Flag(fl cli.Uint64Flag) *Uint64Flag { - return &Uint64Flag{ Uint64Flag: fl, set: nil } +func NewUint64Flag(fl *cli.Uint64Flag) *Uint64Flag { + return &Uint64Flag{Uint64Flag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped Uint64Flag.Apply -func (f *Uint64Flag) Apply(set *flag.FlagSet) { +func (f *Uint64Flag) Apply(set *flag.FlagSet) error { f.set = set - f.Uint64Flag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped Uint64Flag.ApplyWithError -func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.Uint64Flag.ApplyWithError(set) + return f.Uint64Flag.Apply(set) } // UintFlag is the flag type that wraps cli.UintFlag to allow // for other values to be specified type UintFlag struct { - cli.UintFlag + *cli.UintFlag set *flag.FlagSet } // NewUintFlag creates a new UintFlag -func NewUintFlag(fl cli.UintFlag) *UintFlag { - return &UintFlag{ UintFlag: fl, set: nil } +func NewUintFlag(fl *cli.UintFlag) *UintFlag { + return &UintFlag{UintFlag: fl, set: nil} } // Apply saves the flagSet for later usage calls, then calls // the wrapped UintFlag.Apply -func (f *UintFlag) Apply(set *flag.FlagSet) { - f.set = set - f.UintFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls -// the wrapped UintFlag.ApplyWithError -func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error { +func (f *UintFlag) Apply(set *flag.FlagSet) error { f.set = set - return f.UintFlag.ApplyWithError(set) + return f.UintFlag.Apply(set) } diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 90a96d349d..80d20ee2e6 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -3,12 +3,12 @@ package altsrc import ( "flag" "fmt" + "github.com/urfave/cli/v2" "os" + "runtime" "strings" "testing" "time" - - "github.com/urfave/cli" ) type testApplyInputSource struct { @@ -20,13 +20,14 @@ type testApplyInputSource struct { ContextValue flag.Value EnvVarValue string EnvVarName string + SourcePath string MapValue interface{} } func TestGenericApplyInputSourceValue(t *testing.T) { v := &Parser{"abc", "def"} c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}), FlagName: "test", MapValue: v, }) @@ -36,7 +37,7 @@ func TestGenericApplyInputSourceValue(t *testing.T) { func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { p := &Parser{"abc", "def"} c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}), FlagName: "test", MapValue: &Parser{"efg", "hig"}, ContextValueString: p.String(), @@ -46,7 +47,11 @@ func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}), + Flag: NewGenericFlag(&cli.GenericFlag{ + Name: "test", + Value: &Parser{}, + EnvVars: []string{"TEST"}, + }), FlagName: "test", MapValue: &Parser{"efg", "hij"}, EnvVarName: "TEST", @@ -57,7 +62,7 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestStringSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, }) @@ -66,7 +71,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) { func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, ContextValueString: "ohno", @@ -76,7 +81,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, EnvVarName: "TEST", @@ -87,7 +92,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestIntSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), FlagName: "test", MapValue: []interface{}{1, 2}, }) @@ -96,7 +101,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) { func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), FlagName: "test", MapValue: []interface{}{1, 2}, ContextValueString: "3", @@ -106,7 +111,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []interface{}{1, 2}, EnvVarName: "TEST", @@ -117,7 +122,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestBoolApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}), FlagName: "test", MapValue: true, }) @@ -126,7 +131,7 @@ func TestBoolApplyInputSourceMethodSet(t *testing.T) { func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}), FlagName: "test", MapValue: false, ContextValueString: "true", @@ -136,7 +141,7 @@ func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: false, EnvVarName: "TEST", @@ -145,69 +150,76 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { expect(t, true, c.Bool("test")) } -func TestBoolTApplyInputSourceMethodSet(t *testing.T) { +func TestStringApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", - MapValue: false, + MapValue: "hello", }) - expect(t, false, c.BoolT("test")) + expect(t, "hello", c.String("test")) } -func TestBoolTApplyInputSourceMethodContextSet(t *testing.T) { +func TestStringApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", - MapValue: true, - ContextValueString: "false", + MapValue: "hello", + ContextValueString: "goodbye", }) - expect(t, false, c.BoolT("test")) + expect(t, "goodbye", c.String("test")) } -func TestBoolTApplyInputSourceMethodEnvVarSet(t *testing.T) { +func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", - MapValue: true, + MapValue: "hello", EnvVarName: "TEST", - EnvVarValue: "false", + EnvVarValue: "goodbye", }) - expect(t, false, c.BoolT("test")) + expect(t, "goodbye", c.String("test")) } - -func TestStringApplyInputSourceMethodSet(t *testing.T) { +func TestPathApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test"}), - FlagName: "test", - MapValue: "hello", + Flag: NewPathFlag(&cli.PathFlag{Name: "test"}), + FlagName: "test", + MapValue: "hello", + SourcePath: "/path/to/source/file", }) - expect(t, "hello", c.String("test")) + + expected := "/path/to/source/hello" + if runtime.GOOS == "windows" { + expected = `C:\path\to\source\hello` + } + expect(t, expected, c.String("test")) } -func TestStringApplyInputSourceMethodContextSet(t *testing.T) { +func TestPathApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + Flag: NewPathFlag(&cli.PathFlag{Name: "test"}), FlagName: "test", MapValue: "hello", ContextValueString: "goodbye", + SourcePath: "/path/to/source/file", }) expect(t, "goodbye", c.String("test")) } -func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { +func TestPathApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewPathFlag(&cli.PathFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: "hello", EnvVarName: "TEST", EnvVarValue: "goodbye", + SourcePath: "/path/to/source/file", }) expect(t, "goodbye", c.String("test")) } func TestIntApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: 15, }) @@ -216,7 +228,7 @@ func TestIntApplyInputSourceMethodSet(t *testing.T) { func TestIntApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: 15, ContextValueString: "7", @@ -226,7 +238,7 @@ func TestIntApplyInputSourceMethodContextSet(t *testing.T) { func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 15, EnvVarName: "TEST", @@ -237,7 +249,7 @@ func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestDurationApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: 30 * time.Second, }) @@ -246,7 +258,7 @@ func TestDurationApplyInputSourceMethodSet(t *testing.T) { func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: 30 * time.Second, ContextValueString: (15 * time.Second).String(), @@ -256,7 +268,7 @@ func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 30 * time.Second, EnvVarName: "TEST", @@ -267,7 +279,7 @@ func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: 1.3, }) @@ -276,7 +288,7 @@ func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: 1.3, ContextValueString: fmt.Sprintf("%v", 1.4), @@ -286,7 +298,7 @@ func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 1.3, EnvVarName: "TEST", @@ -296,7 +308,10 @@ func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { } func runTest(t *testing.T, test testApplyInputSource) *cli.Context { - inputSource := &MapInputSource{valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}} + inputSource := &MapInputSource{ + file: test.SourcePath, + valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}, + } set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError) c := cli.NewContext(nil, set, nil) if test.EnvVarName != "" && test.EnvVarValue != "" { @@ -304,7 +319,7 @@ func runTest(t *testing.T, test testApplyInputSource) *cli.Context { defer os.Setenv(test.EnvVarName, "") } - test.Flag.Apply(set) + _ = test.Flag.Apply(set) if test.ContextValue != nil { f := set.Lookup(test.FlagName) f.Value = test.ContextValue diff --git a/altsrc/helpers_test.go b/altsrc/helpers_test.go index 3b7f7e94e0..33e8a4b976 100644 --- a/altsrc/helpers_test.go +++ b/altsrc/helpers_test.go @@ -1,13 +1,23 @@ package altsrc import ( + "os" "reflect" + "runtime" + "strings" "testing" ) +var ( + wd, _ = os.Getwd() +) + func expect(t *testing.T, a interface{}, b interface{}) { - if !reflect.DeepEqual(b, a) { - t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + _, fn, line, _ := runtime.Caller(1) + fn = strings.Replace(fn, wd+"/", "", -1) + + if !reflect.DeepEqual(a, b) { + t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 8ea7e92053..5f605741b2 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -1,14 +1,18 @@ package altsrc import ( + "github.com/urfave/cli/v2" "time" - - "github.com/urfave/cli" ) // InputSourceContext is an interface used to allow // other input sources to be implemented as needed. +// +// Source returns an identifier for the input source. In case of file source +// it should return path to the file. type InputSourceContext interface { + Source() string + Int(name string) (int, error) Duration(name string) (time.Duration, error) Float64(name string) (float64, error) @@ -17,5 +21,4 @@ type InputSourceContext interface { IntSlice(name string) ([]int, error) Generic(name string) (cli.Generic, error) Bool(name string) (bool, error) - BoolT(name string) (bool, error) } diff --git a/altsrc/json_command_test.go b/altsrc/json_command_test.go index 63c19b2a34..bd8e6cfd72 100644 --- a/altsrc/json_command_test.go +++ b/altsrc/json_command_test.go @@ -2,11 +2,10 @@ package altsrc import ( "flag" + "github.com/urfave/cli/v2" "io/ioutil" "os" "testing" - - "github.com/urfave/cli" ) const ( @@ -19,7 +18,7 @@ func TestCommandJSONFileTest(t *testing.T) { cleanup := writeTempFile(t, fileName, simpleJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName} _ = set.Parse(test) @@ -37,7 +36,7 @@ func TestCommandJSONFileTest(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), + NewIntFlag(&cli.IntFlag{Name: "test"}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) @@ -50,7 +49,7 @@ func TestCommandJSONFileTestGlobalEnvVarWins(t *testing.T) { cleanup := writeTempFile(t, fileName, simpleJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") @@ -71,7 +70,7 @@ func TestCommandJSONFileTestGlobalEnvVarWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), + NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) @@ -85,7 +84,7 @@ func TestCommandJSONFileTestGlobalEnvVarWinsNested(t *testing.T) { cleanup := writeTempFile(t, fileName, nestedJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") @@ -106,7 +105,7 @@ func TestCommandJSONFileTestGlobalEnvVarWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), + NewIntFlag(&cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) @@ -120,7 +119,7 @@ func TestCommandJSONFileTestSpecifiedFlagWins(t *testing.T) { cleanup := writeTempFile(t, fileName, simpleJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName, "--test", "7"} _ = set.Parse(test) @@ -138,7 +137,7 @@ func TestCommandJSONFileTestSpecifiedFlagWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), + NewIntFlag(&cli.IntFlag{Name: "test"}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) @@ -152,7 +151,7 @@ func TestCommandJSONFileTestSpecifiedFlagWinsNested(t *testing.T) { cleanup := writeTempFile(t, fileName, nestedJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName, "--top.test", "7"} _ = set.Parse(test) @@ -170,7 +169,7 @@ func TestCommandJSONFileTestSpecifiedFlagWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test"}), + NewIntFlag(&cli.IntFlag{Name: "top.test"}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) @@ -184,7 +183,7 @@ func TestCommandJSONFileTestDefaultValueFileWins(t *testing.T) { cleanup := writeTempFile(t, fileName, simpleJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName} _ = set.Parse(test) @@ -202,7 +201,7 @@ func TestCommandJSONFileTestDefaultValueFileWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) @@ -216,7 +215,7 @@ func TestCommandJSONFileTestDefaultValueFileWinsNested(t *testing.T) { cleanup := writeTempFile(t, fileName, nestedJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName} _ = set.Parse(test) @@ -234,7 +233,7 @@ func TestCommandJSONFileTestDefaultValueFileWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) @@ -248,7 +247,7 @@ func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWins(t *testing.T cleanup := writeTempFile(t, fileName, simpleJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") @@ -269,7 +268,7 @@ func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWins(t *testing.T return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) @@ -282,7 +281,7 @@ func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWinsNested(t *tes cleanup := writeTempFile(t, fileName, nestedJSON) defer cleanup() - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") @@ -303,7 +302,7 @@ func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWinsNested(t *tes return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}), &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index a5b2b0d353..31c46cd051 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -3,12 +3,11 @@ package altsrc import ( "encoding/json" "fmt" + "github.com/urfave/cli/v2" "io" "io/ioutil" "strings" "time" - - "github.com/urfave/cli" ) // NewJSONSourceFromFlagFunc returns a func that takes a cli.Context @@ -29,6 +28,7 @@ func NewJSONSourceFromFile(f string) (InputSourceContext, error) { if err != nil { return nil, err } + return NewJSONSource(data) } @@ -52,6 +52,10 @@ func NewJSONSource(data []byte) (InputSourceContext, error) { return &jsonSource{deserialized: deserialized}, nil } +func (x *jsonSource) Source() string { + return x.file +} + func (x *jsonSource) Int(name string) (int, error) { i, err := x.getValue(name) if err != nil { @@ -62,10 +66,10 @@ func (x *jsonSource) Int(name string) (int, error) { return 0, fmt.Errorf("unexpected type %T for %q", i, name) case int: return v, nil - case float64: - return int(float64(v)), nil case float32: - return int(float32(v)), nil + return int(v), nil + case float64: + return int(v), nil } } @@ -175,12 +179,6 @@ func (x *jsonSource) Bool(name string) (bool, error) { return v, nil } -// since this source appears to require all configuration to be specified, the -// concept of a boolean defaulting to true seems inconsistent with no defaults -func (x *jsonSource) BoolT(name string) (bool, error) { - return false, fmt.Errorf("unsupported type BoolT for JSONSource") -} - func (x *jsonSource) getValue(key string) (interface{}, error) { return jsonGetValue(key, x.deserialized) } @@ -204,5 +202,6 @@ func jsonGetValue(key string, m map[string]interface{}) (interface{}, error) { } type jsonSource struct { + file string deserialized map[string]interface{} } diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index e8cb06022f..66bd6e8e89 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -2,16 +2,16 @@ package altsrc import ( "fmt" + "github.com/urfave/cli/v2" "reflect" "strings" "time" - - "github.com/urfave/cli" ) // MapInputSource implements InputSourceContext to return // data from the map that is loaded. type MapInputSource struct { + file string valueMap map[interface{}]interface{} } @@ -39,6 +39,11 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool return nil, false } +// Source returns the path of the source file +func (fsm *MapInputSource) Source() string { + return fsm.file +} + // Int returns an int from the map if it exists otherwise returns 0 func (fsm *MapInputSource) Int(name string) (int, error) { otherGenericValue, exists := fsm.valueMap[name] @@ -65,24 +70,28 @@ func (fsm *MapInputSource) Int(name string) (int, error) { func (fsm *MapInputSource) Duration(name string) (time.Duration, error) { otherGenericValue, exists := fsm.valueMap[name] if exists { - otherValue, isType := otherGenericValue.(time.Duration) - if !isType { - return 0, incorrectTypeForFlagError(name, "duration", otherGenericValue) - } - return otherValue, nil + return castDuration(name, otherGenericValue) } nestedGenericValue, exists := nestedVal(name, fsm.valueMap) if exists { - otherValue, isType := nestedGenericValue.(time.Duration) - if !isType { - return 0, incorrectTypeForFlagError(name, "duration", nestedGenericValue) - } - return otherValue, nil + return castDuration(name, nestedGenericValue) } return 0, nil } +func castDuration(name string, value interface{}) (time.Duration, error) { + if otherValue, isType := value.(time.Duration); isType { + return otherValue, nil + } + otherStringValue, isType := value.(string) + parsedValue, err := time.ParseDuration(otherStringValue) + if !isType || err != nil { + return 0, incorrectTypeForFlagError(name, "duration", value) + } + return parsedValue, nil +} + // Float64 returns an float64 from the map if it exists otherwise returns 0 func (fsm *MapInputSource) Float64(name string) (float64, error) { otherGenericValue, exists := fsm.valueMap[name] @@ -229,28 +238,6 @@ func (fsm *MapInputSource) Bool(name string) (bool, error) { return false, nil } -// BoolT returns an bool from the map otherwise returns true -func (fsm *MapInputSource) BoolT(name string) (bool, error) { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(bool) - if !isType { - return true, incorrectTypeForFlagError(name, "bool", otherGenericValue) - } - return otherValue, nil - } - nestedGenericValue, exists := nestedVal(name, fsm.valueMap) - if exists { - otherValue, isType := nestedGenericValue.(bool) - if !isType { - return true, incorrectTypeForFlagError(name, "bool", nestedGenericValue) - } - return otherValue, nil - } - - return true, nil -} - func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error { valueType := reflect.TypeOf(value) valueTypeName := "" diff --git a/altsrc/map_input_source_test.go b/altsrc/map_input_source_test.go new file mode 100644 index 0000000000..a921d0493a --- /dev/null +++ b/altsrc/map_input_source_test.go @@ -0,0 +1,25 @@ +package altsrc + +import ( + "testing" + "time" +) + +func TestMapDuration(t *testing.T) { + inputSource := &MapInputSource{ + file: "test", + valueMap: map[interface{}]interface{}{ + "duration_of_duration_type": time.Minute, + "duration_of_string_type": "1m", + "duration_of_int_type": 1000, + }, + } + d, err := inputSource.Duration("duration_of_duration_type") + expect(t, time.Minute, d) + expect(t, nil, err) + d, err = inputSource.Duration("duration_of_string_type") + expect(t, time.Minute, d) + expect(t, nil, err) + d, err = inputSource.Duration("duration_of_int_type") + refute(t, nil, err) +} diff --git a/altsrc/toml_command_test.go b/altsrc/toml_command_test.go index 1d91d56c3a..0ae06cf934 100644 --- a/altsrc/toml_command_test.go +++ b/altsrc/toml_command_test.go @@ -1,26 +1,20 @@ -// Disabling building of toml support in cases where golang is 1.0 or 1.1 -// as the encoding library is not implemented or supported. - -// +build go1.2 - package altsrc import ( "flag" + "github.com/urfave/cli/v2" "io/ioutil" "os" "testing" - - "github.com/urfave/cli" ) func TestCommandTomFileTest(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -35,8 +29,8 @@ func TestCommandTomFileTest(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) err := command.Run(c) @@ -45,15 +39,15 @@ func TestCommandTomFileTest(t *testing.T) { } func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -68,8 +62,8 @@ func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) @@ -79,15 +73,15 @@ func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) { } func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) defer os.Remove("current.toml") - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -102,8 +96,8 @@ func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) @@ -113,13 +107,13 @@ func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) { } func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml", "--test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -134,8 +128,8 @@ func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) @@ -145,14 +139,14 @@ func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) { } func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte(`[top] + _ = ioutil.WriteFile("current.toml", []byte(`[top] test = 15`), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml", "--top.test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -167,8 +161,8 @@ func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) @@ -178,13 +172,13 @@ func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) { } func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -199,8 +193,8 @@ func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) @@ -210,13 +204,13 @@ func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) { } func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -231,8 +225,8 @@ func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) @@ -242,16 +236,16 @@ func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) { } func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -266,8 +260,8 @@ func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) err := command.Run(c) @@ -276,16 +270,16 @@ func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T } func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) defer os.Remove("current.toml") - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -300,8 +294,8 @@ func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *tes return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) err := command.Run(c) diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 127693aae7..c679fdbebc 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -1,8 +1,3 @@ -// Disabling building of toml support in cases where golang is 1.0 or 1.1 -// as the encoding library is not implemented or supported. - -// +build go1.2 - package altsrc import ( @@ -10,7 +5,7 @@ import ( "reflect" "github.com/BurntSushi/toml" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) type tomlMap struct { @@ -28,7 +23,7 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { case reflect.String: ret[key] = val.(string) case reflect.Int: - ret[key] = int(val.(int)) + ret[key] = val.(int) case reflect.Int8: ret[key] = int(val.(int8)) case reflect.Int16: @@ -50,7 +45,7 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { case reflect.Float32: ret[key] = float64(val.(float32)) case reflect.Float64: - ret[key] = float64(val.(float64)) + ret[key] = val.(float64) case reflect.Map: if tmp, err := unmarshalMap(val); err == nil { ret[key] = tmp @@ -86,7 +81,7 @@ func NewTomlSourceFromFile(file string) (InputSourceContext, error) { if err := readCommandToml(tsc.FilePath, &results); err != nil { return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error()) } - return &MapInputSource{valueMap: results.Map}, nil + return &MapInputSource{file: file, valueMap: results.Map}, nil } // NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context. diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 31f78ce589..4303e9ab3d 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -1,26 +1,20 @@ -// Disabling building of yaml support in cases where golang is 1.0 or 1.1 -// as the encoding library is not implemented or supported. - -// +build go1.2 - package altsrc import ( "flag" + "github.com/urfave/cli/v2" "io/ioutil" "os" "testing" - - "github.com/urfave/cli" ) func TestCommandYamlFileTest(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -35,8 +29,8 @@ func TestCommandYamlFileTest(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -45,15 +39,15 @@ func TestCommandYamlFileTest(t *testing.T) { } func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -68,8 +62,8 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -79,16 +73,16 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { } func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte(`top: + _ = ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) defer os.Remove("current.yaml") - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -103,8 +97,8 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -114,13 +108,13 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { } func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml", "--test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -135,8 +129,8 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -146,14 +140,14 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { } func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte(`top: + _ = ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml", "--top.test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -168,8 +162,8 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -179,13 +173,13 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { } func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -200,8 +194,8 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -211,14 +205,14 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { } func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte(`top: + _ = ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -233,8 +227,8 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -244,16 +238,16 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { } func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -268,8 +262,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -278,17 +272,17 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T } func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte(`top: + _ = ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) defer os.Remove("current.yaml") - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -303,8 +297,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *tes return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 2177c7599b..cf2f8a5c55 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -1,8 +1,3 @@ -// Disabling building of yaml support in cases where golang is 1.0 or 1.1 -// as the encoding library is not implemented or supported. - -// +build go1.2 - package altsrc import ( @@ -14,7 +9,7 @@ import ( "runtime" "strings" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" "gopkg.in/yaml.v2" ) @@ -32,7 +27,7 @@ func NewYamlSourceFromFile(file string) (InputSourceContext, error) { return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error()) } - return &MapInputSource{valueMap: results}, nil + return &MapInputSource{file: file, valueMap: results}, nil } // NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. diff --git a/app.go b/app.go index 95d2038101..d03ec5d71d 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "reflect" "sort" "time" ) @@ -13,12 +14,8 @@ import ( var ( changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) - // unused variable. commented for now. will remove in future if agreed upon by everyone - //runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) - - contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." - - errInvalidActionType = NewExitError("ERROR invalid Action type. "+ + contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." + errInvalidActionType = NewExitError("ERROR invalid Action type. "+ fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ fmt.Sprintf("See %s", appActionDeprecationURL), 2) ) @@ -41,7 +38,7 @@ type App struct { // Description of the program Description string // List of commands to execute - Commands []Command + Commands []*Command // List of flags to parse Flags []Flag // Boolean to enable bash completion commands @@ -50,9 +47,9 @@ type App struct { HideHelp bool // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool - // Populate on app startup, only gettable through method Categories() + // categories contains the categorized commands and is populated on app startup categories CommandCategories - // An action to execute when the bash-completion flag is set + // An action to execute when the shell completion flag is set BashComplete BashCompleteFunc // 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 @@ -60,12 +57,8 @@ type App struct { // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics After AfterFunc - // The action to execute when no subcommands are specified - // Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}` - // *Note*: support for the deprecated `Action` signature will be removed in a future version - Action interface{} - + Action ActionFunc // Execute this function if the proper command cannot be found CommandNotFound CommandNotFoundFunc // Execute this function if an usage error occurs @@ -73,13 +66,9 @@ type App struct { // Compilation date Compiled time.Time // List of all authors who contributed - Authors []Author + Authors []*Author // Copyright of the binary if any Copyright string - // Name of Author (Note: Use App.Authors, this is deprecated) - Author string - // Email of Author (Note: Use App.Authors, this is deprecated) - Email string // Writer writer to write output to Writer io.Writer // ErrWriter writes error output @@ -96,7 +85,7 @@ type App struct { // render custom help text by setting this variable. CustomAppHelpTemplate string // Boolean to enable short-option handling so user can combine several - // single-character bool arguements into one + // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool @@ -139,22 +128,52 @@ func (a *App) Setup() { a.didSetup = true - if a.Author != "" || a.Email != "" { - a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) + if a.Name == "" { + a.Name = filepath.Base(os.Args[0]) + } + + if a.HelpName == "" { + a.HelpName = filepath.Base(os.Args[0]) + } + + if a.Usage == "" { + a.Usage = "A new cli application" + } + + if a.Version == "" { + a.Version = "0.0.0" + } + + if a.BashComplete == nil { + a.BashComplete = DefaultAppComplete + } + + if a.Action == nil { + a.Action = helpCommand.Action + } + + if a.Compiled == (time.Time{}) { + a.Compiled = compileTime() } - var newCmds []Command + if a.Writer == nil { + a.Writer = os.Stdout + } + + var newCommands []*Command + for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } - newCmds = append(newCmds, c) + newCommands = append(newCommands, c) } - a.Commands = newCmds + a.Commands = newCommands if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } @@ -163,11 +182,11 @@ func (a *App) Setup() { a.appendFlag(VersionFlag) } - a.categories = CommandCategories{} + a.categories = newCommandCategories() for _, command := range a.Commands { - a.categories = a.categories.AddCommand(command.Category, command) + a.categories.AddCommand(command.Category, command) } - sort.Sort(a.categories) + sort.Sort(a.categories.(*commandCategories)) if a.Metadata == nil { a.Metadata = make(map[string]interface{}) @@ -249,7 +268,7 @@ func (a *App) Run(arguments []string) (err error) { defer func() { if afterErr := a.After(context); afterErr != nil { if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -282,7 +301,7 @@ func (a *App) Run(arguments []string) (err error) { } // Run default Action - err = HandleAction(a.Action, context) + err = a.Action(context) a.handleExitCoder(context, err) return err @@ -303,17 +322,20 @@ func (a *App) RunAndExitOnError() { // RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to // generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { + a.Setup() + // append help to commands if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } } - newCmds := []Command{} + var newCmds []*Command for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) @@ -379,7 +401,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if afterErr != nil { a.handleExitCoder(context, err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -406,7 +428,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } // Run default Action - err = HandleAction(a.Action, context) + err = a.Action(context) a.handleExitCoder(context, err) return err @@ -416,28 +438,21 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { func (a *App) Command(name string) *Command { for _, c := range a.Commands { if c.HasName(name) { - return &c + return c } } return nil } -// Categories returns a slice containing all the categories with the commands they contain -func (a *App) Categories() CommandCategories { - return a.categories -} - // VisibleCategories returns a slice of categories and commands that are // Hidden=false -func (a *App) VisibleCategories() []*CommandCategory { - ret := []*CommandCategory{} - for _, category := range a.categories { - if visible := func() *CommandCategory { - for _, command := range category.Commands { - if !command.Hidden { - return category - } +func (a *App) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + for _, category := range a.categories.Categories() { + if visible := func() CommandCategory { + if len(category.VisibleCommands()) > 0 { + return category } return nil }(); visible != nil { @@ -448,8 +463,8 @@ func (a *App) VisibleCategories() []*CommandCategory { } // VisibleCommands returns a slice of the Commands with Hidden=false -func (a *App) VisibleCommands() []Command { - var ret []Command +func (a *App) VisibleCommands() []*Command { + var ret []*Command for _, command := range a.Commands { if !command.Hidden { ret = append(ret, command) @@ -465,7 +480,7 @@ func (a *App) VisibleFlags() []Flag { func (a *App) hasFlag(flag Flag) bool { for _, f := range a.Flags { - if flag == f { + if reflect.DeepEqual(flag, f) { return true } } @@ -482,9 +497,15 @@ func (a *App) errWriter() io.Writer { return a.ErrWriter } -func (a *App) appendFlag(flag Flag) { - if !a.hasFlag(flag) { - a.Flags = append(a.Flags, flag) +func (a *App) appendFlag(fl Flag) { + if !hasFlag(a.Flags, fl) { + a.Flags = append(a.Flags, fl) + } +} + +func (a *App) appendCommand(c *Command) { + if !hasCommand(a.Commands, c) { + a.Commands = append(a.Commands, c) } } @@ -503,7 +524,7 @@ type Author struct { } // String makes Author comply to the Stringer interface, to allow an easy print in the templating process -func (a Author) String() string { +func (a *Author) String() string { e := "" if a.Email != "" { e = " <" + a.Email + ">" diff --git a/app_regression_test.go b/app_regression_test.go deleted file mode 100644 index 3c8681b993..0000000000 --- a/app_regression_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package cli - -import ( - "testing" -) - -// TestRegression tests a regression that was merged between versions 1.20.0 and 1.21.0 -// The included app.Run line worked in 1.20.0, and then was broken in 1.21.0. -// Relevant PR: https://github.com/urfave/cli/pull/872 -func TestVersionOneTwoOneRegression(t *testing.T) { - testData := []struct { - testCase string - appRunInput []string - skipArgReorder bool - }{ - { - testCase: "with_dash_dash", - appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "--", "docker", "image", "ls", "--no-trunc"}, - }, - { - testCase: "with_dash_dash_and_skip_reorder", - appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "--", "docker", "image", "ls", "--no-trunc"}, - skipArgReorder: true, - }, - { - testCase: "without_dash_dash", - appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}, - }, - { - testCase: "without_dash_dash_and_skip_reorder", - appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}, - skipArgReorder: true, - }, - } - for _, test := range testData { - t.Run(test.testCase, func(t *testing.T) { - // setup - app := NewApp() - app.Commands = []Command{{ - Name: "command", - SkipArgReorder: test.skipArgReorder, - Flags: []Flag{ - StringFlag{ - Name: "flagone", - }, - }, - Action: func(c *Context) error { return nil }, - }} - - // logic under test - err := app.Run(test.appRunInput) - - // assertions - if err != nil { - t.Errorf("did not expected an error, but there was one: %s", err) - } - }) - } -} diff --git a/app_test.go b/app_test.go index 33024ffeae..9d33745d79 100644 --- a/app_test.go +++ b/app_test.go @@ -27,27 +27,27 @@ func init() { } type opCounts struct { - Total, BashComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int + Total, ShellComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int } func ExampleApp_Run() { // set args for examples sake os.Args = []string{"greet", "--name", "Jeremy"} - app := NewApp() - app.Name = "greet" - app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, - } - app.Action = func(c *Context) error { - fmt.Printf("Hello %v\n", c.String("name")) - return nil + app := &App{ + Name: "greet", + Flags: []Flag{ + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + }, + Action: func(c *Context) error { + fmt.Printf("Hello %v\n", c.String("name")) + return nil + }, + UsageText: "app [first_arg] [second_arg]", + Authors: []*Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}}, } - app.UsageText = "app [first_arg] [second_arg]" - app.Author = "Harrison" - app.Email = "harrison@lolwut.com" - app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} - _ = app.Run(os.Args) + + app.Run(os.Args) // Output: // Hello Jeremy } @@ -55,30 +55,31 @@ func ExampleApp_Run() { func ExampleApp_Run_subcommand() { // set args for examples sake os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} - app := NewApp() - app.Name = "say" - app.Commands = []Command{ - { - Name: "hello", - Aliases: []string{"hi"}, - Usage: "use it to see a description", - Description: "This is how we describe hello the function", - Subcommands: []Command{ - { - Name: "english", - Aliases: []string{"en"}, - Usage: "sends a greeting in english", - Description: "greets someone in english", - Flags: []Flag{ - StringFlag{ - Name: "name", - Value: "Bob", - Usage: "Name of the person to greet", + app := &App{ + Name: "say", + Commands: []*Command{ + { + Name: "hello", + Aliases: []string{"hi"}, + Usage: "use it to see a description", + Description: "This is how we describe hello the function", + Subcommands: []*Command{ + { + Name: "english", + Aliases: []string{"en"}, + Usage: "sends a greeting in english", + Description: "greets someone in english", + Flags: []Flag{ + &StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, + }, + Action: func(c *Context) error { + fmt.Println("Hello,", c.String("name")) + return nil }, - }, - Action: func(c *Context) error { - fmt.Println("Hello,", c.String("name")) - return nil }, }, }, @@ -94,26 +95,27 @@ func ExampleApp_Run_appHelp() { // set args for examples sake os.Args = []string{"greet", "help"} - app := NewApp() - app.Name = "greet" - app.Version = "0.1.0" - app.Description = "This is how we describe greet the app" - app.Authors = []Author{ - {Name: "Harrison", Email: "harrison@lolwut.com"}, - {Name: "Oliver Allen", Email: "oliver@toyshop.com"}, - } - app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, - } - app.Commands = []Command{ - { - Name: "describeit", - Aliases: []string{"d"}, - Usage: "use it to see a description", - Description: "This is how we describe describeit the function", - Action: func(c *Context) error { - fmt.Printf("i like to describe things") - return nil + app := &App{ + Name: "greet", + Version: "0.1.0", + Description: "This is how we describe greet the app", + Authors: []*Author{ + {Name: "Harrison", Email: "harrison@lolwut.com"}, + {Name: "Oliver Allen", Email: "oliver@toyshop.com"}, + }, + Flags: []Flag{ + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + }, + Commands: []*Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, }, }, } @@ -141,28 +143,29 @@ func ExampleApp_Run_appHelp() { // // GLOBAL OPTIONS: // --name value a name to say (default: "bob") - // --help, -h show help - // --version, -v print the version + // --help, -h show help (default: false) + // --version, -v print the version (default: false) } func ExampleApp_Run_commandHelp() { // set args for examples sake os.Args = []string{"greet", "h", "describeit"} - app := NewApp() - app.Name = "greet" - app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, - } - app.Commands = []Command{ - { - Name: "describeit", - Aliases: []string{"d"}, - Usage: "use it to see a description", - Description: "This is how we describe describeit the function", - Action: func(c *Context) error { - fmt.Printf("i like to describe things") - return nil + app := &App{ + Name: "greet", + Flags: []Flag{ + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + }, + Commands: []*Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, }, }, } @@ -184,40 +187,47 @@ func ExampleApp_Run_noAction() { _ = app.Run([]string{"greet"}) // Output: // NAME: - // greet + // greet - A new cli application // // USAGE: - // [global options] command [command options] [arguments...] + // greet [global options] command [command options] [arguments...] + // + // VERSION: + // 0.0.0 // // COMMANDS: // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: - // --help, -h show help - // --version, -v print the version + // --help, -h show help (default: false) + // --version, -v print the version (default: false) } func ExampleApp_Run_subcommandNoAction() { - app := App{} - app.Name = "greet" - app.Commands = []Command{ - { - Name: "describeit", - Aliases: []string{"d"}, - Usage: "use it to see a description", - Description: "This is how we describe describeit the function", + app := &App{ + Name: "greet", + Commands: []*Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + }, }, } _ = app.Run([]string{"greet", "describeit"}) // Output: // NAME: - // describeit - use it to see a description + // greet describeit - use it to see a description // // USAGE: - // describeit [arguments...] + // greet describeit [command options] [arguments...] // // DESCRIPTION: // This is how we describe describeit the function + // + // OPTIONS: + // --help, -h show help (default: false) } @@ -228,11 +238,13 @@ func ExampleApp_Run_bashComplete_withShortFlag() { app.Name = "greet" app.EnableBashCompletion = true app.Flags = []Flag{ - IntFlag{ - Name: "other,o", + &IntFlag{ + Name: "other", + Aliases: []string{"o"}, }, - StringFlag{ - Name: "xyz,x", + &StringFlag{ + Name: "xyz", + Aliases: []string{"x"}, }, } @@ -255,16 +267,18 @@ func ExampleApp_Run_bashComplete_withLongFlag() { app.Name = "greet" app.EnableBashCompletion = true app.Flags = []Flag{ - IntFlag{ - Name: "other,o", + &IntFlag{ + Name: "other", + Aliases: []string{"o"}, }, - StringFlag{ - Name: "xyz,x", + &StringFlag{ + Name: "xyz", + Aliases: []string{"x"}, }, - StringFlag{ + &StringFlag{ Name: "some-flag,s", }, - StringFlag{ + &StringFlag{ Name: "similar-flag", }, } @@ -281,19 +295,21 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() { app.Name = "greet" app.EnableBashCompletion = true app.Flags = []Flag{ - IntFlag{ - Name: "int-flag,i", + &IntFlag{ + Name: "int-flag", + Aliases: []string{"i"}, }, - StringFlag{ - Name: "string,s", + &StringFlag{ + Name: "string", + Aliases: []string{"s"}, }, - StringFlag{ + &StringFlag{ Name: "string-flag-2", }, - StringFlag{ + &StringFlag{ Name: "similar-flag", }, - StringFlag{ + &StringFlag{ Name: "some-flag", }, } @@ -305,29 +321,31 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() { } func ExampleApp_Run_bashComplete() { + // set args for examples sake // set args for examples sake os.Args = []string{"greet", "--generate-bash-completion"} - app := NewApp() - app.Name = "greet" - app.EnableBashCompletion = true - app.Commands = []Command{ - { - Name: "describeit", - Aliases: []string{"d"}, - Usage: "use it to see a description", - Description: "This is how we describe describeit the function", - Action: func(c *Context) error { - fmt.Printf("i like to describe things") - return nil - }, - }, { - Name: "next", - Usage: "next example", - Description: "more stuff to see when generating bash completion", - Action: func(c *Context) error { - fmt.Printf("the next example") - return nil + app := &App{ + Name: "greet", + EnableBashCompletion: true, + Commands: []*Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, + }, { + Name: "next", + Usage: "next example", + Description: "more stuff to see when generating shell completion", + Action: func(c *Context) error { + fmt.Printf("the next example") + return nil + }, }, }, } @@ -349,7 +367,7 @@ func ExampleApp_Run_zshComplete() { app := NewApp() app.Name = "greet" app.EnableBashCompletion = true - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "describeit", Aliases: []string{"d"}, @@ -382,10 +400,11 @@ func ExampleApp_Run_zshComplete() { func TestApp_Run(t *testing.T) { s := "" - app := NewApp() - app.Action = func(c *Context) error { - s = s + c.Args().First() - return nil + app := &App{ + Action: func(c *Context) error { + s = s + c.Args().First() + return nil + }, } err := app.Run([]string{"command", "foo"}) @@ -408,12 +427,11 @@ var commandAppTests = []struct { } func TestApp_Command(t *testing.T) { - app := NewApp() - fooCommand := Command{Name: "foobar", Aliases: []string{"f"}} - batCommand := Command{Name: "batbaz", Aliases: []string{"b"}} - app.Commands = []Command{ - fooCommand, - batCommand, + app := &App{ + Commands: []*Command{ + {Name: "foobar", Aliases: []string{"f"}}, + {Name: "batbaz", Aliases: []string{"b"}}, + }, } for _, test := range commandAppTests { @@ -427,81 +445,26 @@ func TestApp_Setup_defaultsWriter(t *testing.T) { expect(t, app.Writer, os.Stdout) } -func TestApp_CommandWithArgBeforeFlags(t *testing.T) { - var parsedOption, firstArg string - - app := NewApp() - command := Command{ - Name: "cmd", - Flags: []Flag{ - StringFlag{Name: "option", Value: "", Usage: "some option"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - firstArg = c.Args().First() - return nil - }, - } - app.Commands = []Command{command} - - _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) - - expect(t, parsedOption, "my-option") - expect(t, firstArg, "my-arg") -} - -func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) { - var parsedOption, parsedSecondOption, firstArg string - var parsedBool, parsedSecondBool bool - - app := NewApp() - command := Command{ - Name: "cmd", - Flags: []Flag{ - StringFlag{Name: "option", Value: "", Usage: "some option"}, - StringFlag{Name: "secondOption", Value: "", Usage: "another option"}, - BoolFlag{Name: "boolflag", Usage: "some bool"}, - BoolFlag{Name: "b", Usage: "another bool"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - parsedSecondOption = c.String("secondOption") - parsedBool = c.Bool("boolflag") - parsedSecondBool = c.Bool("b") - firstArg = c.Args().First() - return nil - }, - } - app.Commands = []Command{command} - - _ = app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"}) - - expect(t, parsedOption, "my-option") - expect(t, parsedSecondOption, "fancy-option") - expect(t, parsedBool, true) - expect(t, parsedSecondBool, true) - expect(t, firstArg, "my-arg") -} - func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context - a := NewApp() - a.Commands = []Command{ - { - Name: "foo", - Action: func(c *Context) error { - context = c - return nil - }, - Flags: []Flag{ - StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", + a := &App{ + Commands: []*Command{ + { + Name: "foo", + Action: func(c *Context) error { + context = c + return nil + }, + Flags: []Flag{ + &StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, }, + Before: func(_ *Context) error { return nil }, }, - Before: func(_ *Context) error { return nil }, }, } _ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) @@ -512,8 +475,9 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) { a := App{ + Name: "cmd", Flags: []Flag{ - StringFlag{Name: "--foo"}, + &StringFlag{Name: "--foo"}, }, Writer: bytes.NewBufferString(""), } @@ -529,87 +493,94 @@ func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string - var args []string - - app := NewApp() - command := Command{ - Name: "cmd", - Flags: []Flag{ - StringFlag{Name: "option", Value: "", Usage: "some option"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - args = c.Args() - return nil + var args Args + + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Flags: []Flag{ + &StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *Context) error { + parsedOption = c.String("option") + args = c.Args() + return nil + }, + }, }, } - app.Commands = []Command{command} - _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) + _ = app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}) expect(t, parsedOption, "my-option") - expect(t, args[0], "my-arg") - expect(t, args[1], "--") - expect(t, args[2], "--notARealFlag") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "--") + expect(t, args.Get(2), "--notARealFlag") } func TestApp_CommandWithDash(t *testing.T) { - var args []string - - app := NewApp() - command := Command{ - Name: "cmd", - Action: func(c *Context) error { - args = c.Args() - return nil + var args Args + + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + }, }, } - app.Commands = []Command{command} _ = app.Run([]string{"", "cmd", "my-arg", "-"}) - expect(t, args[0], "my-arg") - expect(t, args[1], "-") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "-") } func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { - var args []string - - app := NewApp() - command := Command{ - Name: "cmd", - Action: func(c *Context) error { - args = c.Args() - return nil + var args Args + + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + }, }, } - app.Commands = []Command{command} _ = app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) - expect(t, args[0], "my-arg") - expect(t, args[1], "--") - expect(t, args[2], "notAFlagAtAll") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "--") + expect(t, args.Get(2), "notAFlagAtAll") } func TestApp_VisibleCommands(t *testing.T) { - app := NewApp() - app.Commands = []Command{ - { - Name: "frob", - HelpName: "foo frob", - Action: func(_ *Context) error { return nil }, - }, - { - Name: "frib", - HelpName: "foo frib", - Hidden: true, - Action: func(_ *Context) error { return nil }, + app := &App{ + Commands: []*Command{ + { + Name: "frob", + HelpName: "foo frob", + Action: func(_ *Context) error { return nil }, + }, + { + Name: "frib", + HelpName: "foo frib", + Hidden: true, + Action: func(_ *Context) error { return nil }, + }, }, } app.Setup() - expected := []Command{ + expected := []*Command{ app.Commands[0], app.Commands[2], // help } @@ -623,14 +594,22 @@ func TestApp_VisibleCommands(t *testing.T) { expect(t, fmt.Sprintf("%p", expectedCommand.Action), fmt.Sprintf("%p", actualCommand.Action)) } - // nil out funcs, as they cannot be compared - // (https://github.com/golang/go/issues/8554) - expectedCommand.Action = nil - actualCommand.Action = nil - - if !reflect.DeepEqual(expectedCommand, actualCommand) { - t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand) - } + func() { + // nil out funcs, as they cannot be compared + // (https://github.com/golang/go/issues/8554) + expectedAction := expectedCommand.Action + actualAction := actualCommand.Action + defer func() { + expectedCommand.Action = expectedAction + actualCommand.Action = actualAction + }() + expectedCommand.Action = nil + actualCommand.Action = nil + + if !reflect.DeepEqual(expectedCommand, actualCommand) { + t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand) + } + }() } } @@ -642,9 +621,9 @@ func TestApp_UseShortOptionHandling(t *testing.T) { app := NewApp() app.UseShortOptionHandling = true app.Flags = []Flag{ - BoolFlag{Name: "one, o"}, - BoolFlag{Name: "two, t"}, - StringFlag{Name: "name, n"}, + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, } app.Action = func(c *Context) error { one = c.Bool("one") @@ -653,7 +632,7 @@ func TestApp_UseShortOptionHandling(t *testing.T) { return nil } - app.Run([]string{"", "-on", expected}) + _ = app.Run([]string{"", "-on", expected}) expect(t, one, true) expect(t, two, false) expect(t, name, expected) @@ -663,7 +642,7 @@ func TestApp_UseShortOptionHandling_missing_value(t *testing.T) { app := NewApp() app.UseShortOptionHandling = true app.Flags = []Flag{ - StringFlag{Name: "name, n"}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, } err := app.Run([]string{"", "-n"}) @@ -677,12 +656,12 @@ func TestApp_UseShortOptionHandlingCommand(t *testing.T) { app := NewApp() app.UseShortOptionHandling = true - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - BoolFlag{Name: "one, o"}, - BoolFlag{Name: "two, t"}, - StringFlag{Name: "name, n"}, + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, }, Action: func(c *Context) error { one = c.Bool("one") @@ -691,9 +670,9 @@ func TestApp_UseShortOptionHandlingCommand(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} - app.Run([]string{"", "cmd", "-on", expected}) + _ = app.Run([]string{"", "cmd", "-on", expected}) expect(t, one, true) expect(t, two, false) expect(t, name, expected) @@ -702,13 +681,13 @@ func TestApp_UseShortOptionHandlingCommand(t *testing.T) { func TestApp_UseShortOptionHandlingCommand_missing_value(t *testing.T) { app := NewApp() app.UseShortOptionHandling = true - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - StringFlag{Name: "name, n"}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} err := app.Run([]string{"", "cmd", "-n"}) expect(t, err, errors.New("flag needs an argument: -n")) @@ -721,15 +700,15 @@ func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { app := NewApp() app.UseShortOptionHandling = true - command := Command{ + command := &Command{ Name: "cmd", } - subCommand := Command{ + subCommand := &Command{ Name: "sub", Flags: []Flag{ - BoolFlag{Name: "one, o"}, - BoolFlag{Name: "two, t"}, - StringFlag{Name: "name, n"}, + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, }, Action: func(c *Context) error { one = c.Bool("one") @@ -738,8 +717,8 @@ func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { return nil }, } - command.Subcommands = []Command{subCommand} - app.Commands = []Command{command} + command.Subcommands = []*Command{subCommand} + app.Commands = []*Command{command} err := app.Run([]string{"", "cmd", "sub", "-on", expected}) expect(t, err, nil) @@ -751,17 +730,17 @@ func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) { app := NewApp() app.UseShortOptionHandling = true - command := Command{ + command := &Command{ Name: "cmd", } - subCommand := Command{ + subCommand := &Command{ Name: "sub", Flags: []Flag{ - StringFlag{Name: "name, n"}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, }, } - command.Subcommands = []Command{subCommand} - app.Commands = []Command{command} + command.Subcommands = []*Command{subCommand} + app.Commands = []*Command{command} err := app.Run([]string{"", "cmd", "sub", "-n"}) expect(t, err, errors.New("flag needs an argument: -n")) @@ -770,13 +749,14 @@ func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) { func TestApp_Float64Flag(t *testing.T) { var meters float64 - app := NewApp() - app.Flags = []Flag{ - Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, - } - app.Action = func(c *Context) error { - meters = c.Float64("height") - return nil + app := &App{ + Flags: []Flag{ + &Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, + }, + Action: func(c *Context) error { + meters = c.Float64("height") + return nil + }, } _ = app.Run([]string{"", "--height", "1.93"}) @@ -787,22 +767,24 @@ func TestApp_ParseSliceFlags(t *testing.T) { var parsedIntSlice []int var parsedStringSlice []string - app := NewApp() - command := Command{ - Name: "cmd", - Flags: []Flag{ - 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) error { - parsedIntSlice = c.IntSlice("p") - parsedStringSlice = c.StringSlice("ip") - return nil + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Flags: []Flag{ + &IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"}, + &StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"}, + }, + Action: func(c *Context) error { + parsedIntSlice = c.IntSlice("p") + parsedStringSlice = c.StringSlice("ip") + return nil + }, + }, }, } - app.Commands = []Command{command} - _ = app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) + _ = app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) IntsEquals := func(a, b []int) bool { if len(a) != len(b) { @@ -843,22 +825,24 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { var parsedIntSlice []int var parsedStringSlice []string - app := NewApp() - command := Command{ - Name: "cmd", - Flags: []Flag{ - IntSliceFlag{Name: "a", Usage: "set numbers"}, - StringSliceFlag{Name: "str", Usage: "set strings"}, - }, - Action: func(c *Context) error { - parsedIntSlice = c.IntSlice("a") - parsedStringSlice = c.StringSlice("str") - return nil + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Flags: []Flag{ + &IntSliceFlag{Name: "a", Usage: "set numbers"}, + &StringSliceFlag{Name: "str", Usage: "set strings"}, + }, + Action: func(c *Context) error { + parsedIntSlice = c.IntSlice("a") + parsedStringSlice = c.StringSlice("str") + return nil + }, + }, }, } - app.Commands = []Command{command} - _ = app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) + _ = app.Run([]string{"", "cmd", "-a", "2", "-str", "A"}) var expectedIntSlice = []int{2} var expectedStringSlice = []string{"A"} @@ -873,7 +857,8 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { } func TestApp_DefaultStdout(t *testing.T) { - app := NewApp() + app := &App{} + app.Setup() if app.Writer != os.Stdout { t.Error("Default output writer not set.") @@ -901,9 +886,10 @@ func (fw *mockWriter) GetWritten() (b []byte) { func TestApp_SetStdout(t *testing.T) { w := &mockWriter{} - app := NewApp() - app.Name = "test" - app.Writer = w + app := &App{ + Name: "test", + Writer: w, + } err := app.Run([]string{"help"}) @@ -921,32 +907,30 @@ func TestApp_BeforeFunc(t *testing.T) { beforeError := fmt.Errorf("fail") var err error - app := NewApp() - - app.Before = func(c *Context) error { - counts.Total++ - counts.Before = counts.Total - s := c.String("opt") - if s == "fail" { - return beforeError - } - - return nil - } + app := &App{ + Before: func(c *Context) error { + counts.Total++ + counts.Before = counts.Total + s := c.String("opt") + if s == "fail" { + return beforeError + } - app.Commands = []Command{ - { - Name: "sub", - Action: func(c *Context) error { - counts.Total++ - counts.SubCommand = counts.Total - return nil + return nil + }, + Commands: []*Command{ + { + Name: "sub", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, }, }, - } - - app.Flags = []Flag{ - StringFlag{Name: "opt"}, + Flags: []Flag{ + &StringFlag{Name: "opt"}, + }, } // run with the Before() func succeeding @@ -1013,32 +997,30 @@ func TestApp_AfterFunc(t *testing.T) { afterError := fmt.Errorf("fail") var err error - app := NewApp() - - app.After = func(c *Context) error { - counts.Total++ - counts.After = counts.Total - s := c.String("opt") - if s == "fail" { - return afterError - } - - return nil - } + app := &App{ + After: func(c *Context) error { + counts.Total++ + counts.After = counts.Total + s := c.String("opt") + if s == "fail" { + return afterError + } - app.Commands = []Command{ - { - Name: "sub", - Action: func(c *Context) error { - counts.Total++ - counts.SubCommand = counts.Total - return nil + return nil + }, + Commands: []*Command{ + { + Name: "sub", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, }, }, - } - - app.Flags = []Flag{ - StringFlag{Name: "opt"}, + Flags: []Flag{ + &StringFlag{Name: "opt"}, + }, } // run with the After() func succeeding @@ -1082,10 +1064,9 @@ func TestAppNoHelpFlag(t *testing.T) { HelpFlag = oldFlag }() - HelpFlag = BoolFlag{} + HelpFlag = nil - app := NewApp() - app.Writer = ioutil.Discard + app := &App{Writer: ioutil.Discard} err := app.Run([]string{"test", "-h"}) if err != flag.ErrHelp { @@ -1098,33 +1079,33 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { testCase string appFlags []Flag appRunInput []string - appCommands []Command + appCommands []*Command expectedAnError bool }{ // assertion: empty input, when a required flag is present, errors { testCase: "error_case_empty_input_with_required_flag_on_app", appRunInput: []string{"myCLI"}, - appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, expectedAnError: true, }, { testCase: "error_case_empty_input_with_required_flag_on_command", appRunInput: []string{"myCLI", "myCommand"}, - appCommands: []Command{{ + appCommands: []*Command{{ Name: "myCommand", - Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, }}, expectedAnError: true, }, { testCase: "error_case_empty_input_with_required_flag_on_subcommand", appRunInput: []string{"myCLI", "myCommand", "mySubCommand"}, - appCommands: []Command{{ + appCommands: []*Command{{ Name: "myCommand", - Subcommands: []Command{{ + Subcommands: []*Command{{ Name: "mySubCommand", - Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, }}, }}, expectedAnError: true, @@ -1133,24 +1114,24 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "valid_case_help_input_with_required_flag_on_app", appRunInput: []string{"myCLI", "--help"}, - appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, }, { testCase: "valid_case_help_input_with_required_flag_on_command", appRunInput: []string{"myCLI", "myCommand", "--help"}, - appCommands: []Command{{ + appCommands: []*Command{{ Name: "myCommand", - Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, }}, }, { testCase: "valid_case_help_input_with_required_flag_on_subcommand", appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--help"}, - appCommands: []Command{{ + appCommands: []*Command{{ Name: "myCommand", - Subcommands: []Command{{ + Subcommands: []*Command{{ Name: "mySubCommand", - Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, }}, }}, }, @@ -1158,26 +1139,26 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "error_case_optional_input_with_required_flag_on_app", appRunInput: []string{"myCLI", "--optional", "cats"}, - appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, + appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}}, expectedAnError: true, }, { testCase: "error_case_optional_input_with_required_flag_on_command", appRunInput: []string{"myCLI", "myCommand", "--optional", "cats"}, - appCommands: []Command{{ + appCommands: []*Command{{ Name: "myCommand", - Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}}, }}, expectedAnError: true, }, { testCase: "error_case_optional_input_with_required_flag_on_subcommand", appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--optional", "cats"}, - appCommands: []Command{{ + appCommands: []*Command{{ Name: "myCommand", - Subcommands: []Command{{ + Subcommands: []*Command{{ Name: "mySubCommand", - Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}}, + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}}, }}, }}, expectedAnError: true, @@ -1186,24 +1167,24 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) { { testCase: "valid_case_required_flag_input_on_app", appRunInput: []string{"myCLI", "--requiredFlag", "cats"}, - appFlags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, }, { testCase: "valid_case_required_flag_input_on_command", appRunInput: []string{"myCLI", "myCommand", "--requiredFlag", "cats"}, - appCommands: []Command{{ + appCommands: []*Command{{ Name: "myCommand", - Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, }}, }, { testCase: "valid_case_required_flag_input_on_subcommand", appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--requiredFlag", "cats"}, - appCommands: []Command{{ + appCommands: []*Command{{ Name: "myCommand", - Subcommands: []Command{{ + Subcommands: []*Command{{ Name: "mySubCommand", - Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}}, + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, }}, }}, }, @@ -1243,7 +1224,7 @@ func TestAppHelpPrinter(t *testing.T) { wasCalled = true } - app := NewApp() + app := &App{} _ = app.Run([]string{"-h"}) if wasCalled == false { @@ -1262,7 +1243,7 @@ func TestApp_VersionPrinter(t *testing.T) { wasCalled = true } - app := NewApp() + app := &App{} ctx := NewContext(app, nil, nil) ShowVersion(ctx) @@ -1273,20 +1254,19 @@ func TestApp_VersionPrinter(t *testing.T) { func TestApp_CommandNotFound(t *testing.T) { counts := &opCounts{} - app := NewApp() - - app.CommandNotFound = func(c *Context, command string) { - counts.Total++ - counts.CommandNotFound = counts.Total - } - - app.Commands = []Command{ - { - Name: "bar", - Action: func(c *Context) error { - counts.Total++ - counts.SubCommand = counts.Total - return nil + app := &App{ + CommandNotFound: func(c *Context, command string) { + counts.Total++ + counts.CommandNotFound = counts.Total + }, + Commands: []*Command{ + { + Name: "bar", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, }, }, } @@ -1303,17 +1283,18 @@ func TestApp_OrderOfOperations(t *testing.T) { resetCounts := func() { counts = &opCounts{} } - app := NewApp() - app.EnableBashCompletion = true - app.BashComplete = func(c *Context) { - counts.Total++ - counts.BashComplete = counts.Total - } - - app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { - counts.Total++ - counts.OnUsageError = counts.Total - return errors.New("hay OnUsageError") + app := &App{ + EnableBashCompletion: true, + BashComplete: func(c *Context) { + _, _ = fmt.Fprintf(os.Stderr, "---> BashComplete(%#v)\n", c) + counts.Total++ + counts.ShellComplete = counts.Total + }, + OnUsageError: func(c *Context, err error, isSubcommand bool) error { + counts.Total++ + counts.OnUsageError = counts.Total + return errors.New("hay OnUsageError") + }, } beforeNoError := func(c *Context) error { @@ -1347,7 +1328,7 @@ func TestApp_OrderOfOperations(t *testing.T) { } app.After = afterNoError - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Action: func(c *Context) error { @@ -1370,8 +1351,8 @@ func TestApp_OrderOfOperations(t *testing.T) { resetCounts() - _ = app.Run([]string{"command", "--generate-bash-completion"}) - expect(t, counts.BashComplete, 1) + _ = app.Run([]string{"command", fmt.Sprintf("--%s", "generate-bash-completion")}) + expect(t, counts.ShellComplete, 1) expect(t, counts.Total, 1) resetCounts() @@ -1449,26 +1430,26 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { for _, flagSet := range subcommandHelpTopics { t.Logf("==> checking with flags %v", flagSet) - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf - subCmdBar := Command{ + subCmdBar := &Command{ Name: "bar", Usage: "does bar things", } - subCmdBaz := Command{ + subCmdBaz := &Command{ Name: "baz", Usage: "does baz things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "descriptive wall of text about how it does foo things", - Subcommands: []Command{subCmdBar, subCmdBaz}, + Subcommands: []*Command{subCmdBar, subCmdBaz}, Action: func(c *Context) error { return nil }, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run(flagSet) if err != nil { @@ -1476,7 +1457,7 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } output := buf.String() - t.Logf("output: %q\n", buf.Bytes()) + //t.Logf("output: %q\n", buf.Bytes()) if strings.Contains(output, "No help topic for") { t.Errorf("expect a help topic, got none: \n%q", output) @@ -1495,20 +1476,20 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } func TestApp_Run_SubcommandFullPath(t *testing.T) { - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -1516,30 +1497,33 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "command foo bar - does bar things") { - t.Errorf("expected full path to subcommand: %s", output) + expected := "command foo bar - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "command foo bar [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "command foo bar [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } func TestApp_Run_SubcommandHelpName(t *testing.T) { - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", HelpName: "custom", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -1547,30 +1531,34 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "custom - does bar things") { - t.Errorf("expected HelpName for subcommand: %s", output) + + expected := "custom - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "custom [arguments...]") { - t.Errorf("expected HelpName to subcommand: %s", output) + + expected = "custom [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } func TestApp_Run_CommandHelpName(t *testing.T) { - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", HelpName: "custom", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -1578,30 +1566,34 @@ func TestApp_Run_CommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "command foo bar - does bar things") { - t.Errorf("expected full path to subcommand: %s", output) + + expected := "command foo bar - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "command foo bar [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "command foo bar [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf app.Name = "base" - subCmd := Command{ + subCmd := &Command{ Name: "bar", HelpName: "custom", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "--help"}) if err != nil { @@ -1609,11 +1601,15 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "base foo - foo commands") { - t.Errorf("expected full path to subcommand: %s", output) + + expected := "base foo - foo commands" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %q", expected, output) } - if !strings.Contains(output, "base foo command [command options] [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "base foo command [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %q", expected, output) } } @@ -1625,13 +1621,14 @@ func TestApp_Run_Help(t *testing.T) { t.Logf("==> checking with arguments %v", args) - app := NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Writer = buf - app.Action = func(c *Context) error { - buf.WriteString("boom I say!") - return nil + app := &App{ + Name: "boom", + Usage: "make an explosive entrance", + Writer: buf, + Action: func(c *Context) error { + buf.WriteString("boom I say!") + return nil + }, } err := app.Run(args) @@ -1656,14 +1653,15 @@ func TestApp_Run_Version(t *testing.T) { t.Logf("==> checking with arguments %v", args) - app := NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Version = "0.1.0" - app.Writer = buf - app.Action = func(c *Context) error { - buf.WriteString("boom I say!") - return nil + app := &App{ + Name: "boom", + Usage: "make an explosive entrance", + Version: "0.1.0", + Writer: buf, + Action: func(c *Context) error { + buf.WriteString("boom I say!") + return nil + }, } err := app.Run(args) @@ -1681,49 +1679,52 @@ func TestApp_Run_Version(t *testing.T) { } func TestApp_Run_Categories(t *testing.T) { - app := NewApp() - app.Name = "categories" - app.HideHelp = true - app.Commands = []Command{ - { - Name: "command1", - Category: "1", - }, - { - Name: "command2", - Category: "1", - }, - { - Name: "command3", - Category: "2", + buf := new(bytes.Buffer) + + app := &App{ + Name: "categories", + HideHelp: true, + Commands: []*Command{ + { + Name: "command1", + Category: "1", + }, + { + Name: "command2", + Category: "1", + }, + { + Name: "command3", + Category: "2", + }, }, + Writer: buf, } - buf := new(bytes.Buffer) - app.Writer = buf _ = app.Run([]string{"categories"}) - expect := CommandCategories{ - &CommandCategory{ - Name: "1", - Commands: []Command{ + expect := commandCategories([]*commandCategory{ + { + name: "1", + commands: []*Command{ app.Commands[0], app.Commands[1], }, }, - &CommandCategory{ - Name: "2", - Commands: []Command{ + { + name: "2", + commands: []*Command{ app.Commands[2], }, }, - } - if !reflect.DeepEqual(app.Categories(), expect) { - t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) + }) + + if !reflect.DeepEqual(app.categories, &expect) { + t.Fatalf("expected categories %#v, to equal %#v", app.categories, &expect) } output := buf.String() - t.Logf("output: %q\n", buf.Bytes()) + //t.Logf("output: %q\n", buf.Bytes()) if !strings.Contains(output, "1:\n command1") { t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) @@ -1731,38 +1732,39 @@ func TestApp_Run_Categories(t *testing.T) { } func TestApp_VisibleCategories(t *testing.T) { - app := NewApp() - app.Name = "visible-categories" - app.HideHelp = true - app.Commands = []Command{ - { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - }, - { - Name: "command2", - Category: "2", - HelpName: "foo command2", - }, - { - Name: "command3", - Category: "3", - HelpName: "foo command3", + app := &App{ + Name: "visible-categories", + HideHelp: true, + Commands: []*Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + }, }, } - expected := []*CommandCategory{ - { - Name: "2", - Commands: []Command{ + expected := []CommandCategory{ + &commandCategory{ + name: "2", + commands: []*Command{ app.Commands[1], }, }, - { - Name: "3", - Commands: []Command{ + &commandCategory{ + name: "3", + commands: []*Command{ app.Commands[2], }, }, @@ -1771,33 +1773,34 @@ func TestApp_VisibleCategories(t *testing.T) { app.Setup() expect(t, expected, app.VisibleCategories()) - app = NewApp() - app.Name = "visible-categories" - app.HideHelp = true - app.Commands = []Command{ - { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - }, - { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, - }, - { - Name: "command3", - Category: "3", - HelpName: "foo command3", + app = &App{ + Name: "visible-categories", + HideHelp: true, + Commands: []*Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + }, }, } - expected = []*CommandCategory{ - { - Name: "3", - Commands: []Command{ + expected = []CommandCategory{ + &commandCategory{ + name: "3", + commands: []*Command{ app.Commands[2], }, }, @@ -1806,41 +1809,41 @@ func TestApp_VisibleCategories(t *testing.T) { app.Setup() expect(t, expected, app.VisibleCategories()) - app = NewApp() - app.Name = "visible-categories" - app.HideHelp = true - app.Commands = []Command{ - { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - }, - { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, - }, - { - Name: "command3", - Category: "3", - HelpName: "foo command3", - Hidden: true, + app = &App{ + Name: "visible-categories", + HideHelp: true, + Commands: []*Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, + }, }, } - expected = []*CommandCategory{} - app.Setup() - expect(t, expected, app.VisibleCategories()) + expect(t, []CommandCategory{}, app.VisibleCategories()) } func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { - app := NewApp() - app.Action = func(c *Context) error { return nil } - app.Before = func(c *Context) error { return fmt.Errorf("before error") } - app.After = func(c *Context) error { return fmt.Errorf("after error") } + app := &App{ + Action: func(c *Context) error { return nil }, + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, + } err := app.Run([]string{"foo"}) if err == nil { @@ -1856,17 +1859,18 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { } func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { - app := NewApp() - app.Commands = []Command{ - { - Subcommands: []Command{ - { - Name: "sub", + app := &App{ + Commands: []*Command{ + { + Subcommands: []*Command{ + { + Name: "sub", + }, }, + 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) error { return fmt.Errorf("before error") }, - After: func(c *Context) error { return fmt.Errorf("after error") }, }, } @@ -1884,22 +1888,23 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { } func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { - app := NewApp() - app.Flags = []Flag{ - IntFlag{Name: "flag"}, - } - app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { - if isSubcommand { - t.Errorf("Expect no subcommand") - } - if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { - t.Errorf("Expect an invalid value error, but got \"%v\"", err) - } - return errors.New("intercepted: " + err.Error()) - } - app.Commands = []Command{ - { - Name: "bar", + app := &App{ + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect no subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + Commands: []*Command{ + { + Name: "bar", + }, }, } @@ -1914,22 +1919,23 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { } func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { - app := NewApp() - app.Flags = []Flag{ - IntFlag{Name: "flag"}, - } - app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { - if isSubcommand { - t.Errorf("Expect subcommand") - } - if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { - t.Errorf("Expect an invalid value error, but got \"%v\"", err) - } - return errors.New("intercepted: " + err.Error()) - } - app.Commands = []Command{ - { - Name: "bar", + app := &App{ + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + Commands: []*Command{ + { + Name: "bar", + }, }, } @@ -1954,8 +1960,8 @@ func (c *customBoolFlag) String() string { return "***" + c.Nombre + "***" } -func (c *customBoolFlag) GetName() string { - return c.Nombre +func (c *customBoolFlag) Names() []string { + return []string{c.Nombre} } func (c *customBoolFlag) TakesValue() bool { @@ -1970,13 +1976,19 @@ func (c *customBoolFlag) GetUsage() string { return "usage" } -func (c *customBoolFlag) Apply(set *flag.FlagSet) { +func (c *customBoolFlag) Apply(set *flag.FlagSet) error { set.String(c.Nombre, c.Nombre, "") + return nil +} + +func (c *customBoolFlag) IsSet() bool { + return false } func TestCustomFlagsUnused(t *testing.T) { - app := NewApp() - app.Flags = []Flag{&customBoolFlag{"custom"}} + app := &App{ + Flags: []Flag{&customBoolFlag{"custom"}}, + } err := app.Run([]string{"foo"}) if err != nil { @@ -1985,8 +1997,9 @@ func TestCustomFlagsUnused(t *testing.T) { } func TestCustomFlagsUsed(t *testing.T) { - app := NewApp() - app.Flags = []Flag{&customBoolFlag{"custom"}} + app := &App{ + Flags: []Flag{&customBoolFlag{"custom"}}, + } err := app.Run([]string{"foo", "--custom=bar"}) if err != nil { @@ -1995,12 +2008,12 @@ func TestCustomFlagsUsed(t *testing.T) { } func TestCustomHelpVersionFlags(t *testing.T) { - app := NewApp() + app := &App{} // Be sure to reset the global flags defer func(helpFlag Flag, versionFlag Flag) { - HelpFlag = helpFlag - VersionFlag = versionFlag + HelpFlag = helpFlag.(*BoolFlag) + VersionFlag = versionFlag.(*BoolFlag) }(HelpFlag, VersionFlag) HelpFlag = &customBoolFlag{"help-custom"} @@ -2012,90 +2025,6 @@ func TestCustomHelpVersionFlags(t *testing.T) { } } -func TestHandleAction_WithNonFuncAction(t *testing.T) { - app := NewApp() - app.Action = 42 - 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") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") { - t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { - app := NewApp() - app.Action = func() string { return "" } - 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") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { - t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { - app := NewApp() - app.Action = func(_ *Context) (int, error) { return 0, 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") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { - t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - func TestHandleExitCoder_Default(t *testing.T) { app := NewApp() fs, err := flagSet(app.Name, app.Flags) @@ -2132,83 +2061,48 @@ func TestHandleExitCoder_Custom(t *testing.T) { } } -func TestHandleAction_WithUnknownPanic(t *testing.T) { - defer func() { refute(t, recover(), nil) }() - - var fn ActionFunc - - app := NewApp() - app.Action = func(ctx *Context) error { - _ = fn(ctx) - return 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)) -} - func TestShellCompletionForIncompleteFlags(t *testing.T) { - app := NewApp() - app.Flags = []Flag{ - IntFlag{ - Name: "test-completion", + app := &App{ + Flags: []Flag{ + &IntFlag{ + Name: "test-completion", + }, }, - } - app.EnableBashCompletion = true - app.BashComplete = func(ctx *Context) { - for _, command := range ctx.App.Commands { - if command.Hidden { - continue - } - - for _, name := range command.Names() { - _, _ = fmt.Fprintln(ctx.App.Writer, name) - } - } - - for _, f := range ctx.App.Flags { - for _, name := range strings.Split(f.GetName(), ",") { - if name == BashCompletionFlag.GetName() { + EnableBashCompletion: true, + BashComplete: func(ctx *Context) { + for _, command := range ctx.App.Commands { + if command.Hidden { continue } - switch name = strings.TrimSpace(name); len(name) { - case 0: - case 1: - _, _ = fmt.Fprintln(ctx.App.Writer, "-"+name) - default: - _, _ = fmt.Fprintln(ctx.App.Writer, "--"+name) + for _, name := range command.Names() { + _, _ = fmt.Fprintln(ctx.App.Writer, name) } } - } - } - app.Action = func(ctx *Context) error { - return fmt.Errorf("should not get here") - } - err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.GetName()}) - if err != nil { - t.Errorf("app should not return an error: %s", err) - } -} -func TestHandleActionActuallyWorksWithActions(t *testing.T) { - var f ActionFunc - called := false - f = func(c *Context) error { - called = true - return nil + for _, fl := range ctx.App.Flags { + for _, name := range fl.Names() { + if name == BashCompletionFlag.Names()[0] { + continue + } + + switch name = strings.TrimSpace(name); len(name) { + case 0: + case 1: + _, _ = fmt.Fprintln(ctx.App.Writer, "-"+name) + default: + _, _ = fmt.Fprintln(ctx.App.Writer, "--"+name) + } + } + } + }, + Action: func(ctx *Context) error { + return fmt.Errorf("should not get here") + }, } - - err := HandleAction(f, nil) - + err := app.Run([]string{"", "--test-completion", "--" + "generate-bash-completion"}) if err != nil { - t.Errorf("Should not have errored: %v", err) - } - - if !called { - t.Errorf("Function was not called") + t.Errorf("app should not return an error: %s", err) } } @@ -2216,11 +2110,11 @@ func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) { testCode := 104 app := NewApp() - app.Commands = []Command{ - Command{ + app.Commands = []*Command{ + { Name: "cmd", - Subcommands: []Command{ - Command{ + Subcommands: []*Command{ + { Name: "subcmd", Action: func(c *Context) error { return NewExitError("exit error", testCode) @@ -2251,14 +2145,14 @@ func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) { exitCodeFromOsExiter = exitCode } - app.Run([]string{ + _ = app.Run([]string{ "myapp", "cmd", "subcmd", }) if exitCodeFromOsExiter != 0 { - t.Errorf("exitCodeFromOsExiter should not change, but its value is %v", exitCodeFromOsExiter) + t.Errorf("exitCodeFromExitErrHandler should not change, but its value is %v", exitCodeFromOsExiter) } if exitCodeFromExitErrHandler != testCode { diff --git a/appveyor.yml b/appveyor.yml index 8ef2fea1a6..d8f589b0ec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,7 +20,8 @@ install: - go version - go env - go get github.com/urfave/gfmrun/cmd/gfmrun - - go mod vendor + - go get golang.org/x/tools/cmd/goimports + - go mod tidy build_script: - go run build.go vet diff --git a/args.go b/args.go new file mode 100644 index 0000000000..bd65c17bde --- /dev/null +++ b/args.go @@ -0,0 +1,54 @@ +package cli + +type Args interface { + // Get returns the nth argument, or else a blank string + Get(n int) string + // First returns the first argument, or else a blank string + First() string + // Tail returns the rest of the arguments (not the first one) + // or else an empty string slice + Tail() []string + // Len returns the length of the wrapped slice + Len() int + // Present checks if there are any arguments present + Present() bool + // Slice returns a copy of the internal slice + Slice() []string +} + +type args []string + +func (a *args) Get(n int) string { + if len(*a) > n { + return (*a)[n] + } + return "" +} + +func (a *args) First() string { + return a.Get(0) +} + +func (a *args) Tail() []string { + if a.Len() >= 2 { + tail := []string((*a)[1:]) + ret := make([]string, len(tail)) + copy(ret, tail) + return ret + } + return []string{} +} + +func (a *args) Len() int { + return len(*a) +} + +func (a *args) Present() bool { + return a.Len() != 0 +} + +func (a *args) Slice() []string { + ret := make([]string, len(*a)) + copy(ret, *a) + return ret +} diff --git a/build.go b/build.go index a78ded358f..3255ab90a4 100644 --- a/build.go +++ b/build.go @@ -12,7 +12,7 @@ import ( "os/exec" "strings" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) var packages = []string{"cli", "altsrc"} @@ -24,19 +24,19 @@ func main() { app.Usage = "Generates a new urfave/cli build!" app.Commands = cli.Commands{ - cli.Command{ + { Name: "vet", Action: VetActionFunc, }, - cli.Command{ + { Name: "test", Action: TestActionFunc, }, - cli.Command{ + { Name: "gfmrun", Action: GfmrunActionFunc, }, - cli.Command{ + { Name: "toc", Action: TocActionFunc, }, @@ -67,9 +67,9 @@ func TestActionFunc(c *cli.Context) error { var packageName string if pkg == "cli" { - packageName = "github.com/urfave/cli" + packageName = "github.com/urfave/cli/v2" } else { - packageName = fmt.Sprintf("github.com/urfave/cli/%s", pkg) + packageName = fmt.Sprintf("github.com/urfave/cli/v2/%s", pkg) } coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg) diff --git a/category.go b/category.go index bf3c73c55e..d9e73a0bf0 100644 --- a/category.go +++ b/category.go @@ -1,41 +1,75 @@ package cli -// CommandCategories is a slice of *CommandCategory. -type CommandCategories []*CommandCategory +type CommandCategories interface { + // AddCommand adds a command to a category, creating a new category if necessary. + AddCommand(category string, command *Command) + // categories returns a copy of the category slice + Categories() []CommandCategory +} -// CommandCategory is a category containing commands. -type CommandCategory struct { - Name string - Commands Commands +type commandCategories []*commandCategory + +func newCommandCategories() CommandCategories { + ret := commandCategories([]*commandCategory{}) + return &ret } -func (c CommandCategories) Less(i, j int) bool { - return lexicographicLess(c[i].Name, c[j].Name) +func (c *commandCategories) Less(i, j int) bool { + return lexicographicLess((*c)[i].Name(), (*c)[j].Name()) } -func (c CommandCategories) Len() int { - return len(c) +func (c *commandCategories) Len() int { + return len(*c) } -func (c CommandCategories) Swap(i, j int) { - c[i], c[j] = c[j], c[i] +func (c *commandCategories) Swap(i, j int) { + (*c)[i], (*c)[j] = (*c)[j], (*c)[i] } -// AddCommand adds a command to a category. -func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { - for _, commandCategory := range c { - if commandCategory.Name == category { - commandCategory.Commands = append(commandCategory.Commands, command) - return c +func (c *commandCategories) AddCommand(category string, command *Command) { + for _, commandCategory := range []*commandCategory(*c) { + if commandCategory.name == category { + commandCategory.commands = append(commandCategory.commands, command) + return } } - return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) + newVal := append(*c, + &commandCategory{name: category, commands: []*Command{command}}) + *c = newVal +} + +func (c *commandCategories) Categories() []CommandCategory { + ret := make([]CommandCategory, len(*c)) + for i, cat := range *c { + ret[i] = cat + } + return ret +} + +// CommandCategory is a category containing commands. +type CommandCategory interface { + // Name returns the category name string + Name() string + // VisibleCommands returns a slice of the Commands with Hidden=false + VisibleCommands() []*Command +} + +type commandCategory struct { + name string + commands []*Command } -// VisibleCommands returns a slice of the Commands with Hidden=false -func (c *CommandCategory) VisibleCommands() []Command { - ret := []Command{} - for _, command := range c.Commands { +func (c *commandCategory) Name() string { + return c.name +} + +func (c *commandCategory) VisibleCommands() []*Command { + if c.commands == nil { + c.commands = []*Command{} + } + + var ret []*Command + for _, command := range c.commands { if !command.Hidden { ret = append(ret, command) } diff --git a/cli.go b/cli.go index 4bd2508392..62a5bc22d2 100644 --- a/cli.go +++ b/cli.go @@ -2,18 +2,19 @@ // Go applications. cli is designed to be easy to understand and write, the most simple // cli application can be written as follows: // func main() { -// cli.NewApp().Run(os.Args) +// (&cli.App{}).Run(os.Args) // } // // Of course this application does not do much, so let's make this an actual application: // func main() { -// app := cli.NewApp() -// app.Name = "greet" -// app.Usage = "say a greeting" -// app.Action = func(c *cli.Context) error { -// println("Greetings") -// return nil -// } +// app := &cli.App{ +// Name: "greet", +// Usage: "say a greeting", +// Action: func(c *cli.Context) error { +// fmt.Println("Greetings") +// return nil +// }, +// } // // app.Run(os.Args) // } diff --git a/command.go b/command.go index e7cb97a6af..a579fdb06a 100644 --- a/command.go +++ b/command.go @@ -11,8 +11,6 @@ import ( type Command struct { // The name of the command Name string - // short name of the command. Typically one character (deprecated, use `Aliases`) - ShortName string // A list of aliases for the command Aliases []string // A short description of the usage of this command @@ -34,23 +32,15 @@ type Command struct { // It is run even if Action() panics After AfterFunc // The function to call when this command is invoked - Action interface{} - // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind - // of deprecation period has passed, maybe? - + Action ActionFunc // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc // List of child commands - Subcommands Commands + Subcommands []*Command // List of flags to parse Flags []Flag // Treat all flags as normal arguments if true SkipFlagParsing bool - // Skip argument reordering which attempts to move flags before arguments, - // but only works if all flags appear after all arguments. This behavior was - // removed n version 2 since it only works under specific conditions so we - // backport here by exposing it as an option for compatibility. - SkipArgReorder bool // Boolean to hide built-in help command HideHelp bool // Boolean to hide this command from help or completion @@ -70,7 +60,9 @@ type Command struct { CustomHelpTemplate string } -type CommandsByName []Command +type Commands []*Command + +type CommandsByName []*Command func (c CommandsByName) Len() int { return len(c) @@ -86,35 +78,29 @@ func (c CommandsByName) Swap(i, j int) { // 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 { +func (c *Command) FullName() string { if c.commandNamePath == nil { return c.Name } return strings.Join(c.commandNamePath, " ") } -// Commands is a slice of Command -type Commands []Command - // Run 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) (err error) { if len(c.Subcommands) > 0 { return c.startApp(ctx) } - if !c.HideHelp && (HelpFlag != BoolFlag{}) { + if !c.HideHelp && HelpFlag != nil { // append help to flags - c.Flags = append( - c.Flags, - HelpFlag, - ) + c.appendFlag(HelpFlag) } if ctx.App.UseShortOptionHandling { c.UseShortOptionHandling = true } - set, err := c.parseFlags(ctx.Args().Tail()) + set, err := c.parseFlags(ctx.Args()) context := NewContext(ctx.App, set, ctx) context.Command = c @@ -124,7 +110,7 @@ func (c Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { - err := c.OnUsageError(context, err, false) + err = c.OnUsageError(context, err, false) context.App.handleExitCoder(context, err) return err } @@ -150,7 +136,7 @@ func (c Command) Run(ctx *Context) (err error) { if afterErr != nil { context.App.handleExitCoder(context, err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -171,7 +157,8 @@ func (c Command) Run(ctx *Context) (err error) { c.Action = helpSubcommand.Action } - err = HandleAction(c.Action, context) + context.Command = c + err = c.Action(context) if err != nil { context.App.handleExitCoder(context, err) @@ -179,26 +166,25 @@ func (c Command) Run(ctx *Context) (err error) { return err } -func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { - if c.SkipFlagParsing { - set, err := c.newFlagSet() - if err != nil { - return nil, err - } - - return set, set.Parse(append([]string{"--"}, args...)) - } +func (c *Command) newFlagSet() (*flag.FlagSet, error) { + return flagSet(c.Name, c.Flags) +} - if !c.SkipArgReorder { - args = reorderArgs(c.Flags, args) - } +func (c *Command) useShortOptionHandling() bool { + return c.UseShortOptionHandling +} +func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { set, err := c.newFlagSet() if err != nil { return nil, err } - err = parseIter(set, c, args) + if c.SkipFlagParsing { + return set, set.Parse(append([]string{"--"}, args.Tail()...)) + } + + err = parseIter(set, c, args.Tail()) if err != nil { return nil, err } @@ -211,96 +197,13 @@ func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { return set, nil } -func (c *Command) newFlagSet() (*flag.FlagSet, error) { - return flagSet(c.Name, c.Flags) -} - -func (c *Command) useShortOptionHandling() bool { - return c.UseShortOptionHandling -} - -// reorderArgs moves all flags (via reorderedArgs) before the rest of -// the arguments (remainingArgs) as this is what flag expects. -func reorderArgs(commandFlags []Flag, args []string) []string { - var remainingArgs, reorderedArgs []string - - nextIndexMayContainValue := false - for i, arg := range args { - - // dont reorder any args after a -- - // read about -- here: - // https://unix.stackexchange.com/questions/11376/what-does-double-dash-mean-also-known-as-bare-double-dash - if arg == "--" { - remainingArgs = append(remainingArgs, args[i:]...) - break - - // checks if this arg is a value that should be re-ordered next to its associated flag - } else if nextIndexMayContainValue && !strings.HasPrefix(arg, "-") { - nextIndexMayContainValue = false - reorderedArgs = append(reorderedArgs, arg) - - // checks if this is an arg that should be re-ordered - } else if argIsFlag(commandFlags, arg) { - // we have determined that this is a flag that we should re-order - reorderedArgs = append(reorderedArgs, arg) - // if this arg does not contain a "=", then the next index may contain the value for this flag - nextIndexMayContainValue = !strings.Contains(arg, "=") - - // simply append any remaining args - } else { - remainingArgs = append(remainingArgs, arg) - } - } - - return append(reorderedArgs, remainingArgs...) -} - -// argIsFlag checks if an arg is one of our command flags -func argIsFlag(commandFlags []Flag, arg string) bool { - // checks if this is just a `-`, and so definitely not a flag - if arg == "-" { - return false - } - // flags always start with a - - if !strings.HasPrefix(arg, "-") { - return false - } - // this line turns `--flag` into `flag` - if strings.HasPrefix(arg, "--") { - arg = strings.Replace(arg, "-", "", 2) - } - // this line turns `-flag` into `flag` - if strings.HasPrefix(arg, "-") { - arg = strings.Replace(arg, "-", "", 1) - } - // this line turns `flag=value` into `flag` - arg = strings.Split(arg, "=")[0] - // look through all the flags, to see if the `arg` is one of our flags - for _, flag := range commandFlags { - for _, key := range strings.Split(flag.GetName(), ",") { - key := strings.TrimSpace(key) - if key == arg { - return true - } - } - } - // return false if this arg was not one of our flags - return false -} - // Names returns the names including short names and aliases. -func (c Command) Names() []string { - names := []string{c.Name} - - if c.ShortName != "" { - names = append(names, c.ShortName) - } - - return append(names, c.Aliases...) +func (c *Command) Names() []string { + return append([]string{c.Name}, c.Aliases...) } -// HasName returns true if Command.Name or Command.ShortName matches given name -func (c Command) HasName(name string) bool { +// HasName returns true if Command.Name matches given name +func (c *Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { return true @@ -309,12 +212,12 @@ func (c Command) HasName(name string) bool { return false } -func (c Command) startApp(ctx *Context) error { - app := NewApp() - app.Metadata = ctx.App.Metadata - app.ExitErrHandler = ctx.App.ExitErrHandler - // set the name and usage - app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) +func (c *Command) startApp(ctx *Context) error { + app := &App{ + Metadata: ctx.App.Metadata, + Name: fmt.Sprintf("%s %s", ctx.App.Name, c.Name), + } + if c.HelpName == "" { app.HelpName = c.HelpName } else { @@ -337,18 +240,17 @@ func (c Command) startApp(ctx *Context) error { app.Version = ctx.App.Version app.HideVersion = ctx.App.HideVersion app.Compiled = ctx.App.Compiled - app.Author = ctx.App.Author - app.Email = ctx.App.Email app.Writer = ctx.App.Writer app.ErrWriter = ctx.App.ErrWriter + app.ExitErrHandler = ctx.App.ExitErrHandler app.UseShortOptionHandling = ctx.App.UseShortOptionHandling - app.categories = CommandCategories{} + app.categories = newCommandCategories() for _, command := range c.Subcommands { - app.categories = app.categories.AddCommand(command.Category, command) + app.categories.AddCommand(command.Category, command) } - sort.Sort(app.categories) + sort.Sort(app.categories.(*commandCategories)) // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion @@ -374,6 +276,22 @@ func (c Command) startApp(ctx *Context) error { } // VisibleFlags returns a slice of the Flags with Hidden=false -func (c Command) VisibleFlags() []Flag { +func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } + +func (c *Command) appendFlag(fl Flag) { + if !hasFlag(c.Flags, fl) { + c.Flags = append(c.Flags, fl) + } +} + +func hasCommand(commands []*Command, command *Command) bool { + for _, existing := range commands { + if command == existing { + return true + } + } + + return false +} diff --git a/command_test.go b/command_test.go index bcfbee747e..7c55f138da 100644 --- a/command_test.go +++ b/command_test.go @@ -13,79 +13,69 @@ func TestCommandFlagParsing(t *testing.T) { cases := []struct { testArgs []string skipFlagParsing bool - skipArgReorder bool + useShortOptionHandling bool expectedErr error - UseShortOptionHandling bool }{ // Test normal "not ignoring flags" flow - {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, nil, false}, - - // Test no arg reorder - {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false}, - {[]string{"test-cmd", "blah", "blah", "-break", "ls", "-l"}, false, true, nil, true}, - - {[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags - {[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg - {[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg - {[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling - + {testArgs: []string{"test-cmd", "-break", "blah", "blah"}, skipFlagParsing: false, useShortOptionHandling: false, expectedErr: errors.New("flag provided but not defined: -break")}, + {testArgs: []string{"test-cmd", "blah", "blah"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing without any args that look like flags + {testArgs: []string{"test-cmd", "blah", "-break"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing with random flag arg + {testArgs: []string{"test-cmd", "blah", "-help"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing with "special" help flag arg + {testArgs: []string{"test-cmd", "blah", "-h"}, skipFlagParsing: false, useShortOptionHandling: true, expectedErr: nil}, // Test UseShortOptionHandling } for _, c := range cases { - app := NewApp() - app.Writer = ioutil.Discard + app := &App{Writer: ioutil.Discard} set := flag.NewFlagSet("test", 0) _ = set.Parse(c.testArgs) context := NewContext(app, set, nil) command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) error { return nil }, - SkipFlagParsing: c.skipFlagParsing, - SkipArgReorder: c.skipArgReorder, - UseShortOptionHandling: c.UseShortOptionHandling, + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) error { return nil }, + SkipFlagParsing: c.skipFlagParsing, } err := command.Run(context) expect(t, err, c.expectedErr) - expect(t, []string(context.Args()), c.testArgs) + expect(t, context.Args().Slice(), c.testArgs) } } func TestParseAndRunShortOpts(t *testing.T) { cases := []struct { - testArgs []string + testArgs args expectedErr error - expectedArgs []string + expectedArgs Args }{ - {[]string{"foo", "test", "-a"}, nil, []string{}}, - {[]string{"foo", "test", "-c", "arg1", "arg2"}, nil, []string{"arg1", "arg2"}}, - {[]string{"foo", "test", "-f"}, nil, []string{}}, - {[]string{"foo", "test", "-ac", "--fgh"}, nil, []string{}}, - {[]string{"foo", "test", "-af"}, nil, []string{}}, - {[]string{"foo", "test", "-cf"}, nil, []string{}}, - {[]string{"foo", "test", "-acf"}, nil, []string{}}, - {[]string{"foo", "test", "--acf"}, errors.New("flag provided but not defined: -acf"), nil}, - {[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), nil}, - {[]string{"foo", "test", "-acf", "-invalid"}, errors.New("flag provided but not defined: -invalid"), nil}, - {[]string{"foo", "test", "--invalid"}, errors.New("flag provided but not defined: -invalid"), nil}, - {[]string{"foo", "test", "-acf", "--invalid"}, errors.New("flag provided but not defined: -invalid"), nil}, - {[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}}, - {[]string{"foo", "test", "-acf", "arg1", "--invalid"}, nil, []string{"arg1", "--invalid"}}, - {[]string{"foo", "test", "-acfi", "not-arg", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}}, - {[]string{"foo", "test", "-i", "ivalue"}, nil, []string{}}, - {[]string{"foo", "test", "-i", "ivalue", "arg1"}, nil, []string{"arg1"}}, - {[]string{"foo", "test", "-i"}, errors.New("flag needs an argument: -i"), nil}, + {testArgs: args{"foo", "test", "-a"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-c", "arg1", "arg2"}, expectedErr: nil, expectedArgs: &args{"arg1", "arg2"}}, + {testArgs: args{"foo", "test", "-f"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-ac", "--fgh"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-af"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-cf"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-acf"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "--acf"}, expectedErr: errors.New("flag provided but not defined: -acf"), expectedArgs: nil}, + {testArgs: args{"foo", "test", "-invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil}, + {testArgs: args{"foo", "test", "-acf", "-invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil}, + {testArgs: args{"foo", "test", "--invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil}, + {testArgs: args{"foo", "test", "-acf", "--invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil}, + {testArgs: args{"foo", "test", "-acf", "arg1", "-invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "-invalid"}}, + {testArgs: args{"foo", "test", "-acf", "arg1", "--invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "--invalid"}}, + {testArgs: args{"foo", "test", "-acfi", "not-arg", "arg1", "-invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "-invalid"}}, + {testArgs: args{"foo", "test", "-i", "ivalue"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-i", "ivalue", "arg1"}, expectedErr: nil, expectedArgs: &args{"arg1"}}, + {testArgs: args{"foo", "test", "-i"}, expectedErr: errors.New("flag needs an argument: -i"), expectedArgs: nil}, } for _, c := range cases { - var args []string - cmd := Command{ + var args Args + cmd := &Command{ Name: "test", Usage: "this is for testing", Description: "testing", @@ -93,18 +83,17 @@ func TestParseAndRunShortOpts(t *testing.T) { args = c.Args() return nil }, - SkipArgReorder: true, UseShortOptionHandling: true, Flags: []Flag{ - BoolFlag{Name: "abc, a"}, - BoolFlag{Name: "cde, c"}, - BoolFlag{Name: "fgh, f"}, - StringFlag{Name: "ijk, i"}, + &BoolFlag{Name: "abc", Aliases: []string{"a"}}, + &BoolFlag{Name: "cde", Aliases: []string{"c"}}, + &BoolFlag{Name: "fgh", Aliases: []string{"f"}}, + &StringFlag{Name: "ijk", Aliases: []string{"i"}}, }, } app := NewApp() - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run(c.testArgs) @@ -114,15 +103,16 @@ func TestParseAndRunShortOpts(t *testing.T) { } func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { - app := NewApp() - app.Commands = []Command{ - { - Name: "bar", - Before: func(c *Context) error { - return fmt.Errorf("before error") - }, - After: func(c *Context) error { - return fmt.Errorf("after error") + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Before: func(c *Context) error { + return fmt.Errorf("before error") + }, + After: func(c *Context) error { + return fmt.Errorf("after error") + }, }, }, } @@ -144,29 +134,30 @@ func TestCommand_Run_BeforeSavesMetadata(t *testing.T) { var receivedMsgFromAction string var receivedMsgFromAfter string - app := NewApp() - app.Commands = []Command{ - { - Name: "bar", - Before: func(c *Context) error { - c.App.Metadata["msg"] = "hello world" - return nil - }, - Action: func(c *Context) error { - msg, ok := c.App.Metadata["msg"] - if !ok { - return errors.New("msg not found") - } - receivedMsgFromAction = msg.(string) - return nil - }, - After: func(c *Context) error { - msg, ok := c.App.Metadata["msg"] - if !ok { - return errors.New("msg not found") - } - receivedMsgFromAfter = msg.(string) - return nil + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Before: func(c *Context) error { + c.App.Metadata["msg"] = "hello world" + return nil + }, + Action: func(c *Context) error { + msg, ok := c.App.Metadata["msg"] + if !ok { + return errors.New("msg not found") + } + receivedMsgFromAction = msg.(string) + return nil + }, + After: func(c *Context) error { + msg, ok := c.App.Metadata["msg"] + if !ok { + return errors.New("msg not found") + } + receivedMsgFromAfter = msg.(string) + return nil + }, }, }, } @@ -189,15 +180,16 @@ func TestCommand_Run_BeforeSavesMetadata(t *testing.T) { } func TestCommand_OnUsageError_hasCommandContext(t *testing.T) { - app := NewApp() - app.Commands = []Command{ - { - Name: "bar", - Flags: []Flag{ - IntFlag{Name: "flag"}, - }, - OnUsageError: func(c *Context, err error, _ bool) error { - return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error()) + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error()) + }, }, }, } @@ -213,18 +205,19 @@ func TestCommand_OnUsageError_hasCommandContext(t *testing.T) { } func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { - app := NewApp() - app.Commands = []Command{ - { - Name: "bar", - Flags: []Flag{ - IntFlag{Name: "flag"}, - }, - OnUsageError: func(c *Context, err error, _ bool) error { - if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { - t.Errorf("Expect an invalid value error, but got \"%v\"", err) - } - return errors.New("intercepted: " + err.Error()) + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, }, }, } @@ -240,23 +233,24 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { } func TestCommand_OnUsageError_WithSubcommand(t *testing.T) { - app := NewApp() - app.Commands = []Command{ - { - Name: "bar", - Subcommands: []Command{ - { - Name: "baz", + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Subcommands: []*Command{ + { + Name: "baz", + }, + }, + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) }, - }, - Flags: []Flag{ - IntFlag{Name: "flag"}, - }, - OnUsageError: func(c *Context, err error, _ bool) error { - if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { - t.Errorf("Expect an invalid value error, but got \"%v\"", err) - } - return errors.New("intercepted: " + err.Error()) }, }, } @@ -272,22 +266,23 @@ func TestCommand_OnUsageError_WithSubcommand(t *testing.T) { } 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 + app := &App{ + ErrWriter: ioutil.Discard, + 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 + }, }, }, }, @@ -300,67 +295,30 @@ func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { } } -func TestCommandFlagReordering(t *testing.T) { - cases := []struct { - testArgs []string - expectedValue string - expectedArgs []string - expectedErr error - }{ - {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, "foo", []string{"some-arg"}, nil}, - {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, "foo", []string{"some-arg"}, nil}, - {[]string{"some-exec", "some-command", "--flag=foo", "some-arg"}, "foo", []string{"some-arg"}, nil}, - } - - for _, c := range cases { - value := "" - var args []string - app := &App{ - Commands: []Command{ - { - Name: "some-command", - Flags: []Flag{ - StringFlag{Name: "flag"}, - }, - Action: func(c *Context) { - fmt.Printf("%+v\n", c.String("flag")) - value = c.String("flag") - args = c.Args() - }, - }, - }, - } - - err := app.Run(c.testArgs) - expect(t, err, c.expectedErr) - expect(t, value, c.expectedValue) - expect(t, args, c.expectedArgs) - } -} - func TestCommandSkipFlagParsing(t *testing.T) { cases := []struct { - testArgs []string - expectedArgs []string + testArgs args + expectedArgs *args expectedErr error }{ - {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, []string{"some-arg", "--flag", "foo"}, nil}, - {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, []string{"some-arg", "--flag=foo"}, nil}, + {testArgs: args{"some-exec", "some-command", "some-arg", "--flag", "foo"}, expectedArgs: &args{"some-arg", "--flag", "foo"}, expectedErr: nil}, + {testArgs: args{"some-exec", "some-command", "some-arg", "--flag=foo"}, expectedArgs: &args{"some-arg", "--flag=foo"}, expectedErr: nil}, } for _, c := range cases { - var args []string + var args Args app := &App{ - Commands: []Command{ + Commands: []*Command{ { SkipFlagParsing: true, Name: "some-command", Flags: []Flag{ - StringFlag{Name: "flag"}, + &StringFlag{Name: "flag"}, }, - Action: func(c *Context) { + Action: func(c *Context) error { fmt.Printf("%+v\n", c.String("flag")) args = c.Args() + return nil }, }, }, diff --git a/context.go b/context.go index ecfc032821..535c38818a 100644 --- a/context.go +++ b/context.go @@ -1,36 +1,51 @@ package cli import ( + "context" "errors" "flag" "fmt" "os" - "reflect" + "os/signal" "strings" "syscall" ) // Context is a type that is passed through to // each Handler action in a cli application. Context -// can be used to retrieve context-specific Args and +// can be used to retrieve context-specific args and // parsed command-line options. type Context struct { + context.Context App *App - Command Command + Command *Command shellComplete bool - flagSet *flag.FlagSet setFlags map[string]bool + flagSet *flag.FlagSet parentContext *Context } // NewContext creates a new context. For use in when invoking an App or Command action. func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { c := &Context{App: app, flagSet: set, parentContext: parentCtx} - if parentCtx != nil { + c.Context = parentCtx.Context c.shellComplete = parentCtx.shellComplete } + c.Command = &Command{} + + if c.Context == nil { + ctx, cancel := context.WithCancel(context.Background()) + go func() { + defer cancel() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs + }() + c.Context = ctx + } + return c } @@ -41,214 +56,122 @@ func (c *Context) NumFlags() int { // Set sets a context flag to a value. func (c *Context) Set(name, value string) error { - c.setFlags = nil return c.flagSet.Set(name, value) } -// GlobalSet sets a context flag to a value on the global flagset -func (c *Context) GlobalSet(name, value string) error { - globalContext(c).setFlags = nil - return globalContext(c).flagSet.Set(name, value) -} - // IsSet determines if the flag was actually set func (c *Context) IsSet(name string) bool { - if c.setFlags == nil { - c.setFlags = make(map[string]bool) - - c.flagSet.Visit(func(f *flag.Flag) { - c.setFlags[f.Name] = true - }) - - c.flagSet.VisitAll(func(f *flag.Flag) { - if _, ok := c.setFlags[f.Name]; ok { - return - } - c.setFlags[f.Name] = false - }) - - // XXX hack to support IsSet for flags with EnvVar - // - // There isn't an easy way to do this with the current implementation since - // whether a flag was set via an environment variable is very difficult to - // determine here. Instead, we intend to introduce a backwards incompatible - // change in version 2 to add `IsSet` to the Flag interface to push the - // responsibility closer to where the information required to determine - // whether a flag is set by non-standard means such as environment - // variables is available. - // - // See https://github.com/urfave/cli/issues/294 for additional discussion - flags := c.Command.Flags - if c.Command.Name == "" { // cannot == Command{} since it contains slice types - if c.App != nil { - flags = c.App.Flags - } - } - for _, f := range flags { - eachName(f.GetName(), func(name string) { - if isSet, ok := c.setFlags[name]; isSet || !ok { - return - } - - val := reflect.ValueOf(f) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - - filePathValue := val.FieldByName("FilePath") - if filePathValue.IsValid() { - eachName(filePathValue.String(), func(filePath string) { - if _, err := os.Stat(filePath); err == nil { - c.setFlags[name] = true - return - } - }) - } - - envVarValue := val.FieldByName("EnvVar") - if envVarValue.IsValid() { - eachName(envVarValue.String(), func(envVar string) { - envVar = strings.TrimSpace(envVar) - if _, ok := syscall.Getenv(envVar); ok { - c.setFlags[name] = true - return - } - }) + if fs := lookupFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { + isSet := false + fs.Visit(func(f *flag.Flag) { + if f.Name == name { + isSet = true } }) + if isSet { + return true + } } - } - return c.setFlags[name] -} + f := lookupFlag(name, c) + if f == nil { + return false + } -// GlobalIsSet determines if the global flag was actually set -func (c *Context) GlobalIsSet(name string) bool { - ctx := c - if ctx.parentContext != nil { - ctx = ctx.parentContext + return f.IsSet() } - for ; ctx != nil; ctx = ctx.parentContext { - if ctx.IsSet(name) { - return true - } - } return false } -// FlagNames returns a slice of flag names used in this context. -func (c *Context) FlagNames() (names []string) { - for _, f := range c.Command.Flags { - name := strings.Split(f.GetName(), ",")[0] - if name == "help" { - continue - } - names = append(names, name) - } - return +// LocalFlagNames returns a slice of flag names used in this context. +func (c *Context) LocalFlagNames() []string { + var names []string + c.flagSet.Visit(makeFlagNameVisitor(&names)) + return names } -// GlobalFlagNames returns a slice of global flag names used by the app. -func (c *Context) GlobalFlagNames() (names []string) { - for _, f := range c.App.Flags { - name := strings.Split(f.GetName(), ",")[0] - if name == "help" || name == "version" { - continue - } - names = append(names, name) +// FlagNames returns a slice of flag names used by the this context and all of +// its parent contexts. +func (c *Context) FlagNames() []string { + var names []string + for _, ctx := range c.Lineage() { + ctx.flagSet.Visit(makeFlagNameVisitor(&names)) } - return + return names } -// Parent returns the parent context, if any -func (c *Context) Parent() *Context { - return c.parentContext +// Lineage returns *this* context and all of its ancestor contexts in order from +// child to parent +func (c *Context) Lineage() []*Context { + var lineage []*Context + + for cur := c; cur != nil; cur = cur.parentContext { + lineage = append(lineage, cur) + } + + return lineage } -// value returns the value of the flag coressponding to `name` +// value returns the value of the flag corresponding 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 - // Args returns the command line arguments associated with the context. func (c *Context) Args() Args { - args := Args(c.flagSet.Args()) - return args + ret := args(c.flagSet.Args()) + return &ret } // NArg returns the number of the command line arguments. func (c *Context) NArg() int { - return len(c.Args()) -} - -// Get returns the nth argument, or else a blank string -func (a Args) Get(n int) string { - if len(a) > n { - return a[n] - } - return "" + return c.Args().Len() } -// First returns the first argument, or else a blank string -func (a Args) First() string { - return a.Get(0) -} +func lookupFlag(name string, ctx *Context) Flag { + for _, c := range ctx.Lineage() { + if c.Command == nil { + continue + } -// Tail returns the rest of the arguments (not the first one) -// or else an empty string slice -func (a Args) Tail() []string { - if len(a) >= 2 { - return []string(a)[1:] + for _, f := range c.Command.Flags { + for _, n := range f.Names() { + if n == name { + return f + } + } + } } - return []string{} -} - -// Present checks if there are any arguments present -func (a Args) Present() bool { - return len(a) != 0 -} -// Swap swaps arguments at the given indexes -func (a Args) Swap(from, to int) error { - if from >= len(a) || to >= len(a) { - return errors.New("index out of range") + if ctx.App != nil { + for _, f := range ctx.App.Flags { + for _, n := range f.Names() { + if n == name { + return f + } + } + } } - a[from], a[to] = a[to], a[from] + return nil } -func globalContext(ctx *Context) *Context { - if ctx == nil { - return nil - } - - for { - if ctx.parentContext == nil { - return ctx +func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { + for _, c := range ctx.Lineage() { + if f := c.flagSet.Lookup(name); f != nil { + return c.flagSet } - ctx = ctx.parentContext } -} -func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { - if ctx.parentContext != nil { - ctx = ctx.parentContext - } - for ; ctx != nil; ctx = ctx.parentContext { - if f := ctx.flagSet.Lookup(name); f != nil { - return ctx.flagSet - } - } return nil } func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { switch ff.Value.(type) { - case *StringSlice: + case Serializer: + _ = set.Set(name, ff.Value.(Serializer).Serialize()) default: _ = set.Set(name, ff.Value.String()) } @@ -260,7 +183,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { visited[f.Name] = true }) for _, f := range flags { - parts := strings.Split(f.GetName(), ",") + parts := f.Names() if len(parts) == 1 { continue } @@ -287,6 +210,24 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { return nil } +func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { + return func(f *flag.Flag) { + nameParts := strings.Split(f.Name, ",") + name := strings.TrimSpace(nameParts[0]) + + for _, part := range nameParts { + part = strings.TrimSpace(part) + if len(part) > len(name) { + name = part + } + } + + if name != "" { + *names = append(*names, name) + } + } +} + type requiredFlagsErr interface { error getMissingFlags() []string @@ -315,7 +256,8 @@ func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { var flagPresent bool var flagName string - for _, key := range strings.Split(f.GetName(), ",") { + + for _, key := range f.Names() { if len(key) > 1 { flagName = key } diff --git a/context_test.go b/context_test.go index 28f5e08476..081a8c43b9 100644 --- a/context_test.go +++ b/context_test.go @@ -1,7 +1,9 @@ package cli import ( + "context" "flag" + "sort" "os" "strings" "testing" @@ -22,7 +24,7 @@ func TestNewContext(t *testing.T) { globalSet.Uint64("myflagUint64", uint64(33), "doc") globalSet.Float64("myflag64", float64(47), "doc") globalCtx := NewContext(nil, globalSet, nil) - command := Command{Name: "mycommand"} + command := &Command{Name: "mycommand"} c := NewContext(nil, set, globalCtx) c.Command = command expect(t, c.Int("myflag"), 12) @@ -30,123 +32,108 @@ func TestNewContext(t *testing.T) { expect(t, c.Uint("myflagUint"), uint(93)) expect(t, c.Uint64("myflagUint64"), uint64(93)) expect(t, c.Float64("myflag64"), float64(17)) - expect(t, c.GlobalInt("myflag"), 42) - expect(t, c.GlobalInt64("myflagInt64"), int64(42)) - expect(t, c.GlobalUint("myflagUint"), uint(33)) - expect(t, c.GlobalUint64("myflagUint64"), uint64(33)) - expect(t, c.GlobalFloat64("myflag64"), float64(47)) expect(t, c.Command.Name, "mycommand") } func TestContext_Int(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Int("top-flag", 13, "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Int("myflag"), 12) + expect(t, c.Int("top-flag"), 13) } func TestContext_Int64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int64("myflagInt64", 12, "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Int64("top-flag", 13, "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Int64("myflagInt64"), int64(12)) + expect(t, c.Int64("top-flag"), int64(13)) } func TestContext_Uint(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Uint("myflagUint", uint(13), "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Uint("top-flag", uint(14), "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Uint("myflagUint"), uint(13)) + expect(t, c.Uint("top-flag"), uint(14)) } func TestContext_Uint64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Uint64("myflagUint64", uint64(9), "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Uint64("top-flag", uint64(10), "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Uint64("myflagUint64"), uint64(9)) -} - -func TestContext_GlobalInt(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Int("myflag", 12, "doc") - c := NewContext(nil, set, nil) - expect(t, c.GlobalInt("myflag"), 12) - expect(t, c.GlobalInt("nope"), 0) -} - -func TestContext_GlobalInt64(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Int64("myflagInt64", 12, "doc") - c := NewContext(nil, set, nil) - expect(t, c.GlobalInt64("myflagInt64"), int64(12)) - expect(t, c.GlobalInt64("nope"), int64(0)) + expect(t, c.Uint64("top-flag"), uint64(10)) } func TestContext_Float64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Float64("myflag", float64(17), "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Float64("top-flag", float64(18), "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Float64("myflag"), float64(17)) -} - -func TestContext_GlobalFloat64(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Float64("myflag", float64(17), "doc") - c := NewContext(nil, set, nil) - expect(t, c.GlobalFloat64("myflag"), float64(17)) - expect(t, c.GlobalFloat64("nope"), float64(0)) + expect(t, c.Float64("top-flag"), float64(18)) } func TestContext_Duration(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Duration("myflag", 12*time.Second, "doc") - c := NewContext(nil, set, nil) + + parentSet := flag.NewFlagSet("test", 0) + parentSet.Duration("top-flag", 13*time.Second, "doc") + parentCtx := NewContext(nil, parentSet, nil) + + c := NewContext(nil, set, parentCtx) expect(t, c.Duration("myflag"), 12*time.Second) + expect(t, c.Duration("top-flag"), 13*time.Second) } func TestContext_String(t *testing.T) { set := flag.NewFlagSet("test", 0) set.String("myflag", "hello world", "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.String("top-flag", "hai veld", "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.String("myflag"), "hello world") + expect(t, c.String("top-flag"), "hai veld") } -func TestContext_Bool(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Bool("myflag", false, "doc") - c := NewContext(nil, set, nil) - expect(t, c.Bool("myflag"), false) -} - -func TestContext_BoolT(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Bool("myflag", true, "doc") - c := NewContext(nil, set, nil) - expect(t, c.BoolT("myflag"), true) -} - -func TestContext_GlobalBool(t *testing.T) { +func TestContext_Path(t *testing.T) { set := flag.NewFlagSet("test", 0) - - globalSet := flag.NewFlagSet("test-global", 0) - globalSet.Bool("myflag", false, "doc") - globalCtx := NewContext(nil, globalSet, nil) - - c := NewContext(nil, set, globalCtx) - expect(t, c.GlobalBool("myflag"), false) - expect(t, c.GlobalBool("nope"), false) + set.String("path", "path/to/file", "path to file") + parentSet := flag.NewFlagSet("test", 0) + parentSet.String("top-path", "path/to/top/file", "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) + expect(t, c.Path("path"), "path/to/file") + expect(t, c.Path("top-path"), "path/to/top/file") } -func TestContext_GlobalBoolT(t *testing.T) { +func TestContext_Bool(t *testing.T) { set := flag.NewFlagSet("test", 0) - - globalSet := flag.NewFlagSet("test-global", 0) - globalSet.Bool("myflag", true, "doc") - globalCtx := NewContext(nil, globalSet, nil) - - c := NewContext(nil, set, globalCtx) - expect(t, c.GlobalBoolT("myflag"), true) - expect(t, c.GlobalBoolT("nope"), false) + set.Bool("myflag", false, "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) + expect(t, c.Bool("myflag"), false) + expect(t, c.Bool("top-flag"), true) } func TestContext_Args(t *testing.T) { @@ -154,7 +141,7 @@ func TestContext_Args(t *testing.T) { set.Bool("myflag", false, "doc") c := NewContext(nil, set, nil) _ = set.Parse([]string{"--myflag", "bat", "baz"}) - expect(t, len(c.Args()), 2) + expect(t, c.Args().Len(), 2) expect(t, c.Bool("myflag"), true) } @@ -168,18 +155,22 @@ func TestContext_NArg(t *testing.T) { func TestContext_IsSet(t *testing.T) { set := flag.NewFlagSet("test", 0) - set.Bool("myflag", false, "doc") - set.String("otherflag", "hello world", "doc") - globalSet := flag.NewFlagSet("test", 0) - globalSet.Bool("myflagGlobal", true, "doc") - globalCtx := NewContext(nil, globalSet, nil) - c := NewContext(nil, set, globalCtx) - _ = set.Parse([]string{"--myflag", "bat", "baz"}) - _ = globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) - expect(t, c.IsSet("myflag"), true) - expect(t, c.IsSet("otherflag"), false) - expect(t, c.IsSet("bogusflag"), false) - expect(t, c.IsSet("myflagGlobal"), false) + set.Bool("one-flag", false, "doc") + set.Bool("two-flag", false, "doc") + set.String("three-flag", "hello world", "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + ctx := NewContext(nil, set, parentCtx) + + _ = set.Parse([]string{"--one-flag", "--two-flag", "--three-flag", "frob"}) + _ = parentSet.Parse([]string{"--top-flag"}) + + expect(t, ctx.IsSet("one-flag"), true) + expect(t, ctx.IsSet("two-flag"), true) + expect(t, ctx.IsSet("three-flag"), true) + expect(t, ctx.IsSet("top-flag"), true) + expect(t, ctx.IsSet("bogus"), false) } // XXX Corresponds to hack in context.IsSet for flags with EnvVar field @@ -192,15 +183,15 @@ func TestContext_IsSet_fromEnv(t *testing.T) { unparsableIsSet, uIsSet bool ) - clearenv() + 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: "unparsable, u", EnvVar: "APP_UNPARSABLE"}, - Float64Flag{Name: "no-env-var, n"}, + &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, + &StringFlag{Name: "password", Aliases: []string{"p"}, EnvVars: []string{"APP_PASSWORD"}}, + &Float64Flag{Name: "unparsable", Aliases: []string{"u"}, EnvVars: []string{"APP_UNPARSABLE"}}, + &Float64Flag{Name: "no-env-var", Aliases: []string{"n"}}, }, Action: func(ctx *Context) error { timeoutIsSet = ctx.IsSet("timeout") @@ -228,94 +219,6 @@ func TestContext_IsSet_fromEnv(t *testing.T) { expect(t, uIsSet, false) } -func TestContext_GlobalIsSet(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Bool("myflag", false, "doc") - set.String("otherflag", "hello world", "doc") - globalSet := flag.NewFlagSet("test", 0) - globalSet.Bool("myflagGlobal", true, "doc") - globalSet.Bool("myflagGlobalUnset", true, "doc") - globalCtx := NewContext(nil, globalSet, nil) - c := NewContext(nil, set, globalCtx) - _ = set.Parse([]string{"--myflag", "bat", "baz"}) - _ = globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) - expect(t, c.GlobalIsSet("myflag"), false) - expect(t, c.GlobalIsSet("otherflag"), false) - expect(t, c.GlobalIsSet("bogusflag"), false) - expect(t, c.GlobalIsSet("myflagGlobal"), true) - expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) - expect(t, c.GlobalIsSet("bogusGlobal"), false) -} - -// 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 bool - noEnvVarIsSet, nIsSet bool - passwordIsSet, pIsSet bool - passwordValue string - unparsableIsSet, uIsSet bool - overrideIsSet, oIsSet bool - overrideValue string - ) - - clearenv() - _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") - _ = os.Setenv("APP_PASSWORD", "badpass") - _ = os.Setenv("APP_OVERRIDE", "overridden") - 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"}, - StringFlag{Name: "overrides-default, o", Value: "default", EnvVar: "APP_OVERRIDE"}, - }, - Commands: []Command{ - { - Name: "hello", - Action: func(ctx *Context) error { - timeoutIsSet = ctx.GlobalIsSet("timeout") - tIsSet = ctx.GlobalIsSet("t") - passwordIsSet = ctx.GlobalIsSet("password") - pIsSet = ctx.GlobalIsSet("p") - passwordValue = ctx.GlobalString("password") - unparsableIsSet = ctx.GlobalIsSet("unparsable") - uIsSet = ctx.GlobalIsSet("u") - noEnvVarIsSet = ctx.GlobalIsSet("no-env-var") - nIsSet = ctx.GlobalIsSet("n") - overrideIsSet = ctx.GlobalIsSet("overrides-default") - oIsSet = ctx.GlobalIsSet("o") - overrideValue = ctx.GlobalString("overrides-default") - return nil - }, - }, - }, - } - 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, passwordValue, "badpass") - expect(t, unparsableIsSet, false) - expect(t, noEnvVarIsSet, false) - expect(t, nIsSet, false) - expect(t, overrideIsSet, true) - expect(t, oIsSet, true) - expect(t, overrideValue, "overridden") - - _ = 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) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") @@ -329,62 +232,6 @@ func TestContext_NumFlags(t *testing.T) { expect(t, c.NumFlags(), 2) } -func TestContext_GlobalFlag(t *testing.T) { - var globalFlag string - var globalFlagSet bool - app := NewApp() - app.Flags = []Flag{ - StringFlag{Name: "global, g", Usage: "global"}, - } - app.Action = func(c *Context) error { - globalFlag = c.GlobalString("global") - globalFlagSet = c.GlobalIsSet("global") - return nil - } - _ = app.Run([]string{"command", "-g", "foo"}) - expect(t, globalFlag, "foo") - expect(t, globalFlagSet, true) - -} - -func TestContext_GlobalFlagsInSubcommands(t *testing.T) { - subcommandRun := false - parentFlag := false - app := NewApp() - - app.Flags = []Flag{ - BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, - } - - app.Commands = []Command{ - { - Name: "foo", - Flags: []Flag{ - BoolFlag{Name: "parent, p", Usage: "Parent flag"}, - }, - Subcommands: []Command{ - { - Name: "bar", - Action: func(c *Context) error { - if c.GlobalBool("debug") { - subcommandRun = true - } - if c.GlobalBool("parent") { - parentFlag = true - } - return nil - }, - }, - }, - }, - } - - _ = app.Run([]string{"command", "-d", "foo", "-p", "bar"}) - - expect(t, subcommandRun, true) - expect(t, parentFlag, true) -} - func TestContext_Set(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("int", 5, "an int") @@ -396,25 +243,105 @@ func TestContext_Set(t *testing.T) { expect(t, c.IsSet("int"), true) } -func TestContext_GlobalSet(t *testing.T) { - gSet := flag.NewFlagSet("test", 0) - gSet.Int("int", 5, "an int") +func TestContext_LocalFlagNames(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("one-flag", false, "doc") + set.String("two-flag", "hello world", "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + ctx := NewContext(nil, set, parentCtx) + _ = set.Parse([]string{"--one-flag", "--two-flag=foo"}) + _ = parentSet.Parse([]string{"--top-flag"}) + + actualFlags := ctx.LocalFlagNames() + sort.Strings(actualFlags) + + expect(t, actualFlags, []string{"one-flag", "two-flag"}) +} - set := flag.NewFlagSet("sub", 0) - set.Int("int", 3, "an int") +func TestContext_FlagNames(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("one-flag", false, "doc") + set.String("two-flag", "hello world", "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + ctx := NewContext(nil, set, parentCtx) + _ = set.Parse([]string{"--one-flag", "--two-flag=foo"}) + _ = parentSet.Parse([]string{"--top-flag"}) + + actualFlags := ctx.FlagNames() + sort.Strings(actualFlags) + + expect(t, actualFlags, []string{"one-flag", "top-flag", "two-flag"}) +} - pc := NewContext(nil, gSet, nil) - c := NewContext(nil, set, pc) +func TestContext_Lineage(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("local-flag", false, "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + ctx := NewContext(nil, set, parentCtx) + _ = set.Parse([]string{"--local-flag"}) + _ = parentSet.Parse([]string{"--top-flag"}) + + lineage := ctx.Lineage() + expect(t, len(lineage), 2) + expect(t, lineage[0], ctx) + expect(t, lineage[1], parentCtx) +} - _ = c.Set("int", "1") - expect(t, c.Int("int"), 1) - expect(t, c.GlobalInt("int"), 5) +func TestContext_lookupFlagSet(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("local-flag", false, "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + ctx := NewContext(nil, set, parentCtx) + _ = set.Parse([]string{"--local-flag"}) + _ = parentSet.Parse([]string{"--top-flag"}) + + fs := lookupFlagSet("top-flag", ctx) + expect(t, fs, parentCtx.flagSet) + + fs = lookupFlagSet("local-flag", ctx) + expect(t, fs, ctx.flagSet) + + if fs := lookupFlagSet("frob", ctx); fs != nil { + t.Fail() + } +} - expect(t, c.GlobalIsSet("int"), false) - _ = c.GlobalSet("int", "1") - expect(t, c.Int("int"), 1) - expect(t, c.GlobalInt("int"), 1) - expect(t, c.GlobalIsSet("int"), true) +func TestNonNilContext(t *testing.T) { + ctx := NewContext(nil, nil, nil) + if ctx.Context == nil { + t.Fatal("expected a non nil context when no parent is present") + } +} + +// TestContextPropagation tests that +// *cli.Context always has a valid +// context.Context +func TestContextPropagation(t *testing.T) { + parent := NewContext(nil, nil, nil) + parent.Context = context.WithValue(context.Background(), "key", "val") + ctx := NewContext(nil, nil, parent) + val := ctx.Value("key") + if val == nil { + t.Fatal("expected a parent context to be inherited but got nil") + } + valstr, _ := val.(string) + if valstr != "val" { + t.Fatalf("expected the context value to be %q but got %q", "val", valstr) + } + parent = NewContext(nil, nil, nil) + parent.Context = nil + ctx = NewContext(nil, nil, parent) + if ctx.Context == nil { + t.Fatal("expected context to not be nil even if the parent's context is nil") + } } func TestCheckRequiredFlags(t *testing.T) { @@ -432,13 +359,13 @@ func TestCheckRequiredFlags(t *testing.T) { { testCase: "optional", flags: []Flag{ - StringFlag{Name: "optionalFlag"}, + &StringFlag{Name: "optionalFlag"}, }, }, { testCase: "required", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "requiredFlag", Required: true}, }, expectedAnError: true, expectedErrorContents: []string{"requiredFlag"}, @@ -446,30 +373,30 @@ func TestCheckRequiredFlags(t *testing.T) { { testCase: "required_and_present", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "requiredFlag", Required: true}, }, parseInput: []string{"--requiredFlag", "myinput"}, }, { testCase: "required_and_present_via_env_var", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true, EnvVar: "REQUIRED_FLAG"}, + &StringFlag{Name: "requiredFlag", Required: true, EnvVars: []string{"REQUIRED_FLAG"}}, }, envVarInput: [2]string{"REQUIRED_FLAG", "true"}, }, { testCase: "required_and_optional", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true}, - StringFlag{Name: "optionalFlag"}, + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "optionalFlag"}, }, expectedAnError: true, }, { testCase: "required_and_optional_and_optional_present", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true}, - StringFlag{Name: "optionalFlag"}, + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "optionalFlag"}, }, parseInput: []string{"--optionalFlag", "myinput"}, expectedAnError: true, @@ -477,8 +404,8 @@ func TestCheckRequiredFlags(t *testing.T) { { testCase: "required_and_optional_and_optional_present_via_env_var", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true}, - StringFlag{Name: "optionalFlag", EnvVar: "OPTIONAL_FLAG"}, + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "optionalFlag", EnvVars: []string{"OPTIONAL_FLAG"}}, }, envVarInput: [2]string{"OPTIONAL_FLAG", "true"}, expectedAnError: true, @@ -486,16 +413,16 @@ func TestCheckRequiredFlags(t *testing.T) { { testCase: "required_and_optional_and_required_present", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true}, - StringFlag{Name: "optionalFlag"}, + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "optionalFlag"}, }, parseInput: []string{"--requiredFlag", "myinput"}, }, { testCase: "two_required", flags: []Flag{ - StringFlag{Name: "requiredFlagOne", Required: true}, - StringFlag{Name: "requiredFlagTwo", Required: true}, + &StringFlag{Name: "requiredFlagOne", Required: true}, + &StringFlag{Name: "requiredFlagTwo", Required: true}, }, expectedAnError: true, expectedErrorContents: []string{"requiredFlagOne", "requiredFlagTwo"}, @@ -503,8 +430,8 @@ func TestCheckRequiredFlags(t *testing.T) { { testCase: "two_required_and_one_present", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true}, - StringFlag{Name: "requiredFlagTwo", Required: true}, + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "requiredFlagTwo", Required: true}, }, parseInput: []string{"--requiredFlag", "myinput"}, expectedAnError: true, @@ -512,44 +439,47 @@ func TestCheckRequiredFlags(t *testing.T) { { testCase: "two_required_and_both_present", flags: []Flag{ - StringFlag{Name: "requiredFlag", Required: true}, - StringFlag{Name: "requiredFlagTwo", Required: true}, + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "requiredFlagTwo", Required: true}, }, parseInput: []string{"--requiredFlag", "myinput", "--requiredFlagTwo", "myinput"}, }, { testCase: "required_flag_with_short_name", flags: []Flag{ - StringSliceFlag{Name: "names, N", Required: true}, + &StringSliceFlag{Name: "names", Aliases: []string{"N"}, Required: true}, }, parseInput: []string{"-N", "asd", "-N", "qwe"}, }, { testCase: "required_flag_with_multiple_short_names", flags: []Flag{ - StringSliceFlag{Name: "names, N, n", Required: true}, + &StringSliceFlag{Name: "names", Aliases: []string{"N", "n"}, Required: true}, }, parseInput: []string{"-n", "asd", "-n", "qwe"}, }, } + for _, test := range tdata { t.Run(test.testCase, func(t *testing.T) { // setup - set := flag.NewFlagSet("test", 0) - for _, flags := range test.flags { - flags.Apply(set) - } - _ = set.Parse(test.parseInput) if test.envVarInput[0] != "" { os.Clearenv() _ = os.Setenv(test.envVarInput[0], test.envVarInput[1]) } - ctx := &Context{} - context := NewContext(ctx.App, set, ctx) - context.Command.Flags = test.flags + + set := flag.NewFlagSet("test", 0) + for _, flags := range test.flags { + _ = flags.Apply(set) + } + _ = set.Parse(test.parseInput) + + c := &Context{} + ctx := NewContext(c.App, set, c) + ctx.Command.Flags = test.flags // logic under test - err := checkRequiredFlags(test.flags, context) + err := checkRequiredFlags(test.flags, ctx) // assertions if test.expectedAnError && err == nil { @@ -559,8 +489,10 @@ func TestCheckRequiredFlags(t *testing.T) { t.Errorf("did not expected an error, but there was one: %s", err) } for _, errString := range test.expectedErrorContents { - if !strings.Contains(err.Error(), errString) { - t.Errorf("expected error %q to contain %q, but it didn't!", err.Error(), errString) + if err != nil { + if !strings.Contains(err.Error(), errString) { + t.Errorf("expected error %q to contain %q, but it didn't!", err.Error(), errString) + } } } }) diff --git a/docs.go b/docs.go index 5b94566128..a8f5de92e5 100644 --- a/docs.go +++ b/docs.go @@ -53,10 +53,9 @@ func (a *App) writeDocTemplate(w io.Writer) error { }) } -func prepareCommands(commands []Command, level int) []string { - coms := []string{} - for i := range commands { - command := &commands[i] +func prepareCommands(commands []*Command, level int) []string { + var coms []string + for _, command := range commands { if command.Hidden { continue } @@ -110,7 +109,8 @@ func prepareFlags( continue } modifiedArg := opener - for _, s := range strings.Split(flag.GetName(), ",") { + + for _, s := range flag.Names() { trimmed := strings.TrimSpace(s) if len(modifiedArg) > len(opener) { modifiedArg += sep diff --git a/docs/v1/CHANGELOG.md b/docs/CHANGELOG.md similarity index 94% rename from docs/v1/CHANGELOG.md rename to docs/CHANGELOG.md index 7c1a27ec7a..9302459666 100644 --- a/docs/v1/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,7 +2,34 @@ **ATTN**: This project uses [semantic versioning](http://semver.org/). -## [Unreleased] +## 2.0.0 - (unreleased 2.x series) +### Added +- `NewStringSlice` and `NewIntSlice` for creating their related types +- `Float64SliceFlag` for unmarshaling a list of floats from the user +- `Context.Lineage` to get all contexts from current up to global +- `Context.LocalFlagNames` to get the flag names from *only* the current context +- `BoolFlag.Value` to handle both default-false and default-true + +### Changed +- `Context.FlagNames` now returns all flags in the context lineage +- `Context.IsSet` now considers the full context lineage +- Added `IsSet` method to the `Flag` interface which allows us to detect whether or not a flag has been set + +### Removed +- the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`. +- adapter code for deprecated `Action` func signature +- deprecated `App.Author`, `App.Email`, and `Command.ShortName` fields +- All `Context.Global*` methods, as the non-global versions now traverse up + the context lineage automatically. +- `Context.Parent` method, as this is now available via `Context.Lineage` +- `BoolTFlag` and related code, as this is now available via `BoolFlag.Value` + +## [Unreleased] - (1.x series) +### Added + +### Changed + +### Removed ## [1.22.1] - 2019-09-11 diff --git a/docs/v2/.keep b/docs/v2/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/v2/manual.md b/docs/v2/manual.md new file mode 100644 index 0000000000..d3943081b4 --- /dev/null +++ b/docs/v2/manual.md @@ -0,0 +1,1489 @@ +cli v2 manual +=== + + + +- [Getting Started](#getting-started) +- [Examples](#examples) + * [Arguments](#arguments) + * [Flags](#flags) + + [Placeholder Values](#placeholder-values) + + [Alternate Names](#alternate-names) + + [Ordering](#ordering) + + [Values from the Environment](#values-from-the-environment) + + [Values from files](#values-from-files) + + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + + [Default Values for help output](#default-values-for-help-output) + + [Precedence](#precedence) + * [Subcommands](#subcommands) + * [Subcommands categories](#subcommands-categories) + * [Exit code](#exit-code) + * [Combining short options](#combining-short-options) + * [Bash Completion](#bash-completion) + + [Enabling](#enabling) + + [Distribution](#distribution) + + [Customization](#customization) + * [Generated Help Text](#generated-help-text) + + [Customization](#customization-1) + * [Version Flag](#version-flag) + + [Customization](#customization-2) + * [Full API Example](#full-api-example) + + + +## Getting Started + +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + (&cli.App{}).Run(os.Args) +} +``` + +This app will run and show help text, but is not very useful. Let's give an +action to execute and some help documentation: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "boom", + Usage: "make an explosive entrance", + Action: func(c *cli.Context) error { + fmt.Println("boom! I say!") + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Running this already gives you a ton of functionality, plus support for things +like subcommands and flags, which are covered below. + +## Examples + +Being a programmer can be a lonely job. Thankfully by the power of automation +that is not the case! Let's create a greeter app to fend off our demons of +loneliness! + +Start by creating a directory named `greet`, and within it, add a file, +`greet.go` with the following code in it: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "greet", + Usage: "fight the loneliness!", + Action: func(c *cli.Context) error { + fmt.Println("Hello friend!") + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Install our command to the `$GOPATH/bin` directory: + +``` +$ go install +``` + +Finally run our new command: + +``` +$ greet +Hello friend! +``` + +cli also generates neat help text: + +``` +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +VERSION: + 0.0.0 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --version Shows version information +``` + +### Arguments + +You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Action: func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Flags + +Setting and querying flags is simple. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Action: func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +You can also set a destination variable for a flag, to which the content will be +scanned. + + +``` go +package main + +import ( + "log" + "os" + "fmt" + + "github.com/urfave/cli/v2" +) + +func main() { + var language string + + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + }, + Action: func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +See full list of flags at http://godoc.org/github.com/urfave/cli + +#### Placeholder Values + +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +``` + +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited +list for the `Name`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that +giving two different forms of the same flag in the same command invocation is an +error. + +#### Ordering + +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` +or `CommandsByName` with `sort`. + +For example this: + + +``` go +package main + +import ( + "log" + "os" + "sort" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "Language for the greeting", + }, + &cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + }, + 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)) + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +--lang value, -l value Language for the greeting (default: "english") +``` + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVars`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If `EnvVars` contains more than one string, the first environment variable that +resolves is used as the default. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Values from files + +You can also have the default value set from file via `FilePath`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + &cli.StringFlag{ + Name: "password, p", + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the environment (e.g. `EnvVar`). + +#### Values from alternate input sources (YAML, TOML, and others) + +There is a separate package altsrc that adds support for getting flag values +from other file input sources. + +Currently supported input source formats: +* YAML +* JSON +* TOML + +In order to get values for a flag from an alternate input source the following +code would be added to wrap an existing cli.Flag like below: + +``` go + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing +getting data from a yaml file below. + +``` go + 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. As a note +the "load" flag used would also have to be defined on the command flags in order +for this code snippet to work. + +Currently only YAML, JSON, and TOML files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. + +Here is a more complete sample of a command using YAML support: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" +) + +func main() { + flags := []cli.Flag{ + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}, + } + + app := &cli.App{ + Action: func(c *cli.Context) error { + fmt.Println("yaml ist rad") + return nil + }, + Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), + Flags: flags, + } + + app.Run(os.Args) +} +``` + +#### Default Values for help output + +Sometimes it's useful to specify a flag's default help-text value within the flag declaration. This can be useful if the default value for a flag is a computed value. The default value can be set via the `DefaultText` struct field. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "port", + Usage: "Use a randomized port", + Value: 0, + DefaultText: "random", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--port value Use a randomized port (default: random) +``` + +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag + +### Subcommands + +Subcommands can be defined for a more git-like command line app. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, + }, + }, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Subcommands categories + +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will include: + +``` +COMMANDS: + noop + + Template actions: + add + remove +``` + +### Exit code + +Calling `App.Run` will not automatically call `os.Exit`, which means that by +default the exit code will "fall through" to being `0`. An explicit exit code +may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a +`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "ginger-crouton", + Usage: "is it in the soup?", + }, + }, + Action: func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.Exit("Ginger croutons are not in the soup", 86) + } + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Combining short options + +Traditional use of options using their shortnames look like this: + +``` +$ cmd -s -o -m "Some message" +``` + +Suppose you want users to be able to combine options with their shortnames. This +can be done using the `UseShortOptionHandling` bool in your app configuration, +or for individual commands by attaching it to the command configuration. For +example: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{} + app.UseShortOptionHandling = true + app.Commands = []*cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, + &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, + }, + Action: func(c *cli.Context) error { + fmt.Println("serve:", c.Bool("serve")) + fmt.Println("option:", c.Bool("option")) + fmt.Println("message:", c.String("message")) + return nil + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If your program has any number of bool flags such as `serve` and `option`, and +optionally one non-bool flag `message`, with the short options of `-s`, `-o`, +and `-m` respectively, setting `UseShortOptionHandling` will also support the +following syntax: + +``` +$ cmd -som "Some message" +``` + +If you enable `UseShortOptionHandling`, then you must not use any flags that +have a single leading `-` or this will result in failures. For example, +`-option` can no longer be used. Flags with two leading dashes (such as +`--options`) are still valid. + +### Bash Completion + +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object. By default, this setting will only auto-complete to +show an app's subcommands, but you can write your own completion methods for +the App or its subcommands. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Enabling + +Source the `autocomplete/bash_autocomplete` file in your .bashrc file while setting the `PROG` variable to the name of your program: + +``` +PROG=myprogram source /.../cli/autocomplete/bash_autocomplete +``` + +#### Distribution + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file to make it active in the current shell. + +``` +sudo cp src/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should source the generic +`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set +to the name of their program (as above). + +#### Customization + +The default shell completion flag (`--generate-bash-completion`) is defined as +`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "wat", + }, + }, + } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Generated Help Text + +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization + +All of the help text generation may be customized, and at multiple levels. The +templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and +`SubcommandHelpTemplate` which may be reassigned or augmented, and full override +is possible by assigning a compatible func to the `cli.HelpPrinter` variable, +e.g.: + + +``` go +package main + +import ( + "fmt" + "io" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s + +WEBSITE: http://awesometown.example.com + +SUPPORT: support@awesometown.example.com + +`, cli.AppHelpTemplate) + + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if len .Authors}} +AUTHOR: + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +GLOBAL OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}}{{if .Version}} +VERSION: + {{.Version}} + {{end}} +` + + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } + + (&cli.App{}).Run(os.Args) +} +``` + +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + cli.HelpFlag = &cli.BoolFlag{ + Name: "haaaaalp", Aliases: []string{"halp"}, + Usage: "HALP", + EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, + } + + (&cli.App{}).Run(os.Args) +} +``` + +### Version Flag + +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be customized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + cli.VersionFlag = &cli.BoolFlag{ + Name: "print-version", Aliases: []string{"V"}, + Usage: "print only the version", + } + + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) +} +``` + +### Full API Example + +**Notice**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + + +``` go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/urfave/cli/v2" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = &cli.BoolFlag{Name: "halp"} + cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) + } + cli.OsExiter = func(c int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.Names()[0]) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +type genericType struct { + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + +func main() { + app := &cli.App{ + Name: "kənˈtrīv", + Version: "v19.99.0", + Compiled: time.Now(), + Authors: []*cli.Author{ + &cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + }, + Copyright: "(c) 1999 Serious Enterprise", + HelpName: "contrive", + Usage: "demonstrate available API", + UsageText: "contrive - demonstrating the available API", + ArgsUsage: "[args and such]", + Commands: []*cli.Command{ + &cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, + }, + Subcommands: []*cli.Command{ + &cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err + }, + }, + }, + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "fancy"}, + &cli.BoolFlag{Value: true, Name: "fancier"}, + &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, + &cli.Float64Flag{Name: "howmuch"}, + &cli.GenericFlag{Name: "wat", Value: &genericType{}}, + &cli.Int64Flag{Name: "longdistance"}, + &cli.Int64SliceFlag{Name: "intervals"}, + &cli.IntFlag{Name: "distance"}, + &cli.IntSliceFlag{Name: "times"}, + &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, + &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, + &cli.UintFlag{Name: "age"}, + &cli.Uint64Flag{Name: "bigage"}, + }, + EnableBashCompletion: true, + HideHelp: false, + HideVersion: false, + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + }, + CommandNotFound: func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + }, + Action: func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) + + categories := c.App.Categories + categories.AddCommand("sounds", &cli.Command{ + Name: "bloop", + }) + + for _, category := range c.App.Categories.Categories() { + fmt.Fprintf(c.App.Writer, "%s\n", category.Name) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + } + + fmt.Printf("%#v\n", c.App.Command("doo")) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } + + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } + c.App.Setup() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) + + fmt.Printf("%#v\n", c.Args().First()) + if c.Args().Len() > 0 { + fmt.Printf("%#v\n", c.Args().Get(1)) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", !nc.Bool("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.IsSet("wat")) + fmt.Printf("%#v\n", nc.Set("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Lineage()[1]) + + nc.Set("wat", "also-nope") + + ec := cli.Exit("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return ec + }, + Metadata: map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + }, + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Run(os.Args) +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` diff --git a/docs_test.go b/docs_test.go index f18d12d2f9..e685f90950 100644 --- a/docs_test.go +++ b/docs_test.go @@ -9,39 +9,44 @@ func testApp() *App { app := NewApp() app.Name = "greet" app.Flags = []Flag{ - StringFlag{ - Name: "socket, s", + &StringFlag{ + Name: "socket", + Aliases: []string{"s"}, Usage: "some 'usage' text", Value: "value", TakesFile: true, }, - StringFlag{Name: "flag, fl, f"}, - BoolFlag{ - Name: "another-flag, b", - Usage: "another usage text", + &StringFlag{Name: "flag", Aliases: []string{"fl", "f"}}, + &BoolFlag{ + Name: "another-flag", + Aliases: []string{"b"}, + Usage: "another usage text", }, } - app.Commands = []Command{{ + app.Commands = []*Command{{ Aliases: []string{"c"}, Flags: []Flag{ - StringFlag{ - Name: "flag, fl, f", + &StringFlag{ + Name: "flag", + Aliases: []string{"fl", "f"}, TakesFile: true, }, - BoolFlag{ - Name: "another-flag, b", - Usage: "another usage text", + &BoolFlag{ + Name: "another-flag", + Aliases: []string{"b"}, + Usage: "another usage text", }, }, Name: "config", Usage: "another usage test", - Subcommands: []Command{{ + Subcommands: []*Command{{ Aliases: []string{"s", "ss"}, Flags: []Flag{ - StringFlag{Name: "sub-flag, sub-fl, s"}, - BoolFlag{ - Name: "sub-command-flag, s", - Usage: "some usage text", + &StringFlag{Name: "sub-flag", Aliases: []string{"sub-fl", "s"}}, + &BoolFlag{ + Name: "sub-command-flag", + Aliases: []string{"s"}, + Usage: "some usage text", }, }, Name: "sub-config", @@ -59,9 +64,7 @@ func testApp() *App { }} app.UsageText = "app [first_arg] [second_arg]" app.Usage = "Some app" - app.Author = "Harrison" - app.Email = "harrison@lolwut.com" - app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Authors = []*Author{{Name: "Harrison", Email: "harrison@lolwut.com"}} return app } diff --git a/errors.go b/errors.go index 562b2953cf..be589033aa 100644 --- a/errors.go +++ b/errors.go @@ -15,25 +15,39 @@ var OsExiter = os.Exit var ErrWriter io.Writer = os.Stderr // MultiError is an error that wraps multiple errors. -type MultiError struct { - Errors []error +type MultiError interface { + error + // Errors returns a copy of the errors slice + Errors() []error } // NewMultiError creates a new MultiError. Pass in one or more errors. -func NewMultiError(err ...error) MultiError { - return MultiError{Errors: err} +func newMultiError(err ...error) MultiError { + ret := multiError(err) + return &ret } +type multiError []error + // Error implements the error interface. -func (m MultiError) Error() string { - errs := make([]string, len(m.Errors)) - for i, err := range m.Errors { +func (m *multiError) Error() string { + errs := make([]string, len(*m)) + for i, err := range *m { errs[i] = err.Error() } return strings.Join(errs, "\n") } +// Errors returns a copy of the errors slice +func (m *multiError) Errors() []error { + errs := make([]error, len(*m)) + for _, err := range *m { + errs = append(errs, err) + } + return errs +} + type ErrorFormatter interface { Format(s fmt.State, verb rune) } @@ -45,29 +59,30 @@ type ExitCoder interface { ExitCode() int } -// ExitError fulfills both the builtin `error` interface and `ExitCoder` -type ExitError struct { +type exitError struct { exitCode int message interface{} } -// NewExitError makes a new *ExitError -func NewExitError(message interface{}, exitCode int) *ExitError { - return &ExitError{ - exitCode: exitCode, +// NewExitError makes a new *exitError +func NewExitError(message interface{}, exitCode int) ExitCoder { + return Exit(message, exitCode) +} + +// Exit wraps a message and exit code into an ExitCoder suitable for handling by +// HandleExitCoder +func Exit(message interface{}, exitCode int) ExitCoder { + return &exitError{ message: message, + exitCode: exitCode, } } -// Error returns the string message, fulfilling the interface required by -// `error` -func (ee *ExitError) Error() string { +func (ee *exitError) Error() string { return fmt.Sprintf("%v", ee.message) } -// ExitCode returns the exit code, fulfilling the interface required by -// `ExitCoder` -func (ee *ExitError) ExitCode() int { +func (ee *exitError) ExitCode() int { return ee.exitCode } @@ -83,9 +98,9 @@ func HandleExitCoder(err error) { if exitErr, ok := err.(ExitCoder); ok { if err.Error() != "" { if _, ok := exitErr.(ErrorFormatter); ok { - fmt.Fprintf(ErrWriter, "%+v\n", err) + _, _ = fmt.Fprintf(ErrWriter, "%+v\n", err) } else { - fmt.Fprintln(ErrWriter, err) + _, _ = fmt.Fprintln(ErrWriter, err) } } OsExiter(exitErr.ExitCode()) @@ -101,10 +116,10 @@ func HandleExitCoder(err error) { func handleMultiError(multiErr MultiError) int { code := 1 - for _, merr := range multiErr.Errors { + for _, merr := range multiErr.Errors() { if multiErr2, ok := merr.(MultiError); ok { code = handleMultiError(multiErr2) - } else { + } else if merr != nil { fmt.Fprintln(ErrWriter, merr) if exitErr, ok := merr.(ExitCoder); ok { code = exitErr.ExitCode() diff --git a/errors_test.go b/errors_test.go index 9b609c596f..9ed3be3c12 100644 --- a/errors_test.go +++ b/errors_test.go @@ -39,7 +39,7 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) { defer func() { OsExiter = fakeOsExiter }() - HandleExitCoder(NewExitError("galactic perimeter breach", 9)) + HandleExitCoder(Exit("galactic perimeter breach", 9)) expect(t, exitCode, 9) expect(t, called, true) @@ -58,9 +58,9 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { defer func() { OsExiter = fakeOsExiter }() - exitErr := NewExitError("galactic perimeter breach", 9) - exitErr2 := NewExitError("last ExitCoder", 11) - err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2) + exitErr := Exit("galactic perimeter breach", 9) + exitErr2 := Exit("last ExitCoder", 11) + err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2) HandleExitCoder(err) expect(t, exitCode, 11) @@ -95,7 +95,7 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { ErrWriter = fakeErrWriter }() - err := NewExitError(NewErrorWithFormat("I am formatted"), 1) + err := Exit(NewErrorWithFormat("I am formatted"), 1) HandleExitCoder(err) expect(t, called, true) @@ -114,7 +114,7 @@ func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { defer func() { OsExiter = fakeOsExiter }() - err := NewMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2")) + err := newMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2")) HandleExitCoder(err) expect(t, called, true) diff --git a/fish.go b/fish.go index cf183af611..67122c9fe7 100644 --- a/fish.go +++ b/fish.go @@ -64,11 +64,9 @@ func (a *App) writeFishCompletionTemplate(w io.Writer) error { }) } -func (a *App) prepareFishCommands(commands []Command, allCommands *[]string, previousCommands []string) []string { +func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, previousCommands []string) []string { completions := []string{} - for i := range commands { - command := &commands[i] - + for _, command := range commands { if command.Hidden { continue } @@ -131,7 +129,7 @@ func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string fishAddFileFlag(f, completion) - for idx, opt := range strings.Split(flag.GetName(), ",") { + for idx, opt := range flag.Names() { if idx == 0 { completion.WriteString(fmt.Sprintf( " -l %s", strings.TrimSpace(opt), @@ -161,15 +159,15 @@ func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string func fishAddFileFlag(flag Flag, completion *strings.Builder) { switch f := flag.(type) { - case GenericFlag: + case *GenericFlag: if f.TakesFile { return } - case StringFlag: + case *StringFlag: if f.TakesFile { return } - case StringSliceFlag: + case *StringSliceFlag: if f.TakesFile { return } diff --git a/flag.go b/flag.go index 1cfa1cdb21..ec128fd440 100644 --- a/flag.go +++ b/flag.go @@ -5,38 +5,53 @@ import ( "fmt" "io/ioutil" "reflect" + "regexp" "runtime" "strconv" "strings" "syscall" + "time" ) const defaultPlaceholder = "value" +var ( + slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) + + commaWhitespace = regexp.MustCompile("[, ]+.*") +) + // BashCompletionFlag enables bash-completion for all commands and subcommands -var BashCompletionFlag Flag = BoolFlag{ +var BashCompletionFlag Flag = &BoolFlag{ Name: "generate-bash-completion", Hidden: true, } // VersionFlag prints the version for the application -var VersionFlag Flag = BoolFlag{ - Name: "version, v", - Usage: "print the version", +var VersionFlag Flag = &BoolFlag{ + Name: "version", + Aliases: []string{"v"}, + Usage: "print the version", } -// HelpFlag prints the help for all commands and subcommands -// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand -// unless HideHelp is set to true) -var HelpFlag Flag = BoolFlag{ - Name: "help, h", - Usage: "show help", +// HelpFlag prints the help for all commands and subcommands. +// Set to nil to disable the flag. The subcommand +// will still be added unless HideHelp is set to true. +var HelpFlag Flag = &BoolFlag{ + Name: "help", + Aliases: []string{"h"}, + Usage: "show help", } // FlagStringer converts a flag definition to a string. This is used by help // to display a flag. var FlagStringer FlagStringFunc = stringifyFlag +// Serializer is used to circumvent the limitations of flag.FlagSet.Set +type Serializer interface { + Serialize() string +} + // FlagNamePrefixer converts a full flag name and its placeholder into the help // message flag prefix. This is used by the default FlagStringer. var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames @@ -57,7 +72,12 @@ func (f FlagsByName) Len() int { } func (f FlagsByName) Less(i, j int) bool { - return lexicographicLess(f[i].GetName(), f[j].GetName()) + if len(f[j].Names()) == 0 { + return false + } else if len(f[i].Names()) == 0 { + return true + } + return lexicographicLess(f[i].Names()[0], f[j].Names()[0]) } func (f FlagsByName) Swap(i, j int) { @@ -70,8 +90,9 @@ func (f FlagsByName) Swap(i, j int) { type Flag interface { fmt.Stringer // Apply Flag settings to the given flag set - Apply(*flag.FlagSet) - GetName() string + Apply(*flag.FlagSet) error + Names() []string + IsSet() bool } // RequiredFlag is an interface that allows us to mark flags as required @@ -97,40 +118,18 @@ type DocGenerationFlag interface { GetValue() string } -// 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 { - //TODO remove in v2 when errorableFlag is removed - if ef, ok := f.(errorableFlag); ok { - if err := ef.ApplyWithError(set); err != nil { - return nil, err - } - } else { - f.Apply(set) + if err := f.Apply(set); err != nil { + return nil, err } } set.SetOutput(ioutil.Discard) return set, nil } -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} - func visibleFlags(fl []Flag) []Flag { var visible []Flag for _, f := range fl { @@ -169,25 +168,27 @@ func unquoteUsage(usage string) (string, string) { return "", usage } -func prefixedNames(fullName, placeholder string) string { +func prefixedNames(names []string, placeholder string) string { var prefixed string - parts := strings.Split(fullName, ",") - for i, name := range parts { - name = strings.Trim(name, " ") + for i, name := range names { + if name == "" { + continue + } + prefixed += prefixFor(name) + name if placeholder != "" { prefixed += " " + placeholder } - if i < len(parts)-1 { + if i < len(names)-1 { prefixed += ", " } } return prefixed } -func withEnvHint(envVar, str string) string { +func withEnvHint(envVars []string, str string) string { envText := "" - if envVar != "" { + if envVars != nil && len(envVars) > 0 { prefix := "$" suffix := "" sep := ", $" @@ -196,11 +197,51 @@ func withEnvHint(envVar, str string) string { suffix = "%" sep = "%, %" } - envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]" + + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix) } return str + envText } +func flagNames(f Flag) []string { + var ret []string + + name := flagStringField(f, "Name") + aliases := flagStringSliceField(f, "Aliases") + + for _, part := range append([]string{name}, aliases...) { + // v1 -> v2 migration warning zone: + // Strip off anything after the first found comma or space, which + // *hopefully* makes it a tiny bit more obvious that unexpected behavior is + // caused by using the v1 form of stringly typed "Name". + ret = append(ret, commaWhitespace.ReplaceAllString(part, "")) + } + + return ret +} + +func flagStringSliceField(f Flag, name string) []string { + fv := flagValue(f) + field := fv.FieldByName(name) + + if field.IsValid() { + return field.Interface().([]string) + } + + return []string{} +} + +func flagStringField(f Flag, name string) string { + fv := flagValue(f) + field := fv.FieldByName(name) + + if field.IsValid() { + return field.String() + } + + return "" +} + func withFileHint(filePath, str string) string { fileText := "" if filePath != "" { @@ -221,39 +262,27 @@ func stringifyFlag(f Flag) string { fv := flagValue(f) switch f.(type) { - case IntSliceFlag: - return FlagFileHinter( - fv.FieldByName("FilePath").String(), - FlagEnvHinter( - fv.FieldByName("EnvVar").String(), - stringifyIntSliceFlag(f.(IntSliceFlag)), - ), - ) - case Int64SliceFlag: - return FlagFileHinter( - fv.FieldByName("FilePath").String(), - FlagEnvHinter( - fv.FieldByName("EnvVar").String(), - stringifyInt64SliceFlag(f.(Int64SliceFlag)), - ), - ) - case StringSliceFlag: - return FlagFileHinter( - fv.FieldByName("FilePath").String(), - FlagEnvHinter( - fv.FieldByName("EnvVar").String(), - stringifyStringSliceFlag(f.(StringSliceFlag)), - ), - ) + case *IntSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyIntSliceFlag(f.(*IntSliceFlag))) + case *Int64SliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyInt64SliceFlag(f.(*Int64SliceFlag))) + case *Float64SliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyFloat64SliceFlag(f.(*Float64SliceFlag))) + case *StringSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyStringSliceFlag(f.(*StringSliceFlag))) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) needsPlaceholder := false defaultValueString := "" - - if val := fv.FieldByName("Value"); val.IsValid() { - needsPlaceholder = true + val := fv.FieldByName("Value") + if val.IsValid() { + needsPlaceholder = val.Kind() != reflect.Bool defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) if val.Kind() == reflect.String && val.String() != "" { @@ -261,6 +290,12 @@ func stringifyFlag(f Flag) string { } } + helpText := fv.FieldByName("DefaultText") + if helpText.IsValid() && helpText.String() != "" { + needsPlaceholder = val.Kind() != reflect.Bool + defaultValueString = fmt.Sprintf(" (default: %s)", helpText.String()) + } + if defaultValueString == " (default: )" { defaultValueString = "" } @@ -271,16 +306,11 @@ func stringifyFlag(f Flag) string { usageWithDefault := strings.TrimSpace(usage + defaultValueString) - return FlagFileHinter( - fv.FieldByName("FilePath").String(), - FlagEnvHinter( - fv.FieldByName("EnvVar").String(), - FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault, - ), - ) + return withEnvHint(flagStringSliceField(f, "EnvVars"), + fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } -func stringifyIntSliceFlag(f IntSliceFlag) string { +func stringifyIntSliceFlag(f *IntSliceFlag) string { var defaultVals []string if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { @@ -288,10 +318,10 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } -func stringifyInt64SliceFlag(f Int64SliceFlag) string { +func stringifyInt64SliceFlag(f *Int64SliceFlag) string { var defaultVals []string if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { @@ -299,10 +329,22 @@ func stringifyInt64SliceFlag(f Int64SliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + +func stringifyFloat64SliceFlag(f *Float64SliceFlag) string { + var defaultVals []string + + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } -func stringifyStringSliceFlag(f StringSliceFlag) string { +func stringifyStringSliceFlag(f *StringSliceFlag) string { var defaultVals []string if f.Value != nil && len(f.Value.Value()) > 0 { for _, s := range f.Value.Value() { @@ -312,10 +354,10 @@ func stringifyStringSliceFlag(f StringSliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } -func stringifySliceFlag(usage, name string, defaultVals []string) string { +func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { placeholder = defaultPlaceholder @@ -326,15 +368,25 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) } - usageWithDefault := strings.TrimSpace(usage + defaultVal) - return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault + usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) + return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault) +} + +func hasFlag(flags []Flag, fl Flag) bool { + for _, existing := range flags { + if fl == existing { + return true + } + } + + return false } -func flagFromFileEnv(filePath, envName string) (val string, ok bool) { - for _, envVar := range strings.Split(envName, ",") { +func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) { + for _, envVar := range envVars { envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - return envVal, true + if val, ok := syscall.Getenv(envVar); ok { + return val, true } } for _, fileVar := range strings.Split(filePath, ",") { diff --git a/flag_bool.go b/flag_bool.go index 2499b0b524..6a1da61efd 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -9,93 +9,90 @@ import ( // BoolFlag is a flag with type bool type BoolFlag struct { Name string + Aliases []string Usage string - EnvVar string + EnvVars []string FilePath string Required bool Hidden bool + Value bool + DefaultText string Destination *bool + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *BoolFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f BoolFlag) String() string { +func (f *BoolFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f BoolFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *BoolFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f BoolFlag) IsRequired() bool { +func (f *BoolFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f BoolFlag) TakesValue() bool { +func (f *BoolFlag) TakesValue() bool { return false } // GetUsage returns the usage string for the flag -func (f BoolFlag) GetUsage() string { +func (f *BoolFlag) GetUsage() string { return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f BoolFlag) GetValue() string { +func (f *BoolFlag) GetValue() string { return "" } -// Bool looks up the value of a local BoolFlag, returns -// false if not found -func (c *Context) Bool(name string) bool { - return lookupBool(name, c.flagSet) -} - -// GlobalBool looks up the value of a global BoolFlag, returns -// false if not found -func (c *Context) GlobalBool(name string) bool { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupBool(name, fs) - } - return false -} - // Apply populates the flag given the flag set and environment -// Ignores errors -func (f BoolFlag) Apply(set *flag.FlagSet) { - _ = f.ApplyWithError(set) -} +func (f *BoolFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valBool, err := strconv.ParseBool(val) -// ApplyWithError populates the flag given the flag set and environment -func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { - val := false - if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - if envVal == "" { - val = false - } else { - envValBool, err := strconv.ParseBool(envVal) if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as bool value for flag %s: %s", val, f.Name, err) } - val = envValBool + + f.Value = valBool + f.HasBeenSet = true } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { - set.BoolVar(f.Destination, name, val, f.Usage) - return + set.BoolVar(f.Destination, name, f.Value, f.Usage) + continue } - set.Bool(name, val, f.Usage) - }) + set.Bool(name, f.Value, f.Usage) + } return nil } +// Bool looks up the value of a local BoolFlag, returns +// false if not found +func (c *Context) Bool(name string) bool { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupBool(name, fs) + } + return false +} + func lookupBool(name string, set *flag.FlagSet) bool { f := set.Lookup(name) if f != nil { diff --git a/flag_bool_t.go b/flag_bool_t.go deleted file mode 100644 index cd0888fa21..0000000000 --- a/flag_bool_t.go +++ /dev/null @@ -1,110 +0,0 @@ -package cli - -import ( - "flag" - "fmt" - "strconv" -) - -// BoolTFlag is a flag with type bool that is true by default -type BoolTFlag struct { - Name string - Usage string - EnvVar string - FilePath string - Required bool - Hidden bool - Destination *bool -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f BoolTFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f BoolTFlag) GetName() string { - return f.Name -} - -// IsRequired returns whether or not the flag is required -func (f BoolTFlag) IsRequired() bool { - return f.Required -} - -// TakesValue returns true of the flag takes a value, otherwise false -func (f BoolTFlag) TakesValue() bool { - return false -} - -// GetUsage returns the usage string for the flag -func (f BoolTFlag) GetUsage() string { - return f.Usage -} - -// GetValue returns the flags value as string representation and an empty -// string if the flag takes no value at all. -func (f BoolTFlag) GetValue() string { - return "" -} - -// BoolT looks up the value of a local BoolTFlag, returns -// false if not found -func (c *Context) BoolT(name string) bool { - return lookupBoolT(name, c.flagSet) -} - -// GlobalBoolT looks up the value of a global BoolTFlag, returns -// false if not found -func (c *Context) GlobalBoolT(name string) bool { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupBoolT(name, fs) - } - return false -} - -// 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 envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - if envVal == "" { - val = false - } else { - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - val = envValBool - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.BoolVar(f.Destination, name, val, f.Usage) - return - } - set.Bool(name, val, f.Usage) - }) - - return nil -} - -func lookupBoolT(name string, set *flag.FlagSet) bool { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseBool(f.Value.String()) - if err != nil { - return false - } - return parsed - } - return false -} diff --git a/flag_duration.go b/flag_duration.go index df4ade589d..2c34944a4b 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -9,90 +9,89 @@ import ( // DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration) type DurationFlag struct { Name string + Aliases []string Usage string - EnvVar string + EnvVars []string FilePath string Required bool Hidden bool Value time.Duration + DefaultText string Destination *time.Duration + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *DurationFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f DurationFlag) String() string { +func (f *DurationFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f DurationFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *DurationFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f DurationFlag) IsRequired() bool { +func (f *DurationFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f DurationFlag) TakesValue() bool { +func (f *DurationFlag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f DurationFlag) GetUsage() string { +func (f *DurationFlag) GetUsage() string { return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f DurationFlag) GetValue() string { +func (f *DurationFlag) GetValue() string { return f.Value.String() } -// Duration looks up the value of a local DurationFlag, returns -// 0 if not found -func (c *Context) Duration(name string) time.Duration { - return lookupDuration(name, c.flagSet) -} - -// GlobalDuration looks up the value of a global DurationFlag, returns -// 0 if not found -func (c *Context) GlobalDuration(name string) time.Duration { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupDuration(name, fs) - } - return 0 -} - // Apply populates the flag given the flag set and environment -// Ignores errors -func (f DurationFlag) Apply(set *flag.FlagSet) { - _ = f.ApplyWithError(set) -} +func (f *DurationFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valDuration, err := time.ParseDuration(val) -// ApplyWithError populates the flag given the flag set and environment -func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { - if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - envValDuration, err := time.ParseDuration(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) - } + if err != nil { + return fmt.Errorf("could not parse %q as duration value for flag %s: %s", val, f.Name, err) + } - f.Value = envValDuration + f.Value = valDuration + f.HasBeenSet = true + } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.DurationVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Duration(name, f.Value, f.Usage) - }) - + } return nil } +// Duration looks up the value of a local DurationFlag, returns +// 0 if not found +func (c *Context) Duration(name string) time.Duration { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupDuration(name, fs) + } + return 0 +} + func lookupDuration(name string, set *flag.FlagSet) time.Duration { f := set.Lookup(name) if f != nil { diff --git a/flag_float64.go b/flag_float64.go index 65398d3b5c..228554715a 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -9,90 +9,90 @@ import ( // Float64Flag is a flag with type float64 type Float64Flag struct { Name string + Aliases []string Usage string - EnvVar string + EnvVars []string FilePath string Required bool Hidden bool Value float64 + DefaultText string Destination *float64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64Flag)IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f Float64Flag) String() string { +func (f *Float64Flag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f Float64Flag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *Float64Flag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f Float64Flag) IsRequired() bool { +func (f *Float64Flag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f Float64Flag) TakesValue() bool { +func (f *Float64Flag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f Float64Flag) GetUsage() string { +func (f *Float64Flag) GetUsage() string { return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f Float64Flag) GetValue() string { +func (f *Float64Flag) GetValue() string { return fmt.Sprintf("%f", f.Value) } -// Float64 looks up the value of a local Float64Flag, returns -// 0 if not found -func (c *Context) Float64(name string) float64 { - return lookupFloat64(name, c.flagSet) -} - -// GlobalFloat64 looks up the value of a global Float64Flag, returns -// 0 if not found -func (c *Context) GlobalFloat64(name string) float64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupFloat64(name, fs) - } - return 0 -} - // Apply populates the flag given the flag set and environment -// Ignores errors -func (f Float64Flag) Apply(set *flag.FlagSet) { - _ = f.ApplyWithError(set) -} +func (f *Float64Flag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valFloat, err := strconv.ParseFloat(val, 10) -// ApplyWithError populates the flag given the flag set and environment -func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { - if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - envValFloat, err := strconv.ParseFloat(envVal, 10) - if err != nil { - return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) - } + if err != nil { + return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err) + } - f.Value = envValFloat + f.Value = valFloat + f.HasBeenSet = true + } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.Float64Var(f.Destination, name, f.Value, f.Usage) - return + continue } set.Float64(name, f.Value, f.Usage) - }) + } return nil } +// Float64 looks up the value of a local Float64Flag, returns +// 0 if not found +func (c *Context) Float64(name string) float64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64(name, fs) + } + return 0 +} + func lookupFloat64(name string, set *flag.FlagSet) float64 { f := set.Lookup(name) if f != nil { diff --git a/flag_float64_slice.go b/flag_float64_slice.go new file mode 100644 index 0000000000..91d2e9d100 --- /dev/null +++ b/flag_float64_slice.go @@ -0,0 +1,165 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// Float64Slice wraps []float64 to satisfy flag.Value +type Float64Slice struct { + slice []float64 + hasBeenSet bool +} + +// NewFloat64Slice makes a *Float64Slice with default values +func NewFloat64Slice(defaults ...float64) *Float64Slice { + return &Float64Slice{slice: append([]float64{}, defaults...)} +} + +// Set parses the value into a float64 and appends it to the list of values +func (f *Float64Slice) Set(value string) error { + if !f.hasBeenSet { + f.slice = []float64{} + f.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) + f.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + + f.slice = append(f.slice, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Float64Slice) String() string { + return fmt.Sprintf("%#v", f.slice) +} + +// Serialize allows Float64Slice to fulfill Serializer +func (f *Float64Slice) Serialize() string { + jsonBytes, _ := json.Marshal(f.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of float64s set by this flag +func (f *Float64Slice) Value() []float64 { + return f.slice +} + +// Get returns the slice of float64s set by this flag +func (f *Float64Slice) Get() interface{} { + return *f +} + +// Float64SliceFlag is a flag with type *Float64Slice +type Float64SliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *Float64Slice + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Float64SliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Float64SliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Float64SliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Float64SliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Float64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Float64SliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + f.Value = &Float64Slice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) + } + } + + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &Float64Slice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// Float64Slice looks up the value of a local Float64SliceFlag, returns +// nil if not found +func (c *Context) Float64Slice(name string) []float64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64Slice(name, fs) + } + return nil +} + +func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 { + f := set.Lookup(name) + if f != nil { + parsed, err := (f.Value.(*Float64Slice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/flag_generic.go b/flag_generic.go index c43dae7d0b..2d9baa78de 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -13,45 +13,53 @@ type Generic interface { // GenericFlag is a flag with type Generic type GenericFlag struct { - Name string - Usage string - EnvVar string - FilePath string - Required bool - Hidden bool - TakesFile bool - Value Generic + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value Generic + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *GenericFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f GenericFlag) String() string { +func (f *GenericFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f GenericFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *GenericFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f GenericFlag) IsRequired() bool { +func (f *GenericFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f GenericFlag) TakesValue() bool { +func (f *GenericFlag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f GenericFlag) GetUsage() string { +func (f *GenericFlag) GetUsage() string { return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f GenericFlag) GetValue() string { +func (f *GenericFlag) GetValue() string { if f.Value != nil { return f.Value.String() } @@ -60,24 +68,20 @@ func (f GenericFlag) GetValue() string { // 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 fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - if err := val.Set(fileEnvVal); err != nil { - return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err) +func (f GenericFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + if err := f.Value.Set(val); err != nil { + return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err) + } + + f.HasBeenSet = true } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } return nil } @@ -85,13 +89,7 @@ func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { - return lookupGeneric(name, c.flagSet) -} - -// GlobalGeneric looks up the value of a global GenericFlag, returns -// nil if not found -func (c *Context) GlobalGeneric(name string) interface{} { - if fs := lookupGlobalFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { return lookupGeneric(name, fs) } return nil diff --git a/flag_int.go b/flag_int.go index bae32e2818..be961bf5f1 100644 --- a/flag_int.go +++ b/flag_int.go @@ -9,70 +9,77 @@ import ( // IntFlag is a flag with type int type IntFlag struct { Name string + Aliases []string Usage string - EnvVar string + EnvVars []string FilePath string Required bool Hidden bool Value int + DefaultText string Destination *int + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f IntFlag) String() string { +func (f *IntFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f IntFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *IntFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f IntFlag) IsRequired() bool { +func (f *IntFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f IntFlag) TakesValue() bool { +func (f *IntFlag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f IntFlag) GetUsage() string { +func (f *IntFlag) GetUsage() string { return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f IntFlag) GetValue() string { +func (f *IntFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } // Apply populates the flag given the flag set and environment -// Ignores errors -func (f IntFlag) Apply(set *flag.FlagSet) { - _ = f.ApplyWithError(set) -} +func (f *IntFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseInt(val, 0, 64) -// ApplyWithError populates the flag given the flag set and environment -func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { - if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) + if err != nil { + return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + } + + f.Value = int(valInt) + f.HasBeenSet = true } - f.Value = int(envValInt) } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.IntVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Int(name, f.Value, f.Usage) - }) + } return nil } @@ -80,13 +87,7 @@ func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { - return lookupInt(name, c.flagSet) -} - -// GlobalInt looks up the value of a global IntFlag, returns -// 0 if not found -func (c *Context) GlobalInt(name string) int { - if fs := lookupGlobalFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { return lookupInt(name, fs) } return 0 diff --git a/flag_int64.go b/flag_int64.go index aaafbe9d6d..c979119f8e 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -9,85 +9,84 @@ import ( // Int64Flag is a flag with type int64 type Int64Flag struct { Name string + Aliases []string Usage string - EnvVar string + EnvVars []string FilePath string Required bool Hidden bool Value int64 + DefaultText string Destination *int64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64Flag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f Int64Flag) String() string { +func (f *Int64Flag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f Int64Flag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *Int64Flag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f Int64Flag) IsRequired() bool { +func (f *Int64Flag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f Int64Flag) TakesValue() bool { +func (f *Int64Flag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f Int64Flag) GetUsage() string { +func (f *Int64Flag) GetUsage() string { return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f Int64Flag) GetValue() string { +func (f *Int64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } // Apply populates the flag given the flag set and environment -// Ignores errors -func (f Int64Flag) Apply(set *flag.FlagSet) { - _ = f.ApplyWithError(set) -} +func (f *Int64Flag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseInt(val, 0, 64) -// ApplyWithError populates the flag given the flag set and environment -func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { - if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } + if err != nil { + return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + } - f.Value = envValInt + f.Value = valInt + f.HasBeenSet = true + } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.Int64Var(f.Destination, name, f.Value, f.Usage) - return + continue } set.Int64(name, f.Value, f.Usage) - }) - + } return nil } // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { - return lookupInt64(name, c.flagSet) -} - -// GlobalInt64 looks up the value of a global Int64Flag, returns -// 0 if not found -func (c *Context) GlobalInt64(name string) int64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { return lookupInt64(name, fs) } return 0 diff --git a/flag_int64_slice.go b/flag_int64_slice.go index ed2e983b62..41aa0667e6 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -1,69 +1,106 @@ package cli import ( + "encoding/json" "flag" "fmt" "strconv" "strings" ) -// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter -type Int64Slice []int64 +// Int64Slice wraps []int64 to satisfy flag.Value +type Int64Slice struct { + slice []int64 + hasBeenSet bool +} + +// NewInt64Slice makes an *Int64Slice with default values +func NewInt64Slice(defaults ...int64) *Int64Slice { + return &Int64Slice{slice: append([]int64{}, defaults...)} +} // Set parses the value into an integer and appends it to the list of values -func (f *Int64Slice) Set(value string) error { - tmp, err := strconv.ParseInt(value, 10, 64) +func (i *Int64Slice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []int64{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseInt(value, 0, 64) if err != nil { return err } - *f = append(*f, tmp) + + i.slice = append(i.slice, tmp) + return nil } // String returns a readable representation of this value (for usage defaults) -func (f *Int64Slice) String() string { - return fmt.Sprintf("%#v", *f) +func (i *Int64Slice) String() string { + return fmt.Sprintf("%#v", i.slice) +} + +// Serialize allows Int64Slice to fulfill Serializer +func (i *Int64Slice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) } // Value returns the slice of ints set by this flag -func (f *Int64Slice) Value() []int64 { - return *f +func (i *Int64Slice) Value() []int64 { + return i.slice } // Get returns the slice of ints set by this flag -func (f *Int64Slice) Get() interface{} { - return *f +func (i *Int64Slice) Get() interface{} { + return *i } // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { - Name string - Usage string - EnvVar string - FilePath string - Required bool - Hidden bool - Value *Int64Slice + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *Int64Slice + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64SliceFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f Int64SliceFlag) String() string { +func (f *Int64SliceFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f Int64SliceFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *Int64SliceFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f Int64SliceFlag) IsRequired() bool { +func (f *Int64SliceFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f Int64SliceFlag) TakesValue() bool { +func (f *Int64SliceFlag) TakesValue() bool { return true } @@ -74,7 +111,7 @@ func (f Int64SliceFlag) GetUsage() string { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f Int64SliceFlag) GetValue() string { +func (f *Int64SliceFlag) GetValue() string { if f.Value != nil { return f.Value.String() } @@ -82,34 +119,26 @@ func (f Int64SliceFlag) GetValue() string { } // 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 envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - newVal := &Int64Slice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - 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) +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = &Int64Slice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) } } - if f.Value == nil { - f.Value = newVal - } else { - *f.Value = *newVal - } + + f.HasBeenSet = true } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Value == nil { f.Value = &Int64Slice{} } set.Var(f.Value, name, f.Usage) - }) + } + return nil } @@ -119,15 +148,6 @@ func (c *Context) Int64Slice(name string) []int64 { return lookupInt64Slice(name, c.flagSet) } -// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns -// nil if not found -func (c *Context) GlobalInt64Slice(name string) []int64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupInt64Slice(name, fs) - } - return nil -} - func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { f := set.Lookup(name) if f != nil { diff --git a/flag_int_slice.go b/flag_int_slice.go index c38d010fd0..9388978421 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -1,69 +1,117 @@ package cli import ( + "encoding/json" "flag" "fmt" "strconv" "strings" ) -// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter -type IntSlice []int +// IntSlice wraps []int to satisfy flag.Value +type IntSlice struct { + slice []int + hasBeenSet bool +} + +// NewIntSlice makes an *IntSlice with default values +func NewIntSlice(defaults ...int) *IntSlice { + return &IntSlice{slice: append([]int{}, defaults...)} +} + +// TODO: Consistently have specific Set function for Int64 and Float64 ? +// SetInt directly adds an integer to the list of values +func (i *IntSlice) SetInt(value int) { + if !i.hasBeenSet { + i.slice = []int{} + i.hasBeenSet = true + } + + i.slice = append(i.slice, value) +} // Set parses the value into an integer and appends it to the list of values -func (f *IntSlice) Set(value string) error { - tmp, err := strconv.Atoi(value) +func (i *IntSlice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []int{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseInt(value, 0, 64) if err != nil { return err } - *f = append(*f, tmp) + + i.slice = append(i.slice, int(tmp)) + return nil } // String returns a readable representation of this value (for usage defaults) -func (f *IntSlice) String() string { - return fmt.Sprintf("%#v", *f) +func (i *IntSlice) String() string { + return fmt.Sprintf("%#v", i.slice) +} + +// Serialize allows IntSlice to fulfill Serializer +func (i *IntSlice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) } // Value returns the slice of ints set by this flag -func (f *IntSlice) Value() []int { - return *f +func (i *IntSlice) Value() []int { + return i.slice } // Get returns the slice of ints set by this flag -func (f *IntSlice) Get() interface{} { - return *f +func (i *IntSlice) Get() interface{} { + return *i } // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { - Name string - Usage string - EnvVar string - FilePath string - Required bool - Hidden bool - Value *IntSlice + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *IntSlice + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntSliceFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f IntSliceFlag) String() string { +func (f *IntSliceFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f IntSliceFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *IntSliceFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f IntSliceFlag) IsRequired() bool { +func (f *IntSliceFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f IntSliceFlag) TakesValue() bool { +func (f *IntSliceFlag) TakesValue() bool { return true } @@ -74,7 +122,7 @@ func (f IntSliceFlag) GetUsage() string { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f IntSliceFlag) GetValue() string { +func (f *IntSliceFlag) GetValue() string { if f.Value != nil { return f.Value.String() } @@ -82,34 +130,25 @@ func (f IntSliceFlag) GetValue() string { } // 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 envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - newVal := &IntSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - 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) +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = &IntSlice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) } } - if f.Value == nil { - f.Value = newVal - } else { - *f.Value = *newVal - } + + f.HasBeenSet = true } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Value == nil { f.Value = &IntSlice{} } set.Var(f.Value, name, f.Usage) - }) + } return nil } @@ -117,14 +156,8 @@ func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { - return lookupIntSlice(name, c.flagSet) -} - -// GlobalIntSlice looks up the value of a global IntSliceFlag, returns -// nil if not found -func (c *Context) GlobalIntSlice(name string) []int { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupIntSlice(name, fs) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupIntSlice(name, c.flagSet) } return nil } diff --git a/flag_path.go b/flag_path.go new file mode 100644 index 0000000000..d6b23c3c55 --- /dev/null +++ b/flag_path.go @@ -0,0 +1,95 @@ +package cli + +import "flag" + +type PathFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value string + DefaultText string + Destination *string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *PathFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *PathFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *PathFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *PathFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *PathFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *PathFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *PathFlag) GetValue() string { + return f.Value +} + +// Apply populates the flag given the flag set and environment +func (f *PathFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = val + f.HasBeenSet = true + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.String(name, f.Value, f.Usage) + } + + return nil +} + +// String looks up the value of a local PathFlag, returns +// "" if not found +func (c *Context) Path(name string) string { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupPath(name, fs) + } + + return "" +} + +func lookupPath(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + parsed, err := f.Value.String(), error(nil) + if err != nil { + return "" + } + return parsed + } + return "" +} diff --git a/flag_string.go b/flag_string.go index 9f29da40b9..bcd82530cc 100644 --- a/flag_string.go +++ b/flag_string.go @@ -5,67 +5,70 @@ import "flag" // StringFlag is a flag with type string type StringFlag struct { Name string + Aliases []string Usage string - EnvVar string + EnvVars []string FilePath string Required bool Hidden bool TakesFile bool Value string + DefaultText string Destination *string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f StringFlag) String() string { +func (f *StringFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f StringFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *StringFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f StringFlag) IsRequired() bool { +func (f *StringFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f StringFlag) TakesValue() bool { +func (f *StringFlag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f StringFlag) GetUsage() string { +func (f *StringFlag) GetUsage() string { return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f StringFlag) GetValue() string { +func (f *StringFlag) GetValue() string { return f.Value } // 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 envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - f.Value = envVal +func (f *StringFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = val + f.HasBeenSet = true } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.StringVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.String(name, f.Value, f.Usage) - }) + } return nil } @@ -73,13 +76,7 @@ func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { - return lookupString(name, c.flagSet) -} - -// GlobalString looks up the value of a global StringFlag, returns -// "" if not found -func (c *Context) GlobalString(name string) string { - if fs := lookupGlobalFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { return lookupString(name, fs) } return "" diff --git a/flag_string_slice.go b/flag_string_slice.go index e865b2ff03..a114a495eb 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -1,76 +1,112 @@ package cli import ( + "encoding/json" "flag" "fmt" "strings" ) -// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter -type StringSlice []string +// StringSlice wraps a []string to satisfy flag.Value +type StringSlice struct { + slice []string + hasBeenSet bool +} + +// NewStringSlice creates a *StringSlice with default values +func NewStringSlice(defaults ...string) *StringSlice { + return &StringSlice{slice: append([]string{}, defaults...)} +} // Set appends the string value to the list of values -func (f *StringSlice) Set(value string) error { - *f = append(*f, value) +func (s *StringSlice) Set(value string) error { + if !s.hasBeenSet { + s.slice = []string{} + s.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &s.slice) + s.hasBeenSet = true + return nil + } + + s.slice = append(s.slice, value) + return nil } // String returns a readable representation of this value (for usage defaults) -func (f *StringSlice) String() string { - return fmt.Sprintf("%s", *f) +func (s *StringSlice) String() string { + return fmt.Sprintf("%s", s.slice) +} + +// Serialize allows StringSlice to fulfill Serializer +func (s *StringSlice) Serialize() string { + jsonBytes, _ := json.Marshal(s.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) } // Value returns the slice of strings set by this flag -func (f *StringSlice) Value() []string { - return *f +func (s *StringSlice) Value() []string { + return s.slice } // Get returns the slice of strings set by this flag -func (f *StringSlice) Get() interface{} { - return *f +func (s *StringSlice) Get() interface{} { + return *s } // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { - Name string - Usage string - EnvVar string - FilePath string - Required bool - Hidden bool - TakesFile bool - Value *StringSlice + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value *StringSlice + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringSliceFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f StringSliceFlag) String() string { +func (f *StringSliceFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f StringSliceFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *StringSliceFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f StringSliceFlag) IsRequired() bool { +func (f *StringSliceFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f StringSliceFlag) TakesValue() bool { +func (f *StringSliceFlag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f StringSliceFlag) GetUsage() string { +func (f *StringSliceFlag) GetUsage() string { return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f StringSliceFlag) GetValue() string { +func (f *StringSliceFlag) GetValue() string { if f.Value != nil { return f.Value.String() } @@ -78,34 +114,25 @@ func (f StringSliceFlag) GetValue() string { } // 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 envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - newVal := &StringSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(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) +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = &StringSlice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) } } - if f.Value == nil { - f.Value = newVal - } else { - *f.Value = *newVal - } + + f.HasBeenSet = true } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Value == nil { f.Value = &StringSlice{} } set.Var(f.Value, name, f.Usage) - }) + } return nil } @@ -113,13 +140,7 @@ func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { - return lookupStringSlice(name, c.flagSet) -} - -// GlobalStringSlice looks up the value of a global StringSliceFlag, returns -// nil if not found -func (c *Context) GlobalStringSlice(name string) []string { - if fs := lookupGlobalFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { return lookupStringSlice(name, fs) } return nil diff --git a/flag_test.go b/flag_test.go index f4a24c2bf9..5d0ecaea94 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1,6 +1,7 @@ package cli import ( + "flag" "fmt" "io" "io/ioutil" @@ -17,14 +18,14 @@ var boolFlagTests = []struct { name string expected string }{ - {"help", "--help\t"}, - {"h", "-h\t"}, + {"help", "--help\t(default: false)"}, + {"h", "-h\t(default: false)"}, } func TestBoolFlagHelpOutput(t *testing.T) { for _, test := range boolFlagTests { - flag := BoolFlag{Name: test.name} - output := flag.String() + fl := &BoolFlag{Name: test.name} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -32,69 +33,96 @@ func TestBoolFlagHelpOutput(t *testing.T) { } } +func TestBoolFlagApply_SetsAllNames(t *testing.T) { + v := false + fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--wat", "-W", "--huh"}) + expect(t, err, nil) + expect(t, v, true) +} + func TestFlagsFromEnv(t *testing.T) { + newSetIntSlice := func(defaults ...int) IntSlice { + s := NewIntSlice(defaults...) + s.hasBeenSet = true + return *s + } + + newSetInt64Slice := func(defaults ...int64) Int64Slice { + s := NewInt64Slice(defaults...) + s.hasBeenSet = true + return *s + } + + newSetStringSlice := func(defaults ...string) StringSlice { + s := NewStringSlice(defaults...) + s.hasBeenSet = true + return *s + } + var flagTests = []struct { input string output interface{} flag Flag errRegexp string }{ - {"", 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"}, ""}, - {"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: .*`)}, + {"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, + {"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, + {"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, + {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value for flag debug: .*`}, - {"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: .*`)}, + {"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""}, + {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration value for flag time: .*`}, - {"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.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value for flag seconds: .*`}, - {"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", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, + {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, - {"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", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, + {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, - {"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", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`}, + {"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`}, - {"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: .*`)}, + {"1,2", newSetInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value for flag seconds: .*`}, + {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value for flag seconds: .*`}, - {"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, ""}, + {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""}, + {"path", "path", &PathFlag{Name: "path", EnvVars: []string{"PATH"}}, ""}, - {"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, ""}, + {"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, - {"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", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value for flag seconds: .*`}, + {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value for flag seconds: .*`}, - {"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: .*`)}, + {"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value for flag seconds: .*`}, + {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value for flag seconds: .*`}, - {"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, ""}, + {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } - for _, test := range flagTests { + for i, test := range flagTests { os.Clearenv() - _ = os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input) + envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) + _ = os.Setenv(envVarSlice.Index(0).String(), test.input) + a := App{ Flags: []Flag{test.flag}, Action: func(ctx *Context) error { - 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())) + if !reflect.DeepEqual(ctx.value(test.flag.Names()[0]), test.output) { + t.Errorf("ex:%01d expected %q to be parsed as %#v, instead was %#v", i, test.input, test.output, ctx.value(test.flag.Names()[0])) } return nil }, @@ -104,15 +132,15 @@ func TestFlagsFromEnv(t *testing.T) { if test.errRegexp != "" { if err == nil { - t.Errorf("expected error to match %s, got none", test.errRegexp) + t.Errorf("expected error to match %q, 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) + t.Errorf("expected error to match %q, got error %s", test.errRegexp, err) } } } else { if err != nil && test.errRegexp == "" { - t.Errorf("expected no error got %s", err) + t.Errorf("expected no error got %q", err) } } } @@ -120,22 +148,23 @@ func TestFlagsFromEnv(t *testing.T) { var stringFlagTests = []struct { name string + aliases []string usage string value string expected string }{ - {"foo", "", "", "--foo value\t"}, - {"f", "", "", "-f value\t"}, - {"f", "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"}, - {"test", "", "Something", "--test value\t(default: \"Something\")"}, - {"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, - {"config,c", "Load configuration from `CONFIG`", "config.json", "--config CONFIG, -c CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, + {"foo", nil, "", "", "--foo value\t"}, + {"f", nil, "", "", "-f value\t"}, + {"f", nil, "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"}, + {"test", nil, "", "Something", "--test value\t(default: \"Something\")"}, + {"config", []string{"c"}, "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, + {"config", []string{"c"}, "Load configuration from `CONFIG`", "config.json", "--config CONFIG, -c CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, } func TestStringFlagHelpOutput(t *testing.T) { for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} - output := flag.String() + fl := &StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -143,12 +172,24 @@ func TestStringFlagHelpOutput(t *testing.T) { } } +func TestStringFlagDefaultText(t *testing.T) { + fl := &StringFlag{Name: "foo", Aliases: nil, Usage: "amount of `foo` requested", Value: "none", DefaultText: "all of it"} + expected := "--foo foo\tamount of foo requested (default: all of it)" + output := fl.String() + + if output != expected { + t.Errorf("%q does not match %q", output, expected) + } +} + func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() _ = os.Setenv("APP_FOO", "derp") + for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} - output := flag.String() + fl := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} + output := fl.String() expectedSuffix := " [$APP_FOO]" if runtime.GOOS == "windows" { @@ -162,112 +203,145 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { var prefixStringFlagTests = []struct { name string + aliases []string usage string value string prefixer FlagNamePrefixFunc expected string }{ - {"foo", "", "", func(a, b string) string { + {name: "foo", usage: "", value: "", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: foo, ph: value\t"}, - {"f", "", "", func(a, b string) string { + }, expected: "name: foo, ph: value\t"}, + {name: "f", usage: "", value: "", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: f, ph: value\t"}, - {"f", "The total `foo` desired", "all", func(a, b string) string { + }, expected: "name: f, ph: value\t"}, + {name: "f", usage: "The total `foo` desired", value: "all", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: f, ph: foo\tThe total foo desired (default: \"all\")"}, - {"test", "", "Something", func(a, b string) string { + }, expected: "name: f, ph: foo\tThe total foo desired (default: \"all\")"}, + {name: "test", usage: "", value: "Something", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: test, ph: value\t(default: \"Something\")"}, - {"config,c", "Load configuration from `FILE`", "", func(a, b string) string { + }, expected: "name: test, ph: value\t(default: \"Something\")"}, + {name: "config", aliases: []string{"c"}, usage: "Load configuration from `FILE`", value: "", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: config,c, ph: FILE\tLoad configuration from FILE"}, - {"config,c", "Load configuration from `CONFIG`", "config.json", func(a, b string) string { + }, expected: "name: config,c, ph: FILE\tLoad configuration from FILE"}, + {name: "config", aliases: []string{"c"}, usage: "Load configuration from `CONFIG`", value: "config.json", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, + }, expected: "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, } -func TestFlagNamePrefixer(t *testing.T) { - defer func() { - FlagNamePrefixer = prefixedNames - }() +func TestStringFlagApply_SetsAllNames(t *testing.T) { + v := "mmm" + fl := StringFlag{Name: "hay", Aliases: []string{"H", "hayyy"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--hay", "u", "-H", "yuu", "--hayyy", "YUUUU"}) + expect(t, err, nil) + expect(t, v, "YUUUU") +} + +var pathFlagTests = []struct { + name string + aliases []string + usage string + value string + expected string +}{ + {"f", nil, "", "", "-f value\t"}, + {"f", nil, "Path is the `path` of file", "/path/to/file", "-f path\tPath is the path of file (default: \"/path/to/file\")"}, +} + +func TestPathFlagHelpOutput(t *testing.T) { + for _, test := range pathFlagTests { + fl := &PathFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + output := fl.String() - for _, test := range prefixStringFlagTests { - FlagNamePrefixer = test.prefixer - flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} - output := flag.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) } } } +func TestPathFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_PATH", "/path/to/file") + for _, test := range pathFlagTests { + fl := &PathFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_PATH"}} + output := fl.String() + + expectedSuffix := " [$APP_PATH]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_PATH%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +func TestPathFlagApply_SetsAllNames(t *testing.T) { + v := "mmm" + fl := PathFlag{Name: "path", Aliases: []string{"p", "PATH"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--path", "/path/to/file/path", "-p", "/path/to/file/p", "--PATH", "/path/to/file/PATH"}) + expect(t, err, nil) + expect(t, v, "/path/to/file/PATH") +} + var envHintFlagTests = []struct { name string env string hinter FlagEnvHintFunc expected string }{ - {"foo", "", func(a, b string) string { + {"foo", "", func(a []string, b string) string { return fmt.Sprintf("env: %s, str: %s", a, b) }, "env: , str: --foo value\t"}, - {"f", "", func(a, b string) string { + {"f", "", func(a []string, b string) string { return fmt.Sprintf("env: %s, str: %s", a, b) }, "env: , str: -f value\t"}, - {"foo", "ENV_VAR", func(a, b string) string { + {"foo", "ENV_VAR", func(a []string, b string) string { return fmt.Sprintf("env: %s, str: %s", a, b) }, "env: ENV_VAR, str: --foo value\t"}, - {"f", "ENV_VAR", func(a, b string) string { + {"f", "ENV_VAR", func(a []string, b string) string { return fmt.Sprintf("env: %s, str: %s", a, b) }, "env: ENV_VAR, str: -f value\t"}, } -func TestFlagEnvHinter(t *testing.T) { - defer func() { - FlagEnvHinter = withEnvHint - }() - - for _, test := range envHintFlagTests { - FlagEnvHinter = test.hinter - flag := StringFlag{Name: test.name, EnvVar: test.env} - output := flag.String() - if output != test.expected { - t.Errorf("%q does not match %q", output, test.expected) - } - } -} +//func TestFlagEnvHinter(t *testing.T) { +// defer func() { +// FlagEnvHinter = withEnvHint +// }() +// +// for _, test := range envHintFlagTests { +// FlagEnvHinter = test.hinter +// fl := StringFlag{Name: test.name, EnvVars: []string{test.env}} +// output := fl.String() +// if output != test.expected { +// t.Errorf("%q does not match %q", output, test.expected) +// } +// } +//} var stringSliceFlagTests = []struct { name string + aliases []string value *StringSlice expected string }{ - {"foo", func() *StringSlice { - s := &StringSlice{} - _ = s.Set("") - return s - }(), "--foo value\t"}, - {"f", func() *StringSlice { - s := &StringSlice{} - _ = s.Set("") - return s - }(), "-f value\t"}, - {"f", func() *StringSlice { - s := &StringSlice{} - _ = s.Set("Lipstick") - return s - }(), "-f value\t(default: \"Lipstick\")"}, - {"test", func() *StringSlice { - s := &StringSlice{} - _ = s.Set("Something") - return s - }(), "--test value\t(default: \"Something\")"}, + {"foo", nil, NewStringSlice(""), "--foo value\t"}, + {"f", nil, NewStringSlice(""), "-f value\t"}, + {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, + {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, + {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Value: test.value} - output := flag.String() + f := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := f.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -278,9 +352,10 @@ func TestStringSliceFlagHelpOutput(t *testing.T) { func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_QWWX", "11,4") + for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} - output := flag.String() + fl := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} + output := fl.String() expectedSuffix := " [$APP_QWWX]" if runtime.GOOS == "windows" { @@ -292,6 +367,15 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestStringSliceFlagApply_SetsAllNames(t *testing.T) { + fl := StringSliceFlag{Name: "goat", Aliases: []string{"G", "gooots"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--goat", "aaa", "-G", "bbb", "--gooots", "eeeee"}) + expect(t, err, nil) +} + var intFlagTests = []struct { name string expected string @@ -302,8 +386,8 @@ var intFlagTests = []struct { func TestIntFlagHelpOutput(t *testing.T) { for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, Value: 9} - output := flag.String() + fl := &IntFlag{Name: test.name, Value: 9} + output := fl.String() if output != test.expected { t.Errorf("%s does not match %s", output, test.expected) @@ -314,9 +398,10 @@ func TestIntFlagHelpOutput(t *testing.T) { func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_BAR", "2") + for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} - output := flag.String() + fl := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -328,6 +413,17 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestIntFlagApply_SetsAllNames(t *testing.T) { + v := 3 + fl := IntFlag{Name: "banana", Aliases: []string{"B", "banannanana"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--banana", "1", "-B", "2", "--banannanana", "5"}) + expect(t, err, nil) + expect(t, v, 5) +} + var int64FlagTests = []struct { name string expected string @@ -338,8 +434,8 @@ var int64FlagTests = []struct { func TestInt64FlagHelpOutput(t *testing.T) { for _, test := range int64FlagTests { - flag := Int64Flag{Name: test.name, Value: 8589934592} - output := flag.String() + fl := Int64Flag{Name: test.name, Value: 8589934592} + output := fl.String() if output != test.expected { t.Errorf("%s does not match %s", output, test.expected) @@ -350,9 +446,10 @@ func TestInt64FlagHelpOutput(t *testing.T) { func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_BAR", "2") + for _, test := range int64FlagTests { - flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} - output := flag.String() + fl := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -374,8 +471,8 @@ var uintFlagTests = []struct { func TestUintFlagHelpOutput(t *testing.T) { for _, test := range uintFlagTests { - flag := UintFlag{Name: test.name, Value: 41} - output := flag.String() + fl := UintFlag{Name: test.name, Value: 41} + output := fl.String() if output != test.expected { t.Errorf("%s does not match %s", output, test.expected) @@ -386,9 +483,10 @@ func TestUintFlagHelpOutput(t *testing.T) { func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_BAR", "2") + for _, test := range uintFlagTests { - flag := UintFlag{Name: test.name, EnvVar: "APP_BAR"} - output := flag.String() + fl := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -410,8 +508,8 @@ var uint64FlagTests = []struct { func TestUint64FlagHelpOutput(t *testing.T) { for _, test := range uint64FlagTests { - flag := Uint64Flag{Name: test.name, Value: 8589934582} - output := flag.String() + fl := Uint64Flag{Name: test.name, Value: 8589934582} + output := fl.String() if output != test.expected { t.Errorf("%s does not match %s", output, test.expected) @@ -422,9 +520,10 @@ func TestUint64FlagHelpOutput(t *testing.T) { func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_BAR", "2") + for _, test := range uint64FlagTests { - flag := UintFlag{Name: test.name, EnvVar: "APP_BAR"} - output := flag.String() + fl := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -446,8 +545,8 @@ var durationFlagTests = []struct { func TestDurationFlagHelpOutput(t *testing.T) { for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, Value: 1 * time.Second} - output := flag.String() + fl := &DurationFlag{Name: test.name, Value: 1 * time.Second} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -458,9 +557,10 @@ func TestDurationFlagHelpOutput(t *testing.T) { func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_BAR", "2h3m6s") + for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} - output := flag.String() + fl := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -472,25 +572,32 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestDurationFlagApply_SetsAllNames(t *testing.T) { + v := time.Second * 20 + fl := DurationFlag{Name: "howmuch", Aliases: []string{"H", "whyyy"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--howmuch", "30s", "-H", "5m", "--whyyy", "30h"}) + expect(t, err, nil) + expect(t, v, time.Hour*30) +} + var intSliceFlagTests = []struct { name string + aliases []string value *IntSlice expected string }{ - {"heads", &IntSlice{}, "--heads value\t"}, - {"H", &IntSlice{}, "-H value\t"}, - {"H, heads", func() *IntSlice { - i := &IntSlice{} - _ = i.Set("9") - _ = i.Set("3") - return i - }(), "-H value, --heads value\t(default: 9, 3)"}, + {"heads", nil, NewIntSlice(), "--heads value\t"}, + {"H", nil, NewIntSlice(), "-H value\t"}, + {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Value: test.value} - output := flag.String() + fl := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -501,9 +608,10 @@ func TestIntSliceFlagHelpOutput(t *testing.T) { func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_SMURF", "42,3") + for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} - output := flag.String() + fl := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() expectedSuffix := " [$APP_SMURF]" if runtime.GOOS == "windows" { @@ -515,25 +623,31 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { + fl := IntSliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + var int64SliceFlagTests = []struct { name string + aliases []string value *Int64Slice expected string }{ - {"heads", &Int64Slice{}, "--heads value\t"}, - {"H", &Int64Slice{}, "-H value\t"}, - {"H, heads", func() *Int64Slice { - i := &Int64Slice{} - _ = i.Set("2") - _ = i.Set("17179869184") - return i - }(), "-H value, --heads value\t(default: 2, 17179869184)"}, + {"heads", nil, NewInt64Slice(), "--heads value\t"}, + {"H", nil, NewInt64Slice(), "-H value\t"}, + {"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)), + "--heads value, -H value\t(default: 2, 17179869184)"}, } func TestInt64SliceFlagHelpOutput(t *testing.T) { for _, test := range int64SliceFlagTests { - flag := Int64SliceFlag{Name: test.name, Value: test.value} - output := flag.String() + fl := Int64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -544,9 +658,10 @@ func TestInt64SliceFlagHelpOutput(t *testing.T) { func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_SMURF", "42,17179869184") + for _, test := range int64SliceFlagTests { - flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} - output := flag.String() + fl := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() expectedSuffix := " [$APP_SMURF]" if runtime.GOOS == "windows" { @@ -568,8 +683,8 @@ var float64FlagTests = []struct { func TestFloat64FlagHelpOutput(t *testing.T) { for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, Value: 0.1} - output := flag.String() + f := &Float64Flag{Name: test.name, Value: 0.1} + output := f.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -580,9 +695,10 @@ func TestFloat64FlagHelpOutput(t *testing.T) { func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_BAZ", "99.4") + for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} - output := flag.String() + fl := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} + output := fl.String() expectedSuffix := " [$APP_BAZ]" if runtime.GOOS == "windows" { @@ -594,6 +710,57 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestFloat64FlagApply_SetsAllNames(t *testing.T) { + v := 99.1 + fl := Float64Flag{Name: "noodles", Aliases: []string{"N", "nurbles"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--noodles", "1.3", "-N", "11", "--nurbles", "43.33333"}) + expect(t, err, nil) + expect(t, v, float64(43.33333)) +} + +var float64SliceFlagTests = []struct { + name string + aliases []string + value *Float64Slice + expected string +}{ + {"heads", nil, NewFloat64Slice(), "--heads value\t"}, + {"H", nil, NewFloat64Slice(), "-H value\t"}, + {"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5), + "--heads value, -H value\t(default: 0.1234, -10.5)"}, +} + +func TestFloat64SliceFlagHelpOutput(t *testing.T) { + for _, test := range float64SliceFlagTests { + fl := Float64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_SMURF", "0.1234,-10.5") + for _, test := range float64SliceFlagTests { + fl := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + var genericFlagTests = []struct { name string value Generic @@ -605,8 +772,8 @@ var genericFlagTests = []struct { func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} - output := flag.String() + fl := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -617,9 +784,10 @@ func TestGenericFlagHelpOutput(t *testing.T) { func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_ZAP", "3") + for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} - output := flag.String() + fl := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} + output := fl.String() expectedSuffix := " [$APP_ZAP]" if runtime.GOOS == "windows" { @@ -631,10 +799,19 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestGenericFlagApply_SetsAllNames(t *testing.T) { + fl := GenericFlag{Name: "orbs", Aliases: []string{"O", "obrs"}, Value: &Parser{}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--orbs", "eleventy,3", "-O", "4,bloop", "--obrs", "19,s"}) + expect(t, err, nil) +} + func TestParseMultiString(t *testing.T) { _ = (&App{ Flags: []Flag{ - StringFlag{Name: "serve, s"}, + &StringFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.String("serve") != "10" { @@ -652,7 +829,7 @@ func TestParseDestinationString(t *testing.T) { var dest string _ = (&App{ Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "dest", Destination: &dest, }, @@ -671,7 +848,7 @@ func TestParseMultiStringFromEnv(t *testing.T) { _ = os.Setenv("APP_COUNT", "20") _ = (&App{ Flags: []Flag{ - StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, + &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -690,7 +867,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { _ = os.Setenv("APP_COUNT", "20") _ = (&App{ Flags: []Flag{ - StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, + &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -707,27 +884,83 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) { _ = (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: &StringSlice{}}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, }, Action: func(ctx *Context) error { - if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { - t.Errorf("main name not set") + expected := []string{"10", "20"} + if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) { + t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) } - if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { - t.Errorf("short name not set") + if !reflect.DeepEqual(ctx.StringSlice("s"), expected) { + t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) } return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } +func TestParseMultiStringSliceWithDefaults(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, + }, + Action: func(ctx *Context) error { + expected := []string{"10", "20"} + if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) { + t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) + } + if !reflect.DeepEqual(ctx.StringSlice("s"), expected) { + t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { + t.Errorf("main name not set: %v", ctx.StringSlice("serve")) + } + if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"9", "2"}) { + t.Errorf("short name not set: %v", ctx.StringSlice("s")) + } + return nil + }, + }).Run([]string{"run"}) +} + func TestParseMultiStringSliceFromEnv(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") _ = (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") + + _ = (&App{ + Flags: []Flag{ + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -747,7 +980,27 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { _ = (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") + + _ = (&App{ + Flags: []Flag{ + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -764,7 +1017,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { func TestParseMultiInt(t *testing.T) { _ = (&App{ Flags: []Flag{ - IntFlag{Name: "serve, s"}, + &IntFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Int("serve") != 10 { @@ -782,7 +1035,7 @@ func TestParseDestinationInt(t *testing.T) { var dest int _ = (&App{ Flags: []Flag{ - IntFlag{ + &IntFlag{ Name: "dest", Destination: &dest, }, @@ -801,7 +1054,7 @@ func TestParseMultiIntFromEnv(t *testing.T) { _ = os.Setenv("APP_TIMEOUT_SECONDS", "10") _ = (&App{ Flags: []Flag{ - IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -820,7 +1073,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { _ = os.Setenv("APP_TIMEOUT_SECONDS", "10") _ = (&App{ Flags: []Flag{ - IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -837,7 +1090,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { func TestParseMultiIntSlice(t *testing.T) { _ = (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: &IntSlice{}}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -851,13 +1104,67 @@ func TestParseMultiIntSlice(t *testing.T) { }).Run([]string{"run", "-s", "10", "-s", "20"}) } +func TestParseMultiIntSliceWithDefaults(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.IntSlice("s"), []int{9, 2}) { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run"}) +} + func TestParseMultiIntSliceFromEnv(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") _ = (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"}, + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") + + _ = (&App{ + Flags: []Flag{ + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(1, 2, 5), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -877,7 +1184,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { _ = (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -894,7 +1201,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { func TestParseMultiInt64Slice(t *testing.T) { _ = (&App{ Flags: []Flag{ - Int64SliceFlag{Name: "serve, s", Value: &Int64Slice{}}, + &Int64SliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewInt64Slice()}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Int64Slice("serve"), []int64{10, 17179869184}) { @@ -914,7 +1221,7 @@ func TestParseMultiInt64SliceFromEnv(t *testing.T) { _ = (&App{ Flags: []Flag{ - Int64SliceFlag{Name: "intervals, i", Value: &Int64Slice{}, EnvVar: "APP_INTERVALS"}, + &Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewInt64Slice(), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { @@ -934,7 +1241,7 @@ func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { _ = (&App{ Flags: []Flag{ - Int64SliceFlag{Name: "intervals, i", Value: &Int64Slice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + &Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewInt64Slice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { @@ -951,7 +1258,7 @@ func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { func TestParseMultiFloat64(t *testing.T) { _ = (&App{ Flags: []Flag{ - Float64Flag{Name: "serve, s"}, + &Float64Flag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Float64("serve") != 10.2 { @@ -969,7 +1276,7 @@ func TestParseDestinationFloat64(t *testing.T) { var dest float64 _ = (&App{ Flags: []Flag{ - Float64Flag{ + &Float64Flag{ Name: "dest", Destination: &dest, }, @@ -988,7 +1295,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") _ = (&App{ Flags: []Flag{ - Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { @@ -1007,7 +1314,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") _ = (&App{ Flags: []Flag{ - Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { @@ -1021,10 +1328,50 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiFloat64SliceFromEnv(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "0.1,-10.5") + + _ = (&App{ + Flags: []Flag{ + &Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewFloat64Slice(), EnvVars: []string{"APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Float64Slice("intervals"), []float64{0.1, -10.5}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Float64Slice("i"), []float64{0.1, -10.5}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "0.1234,-10.5") + + _ = (&App{ + Flags: []Flag{ + &Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewFloat64Slice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Float64Slice("intervals"), []float64{0.1234, -10.5}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Float64Slice("i"), []float64{0.1234, -10.5}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + func TestParseMultiBool(t *testing.T) { _ = (&App{ Flags: []Flag{ - BoolFlag{Name: "serve, s"}, + &BoolFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { @@ -1040,7 +1387,7 @@ func TestParseMultiBool(t *testing.T) { func TestParseBoolShortOptionHandle(t *testing.T) { _ = (&App{ - Commands: []Command{ + Commands: []*Command{ { Name: "foobar", UseShortOptionHandling: true, @@ -1054,8 +1401,8 @@ func TestParseBoolShortOptionHandle(t *testing.T) { return nil }, Flags: []Flag{ - BoolFlag{Name: "serve, s"}, - BoolFlag{Name: "option, o"}, + &BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &BoolFlag{Name: "option", Aliases: []string{"o"}}, }, }, }, @@ -1066,7 +1413,7 @@ func TestParseDestinationBool(t *testing.T) { var dest bool _ = (&App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Destination: &dest, }, @@ -1085,7 +1432,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) { _ = os.Setenv("APP_DEBUG", "1") _ = (&App{ Flags: []Flag{ - BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -1101,10 +1448,10 @@ func TestParseMultiBoolFromEnv(t *testing.T) { func TestParseMultiBoolFromEnvCascade(t *testing.T) { os.Clearenv() - os.Setenv("APP_DEBUG", "1") + _ = os.Setenv("APP_DEBUG", "1") _ = (&App{ Flags: []Flag{ - BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -1118,8 +1465,8 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { }).Run([]string{"run"}) } -func TestParseBoolTFromEnv(t *testing.T) { - var boolTFlagTests = []struct { +func TestParseBoolFromEnv(t *testing.T) { + var boolFlagTests = []struct { input string output bool }{ @@ -1129,12 +1476,12 @@ func TestParseBoolTFromEnv(t *testing.T) { {"true", true}, } - for _, test := range boolTFlagTests { + for _, test := range boolFlagTests { os.Clearenv() _ = os.Setenv("DEBUG", test.input) _ = (&App{ Flags: []Flag{ - BoolTFlag{Name: "debug, d", EnvVar: "DEBUG"}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != test.output { @@ -1152,74 +1499,18 @@ func TestParseBoolTFromEnv(t *testing.T) { func TestParseMultiBoolT(t *testing.T) { _ = (&App{ Flags: []Flag{ - BoolTFlag{Name: "serve, s"}, + &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, }, Action: func(ctx *Context) error { - if ctx.BoolT("serve") != true { + if ctx.Bool("implode") { t.Errorf("main name not set") } - if ctx.BoolT("s") != true { + if ctx.Bool("i") { t.Errorf("short name not set") } return nil }, - }).Run([]string{"run", "--serve"}) -} - -func TestParseDestinationBoolT(t *testing.T) { - var dest bool - _ = (&App{ - Flags: []Flag{ - BoolTFlag{ - Name: "dest", - Destination: &dest, - }, - }, - Action: func(ctx *Context) error { - if dest != true { - t.Errorf("expected destination BoolT true") - } - return nil - }, - }).Run([]string{"run", "--dest"}) -} - -func TestParseMultiBoolTFromEnv(t *testing.T) { - os.Clearenv() - _ = os.Setenv("APP_DEBUG", "0") - _ = (&App{ - Flags: []Flag{ - BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, - }, - Action: func(ctx *Context) error { - 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 nil - }, - }).Run([]string{"run"}) -} - -func TestParseMultiBoolTFromEnvCascade(t *testing.T) { - os.Clearenv() - _ = os.Setenv("APP_DEBUG", "0") - _ = (&App{ - Flags: []Flag{ - BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, - }, - Action: func(ctx *Context) error { - 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 nil - }, - }).Run([]string{"run"}) + }).Run([]string{"run", "--implode=false"}) } type Parser [2]string @@ -1247,7 +1538,7 @@ func (p *Parser) Get() interface{} { func TestParseGeneric(t *testing.T) { _ = (&App{ Flags: []Flag{ - GenericFlag{Name: "serve, s", Value: &Parser{}}, + &GenericFlag{Name: "serve", Aliases: []string{"s"}, Value: &Parser{}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { @@ -1266,7 +1557,12 @@ func TestParseGenericFromEnv(t *testing.T) { _ = os.Setenv("APP_SERVE", "20,30") _ = (&App{ Flags: []Flag{ - GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, + &GenericFlag{ + Name: "serve", + Aliases: []string{"s"}, + Value: &Parser{}, + EnvVars: []string{"APP_SERVE"}, + }, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { @@ -1285,7 +1581,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) { _ = os.Setenv("APP_FOO", "99,2000") _ = (&App{ Flags: []Flag{ - GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, + &GenericFlag{ + Name: "foos", + Value: &Parser{}, + EnvVars: []string{"COMPAT_FOO", "APP_FOO"}, + }, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { @@ -1313,20 +1613,68 @@ func TestFlagFromFile(t *testing.T) { var filePathTests = []struct { path string - name string + name []string expected string }{ - {"file-does-not-exist", "APP_BAR", ""}, - {"file-does-not-exist", "APP_FOO", "123"}, - {"file-does-not-exist", "APP_FOO,APP_BAR", "123"}, - {temp.Name(), "APP_FOO", "123"}, - {temp.Name(), "APP_BAR", "abc"}, + {"file-does-not-exist", []string{"APP_BAR"}, ""}, + {"file-does-not-exist", []string{"APP_FOO"}, "123"}, + {"file-does-not-exist", []string{"APP_FOO", "APP_BAR"}, "123"}, + {temp.Name(), []string{"APP_FOO"}, "123"}, + {temp.Name(), []string{"APP_BAR"}, "abc"}, } for _, filePathTest := range filePathTests { - got, _ := flagFromFileEnv(filePathTest.path, filePathTest.name) + got, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path) if want := filePathTest.expected; got != want { t.Errorf("Did not expect %v - Want %v", got, want) } } } + +func TestStringSlice_Serialized_Set(t *testing.T) { + sl0 := NewStringSlice("a", "b") + ser0 := sl0.Serialize() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewStringSlice("c", "d") + _ = sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} + +func TestIntSlice_Serialized_Set(t *testing.T) { + sl0 := NewIntSlice(1, 2) + ser0 := sl0.Serialize() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewIntSlice(3, 4) + _ = sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} + +func TestInt64Slice_Serialized_Set(t *testing.T) { + sl0 := NewInt64Slice(int64(1), int64(2)) + ser0 := sl0.Serialize() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewInt64Slice(int64(3), int64(4)) + _ = sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} diff --git a/flag_uint.go b/flag_uint.go index d6a04f4087..9f592388fe 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -9,85 +9,84 @@ import ( // UintFlag is a flag with type uint type UintFlag struct { Name string + Aliases []string Usage string - EnvVar string + EnvVars []string FilePath string Required bool Hidden bool Value uint + DefaultText string Destination *uint + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *UintFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f UintFlag) String() string { +func (f *UintFlag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f UintFlag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *UintFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f UintFlag) IsRequired() bool { +func (f *UintFlag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f UintFlag) TakesValue() bool { +func (f *UintFlag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f UintFlag) GetUsage() string { +func (f *UintFlag) GetUsage() string { return f.Usage } // 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 envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) +func (f *UintFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseUint(val, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %q as uint value for flag %s: %s", val, f.Name, err) + } + + f.Value = uint(valInt) + f.HasBeenSet = true } - - f.Value = uint(envValInt) } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.UintVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Uint(name, f.Value, f.Usage) - }) + } return nil } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (f UintFlag) GetValue() string { +func (f *UintFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { - return lookupUint(name, c.flagSet) -} - -// GlobalUint looks up the value of a global UintFlag, returns -// 0 if not found -func (c *Context) GlobalUint(name string) uint { - if fs := lookupGlobalFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { return lookupUint(name, fs) } return 0 diff --git a/flag_uint64.go b/flag_uint64.go index ea6493a8be..5bbd1fa4b9 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -9,85 +9,84 @@ import ( // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string + Aliases []string Usage string - EnvVar string + EnvVars []string FilePath string Required bool Hidden bool Value uint64 + DefaultText string Destination *uint64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Uint64Flag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (f Uint64Flag) String() string { +func (f *Uint64Flag) String() string { return FlagStringer(f) } -// GetName returns the name of the flag -func (f Uint64Flag) GetName() string { - return f.Name +// Names returns the names of the flag +func (f *Uint64Flag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (f Uint64Flag) IsRequired() bool { +func (f *Uint64Flag) IsRequired() bool { return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (f Uint64Flag) TakesValue() bool { +func (f *Uint64Flag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (f Uint64Flag) GetUsage() string { +func (f *Uint64Flag) GetUsage() string { return f.Usage } -// GetValue returns the flags value as string representation and an empty -// string if the flag takes no value at all. -func (f Uint64Flag) GetValue() string { - return fmt.Sprintf("%d", f.Value) -} - // 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 envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) +func (f *Uint64Flag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseUint(val, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", val, f.Name, err) + } + + f.Value = valInt + f.HasBeenSet = true } - - f.Value = envValInt } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.Uint64Var(f.Destination, name, f.Value, f.Usage) - return + continue } set.Uint64(name, f.Value, f.Usage) - }) + } return nil } -// Uint64 looks up the value of a local Uint64Flag, returns -// 0 if not found -func (c *Context) Uint64(name string) uint64 { - return lookupUint64(name, c.flagSet) +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Uint64Flag) GetValue() string { + return fmt.Sprintf("%d", f.Value) } -// GlobalUint64 looks up the value of a global Uint64Flag, returns +// Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found -func (c *Context) GlobalUint64(name string) uint64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { +func (c *Context) Uint64(name string) uint64 { + if fs := lookupFlagSet(name, c); fs != nil { return lookupUint64(name, fs) } return 0 diff --git a/funcs.go b/funcs.go index 0036b1130a..474c48faf9 100644 --- a/funcs.go +++ b/funcs.go @@ -1,6 +1,6 @@ package cli -// BashCompleteFunc is an action to execute when the bash-completion flag is set +// BashCompleteFunc is an action to execute when the shell completion flag is set type BashCompleteFunc func(*Context) // BeforeFunc is an action to execute before any subcommands are run, but after @@ -23,7 +23,7 @@ type CommandNotFoundFunc func(*Context, string) // is displayed and the execution is interrupted. type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error -// ExitErrHandlerFunc is executed if provided in order to handle ExitError values +// ExitErrHandlerFunc is executed if provided in order to handle exitError values // returned by Actions and Before/After functions. type ExitErrHandlerFunc func(context *Context, err error) @@ -33,11 +33,11 @@ type FlagStringFunc func(Flag) string // FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix // text for a flag's full name. -type FlagNamePrefixFunc func(fullName, placeholder string) string +type FlagNamePrefixFunc func(fullName []string, placeholder string) string // FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help // with the environment variable details. -type FlagEnvHintFunc func(envVar, str string) string +type FlagEnvHintFunc func(envVars []string, str string) string // FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help // with the file path details. diff --git a/go.mod b/go.mod index 7d04d20167..c38d41c14b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/urfave/cli +module github.com/urfave/cli/v2 go 1.11 diff --git a/help.go b/help.go index 2280e338ef..aa5947d6e9 100644 --- a/help.go +++ b/help.go @@ -10,7 +10,7 @@ import ( "unicode/utf8" ) -var helpCommand = Command{ +var helpCommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -26,7 +26,7 @@ var helpCommand = Command{ }, } -var helpSubcommand = Command{ +var helpSubcommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -97,7 +97,7 @@ func DefaultAppComplete(c *Context) { DefaultCompleteWithFlags(nil)(c) } -func printCommandSuggestions(commands []Command, writer io.Writer) { +func printCommandSuggestions(commands []*Command, writer io.Writer) { for _, command := range commands { if command.Hidden { continue @@ -135,10 +135,10 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { cur := strings.TrimPrefix(lastArg, "-") cur = strings.TrimPrefix(cur, "-") for _, flag := range flags { - if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden { + if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden { continue } - for _, name := range strings.Split(flag.GetName(), ",") { + for _, name := range flag.Names(){ name = strings.TrimSpace(name) // this will get total count utf8 letters in flag name count := utf8.RuneCountInString(name) @@ -151,7 +151,7 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { continue } // match if last argument matches this flag and it is not repeated - if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) { + if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) { flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) _, _ = fmt.Fprintln(writer, flagCompletion) } @@ -207,7 +207,7 @@ func ShowCommandHelp(ctx *Context, command string) error { } if ctx.App.CommandNotFound == nil { - return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3) + return Exit(fmt.Sprintf("No help topic for '%v'", command), 3) } ctx.App.CommandNotFound(ctx, command) @@ -216,7 +216,15 @@ func ShowCommandHelp(ctx *Context, command string) error { // ShowSubcommandHelp prints help for the given subcommand func ShowSubcommandHelp(c *Context) error { - return ShowCommandHelp(c, c.Command.Name) + if c == nil { + return nil + } + + if c.Command != nil { + return ShowCommandHelp(c, c.Command.Name) + } + + return ShowCommandHelp(c, "") } // ShowVersion prints the version number of the App @@ -263,6 +271,7 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + err := t.Execute(w, data) if err != nil { // If the writer is closed, t.Execute will fail, and there's nothing @@ -281,24 +290,20 @@ func printHelp(out io.Writer, templ string, data interface{}) { func checkVersion(c *Context) bool { found := false - if VersionFlag.GetName() != "" { - eachName(VersionFlag.GetName(), func(name string) { - if c.GlobalBool(name) || c.Bool(name) { - found = true - } - }) + for _, name := range VersionFlag.Names() { + if c.Bool(name) { + found = true + } } return found } func checkHelp(c *Context) bool { found := false - if HelpFlag.GetName() != "" { - eachName(HelpFlag.GetName(), func(name string) { - if c.GlobalBool(name) || c.Bool(name) { - found = true - } - }) + for _, name := range HelpFlag.Names() { + if c.Bool(name) { + found = true + } } return found } @@ -329,7 +334,7 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { pos := len(arguments) - 1 lastArg := arguments[pos] - if lastArg != "--"+BashCompletionFlag.GetName() { + if lastArg != "--generate-bash-completion" { return false, arguments } diff --git a/help_test.go b/help_test.go index e903494fbb..9f182e89ab 100644 --- a/help_test.go +++ b/help_test.go @@ -12,8 +12,7 @@ import ( func Test_ShowAppHelp_NoAuthor(t *testing.T) { output := new(bytes.Buffer) - app := NewApp() - app.Writer = output + app := &App{Writer: output} c := NewContext(app, nil, nil) @@ -26,8 +25,7 @@ func Test_ShowAppHelp_NoAuthor(t *testing.T) { func Test_ShowAppHelp_NoVersion(t *testing.T) { output := new(bytes.Buffer) - app := NewApp() - app.Writer = output + app := &App{Writer: output} app.Version = "" @@ -42,8 +40,7 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) { func Test_ShowAppHelp_HideVersion(t *testing.T) { output := new(bytes.Buffer) - app := NewApp() - app.Writer = output + app := &App{Writer: output} app.HideVersion = true @@ -62,14 +59,15 @@ func Test_Help_Custom_Flags(t *testing.T) { HelpFlag = oldFlag }() - HelpFlag = BoolFlag{ - Name: "help, x", - Usage: "show help", + HelpFlag = &BoolFlag{ + Name: "help", + Aliases: []string{"x"}, + Usage: "show help", } app := App{ Flags: []Flag{ - BoolFlag{Name: "foo, h"}, + &BoolFlag{Name: "foo", Aliases: []string{"h"}}, }, Action: func(ctx *Context) error { if ctx.Bool("h") != true { @@ -92,14 +90,15 @@ func Test_Version_Custom_Flags(t *testing.T) { VersionFlag = oldFlag }() - VersionFlag = BoolFlag{ - Name: "version, V", - Usage: "show version", + VersionFlag = &BoolFlag{ + Name: "version", + Aliases: []string{"V"}, + Usage: "show version", } app := App{ Flags: []Flag{ - BoolFlag{Name: "foo, v"}, + &BoolFlag{Name: "foo", Aliases: []string{"v"}}, }, Action: func(ctx *Context) error { if ctx.Bool("v") != true { @@ -117,22 +116,22 @@ func Test_Version_Custom_Flags(t *testing.T) { } func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { - app := NewApp() + app := &App{} set := flag.NewFlagSet("test", 0) _ = set.Parse([]string{"foo"}) c := NewContext(app, set, nil) - err := helpCommand.Action.(func(*Context) error)(c) + err := helpCommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*ExitError) + exitErr, ok := err.(*exitError) if !ok { - t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { @@ -145,7 +144,7 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { } func Test_helpCommand_InHelpOutput(t *testing.T) { - app := NewApp() + app := &App{} output := &bytes.Buffer{} app.Writer = output _ = app.Run([]string{"test", "--help"}) @@ -162,22 +161,22 @@ func Test_helpCommand_InHelpOutput(t *testing.T) { } func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { - app := NewApp() + app := &App{} set := flag.NewFlagSet("test", 0) _ = set.Parse([]string{"foo"}) c := NewContext(app, set, nil) - err := helpSubcommand.Action.(func(*Context) error)(c) + err := helpSubcommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*ExitError) + exitErr, ok := err.(*exitError) if !ok { - t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { @@ -191,7 +190,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { func TestShowAppHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob"}, @@ -275,7 +274,7 @@ func TestShowCommandHelp_HelpPrinter(t *testing.T) { app := &App{ Name: "my-app", Writer: &buf, - Commands: []Command{ + Commands: []*Command{ { Name: "my-command", CustomHelpTemplate: tt.template, @@ -364,7 +363,7 @@ func TestShowCommandHelp_HelpPrinterCustom(t *testing.T) { app := &App{ Name: "my-app", Writer: &buf, - Commands: []Command{ + Commands: []*Command{ { Name: "my-command", CustomHelpTemplate: tt.template, @@ -387,7 +386,7 @@ func TestShowCommandHelp_HelpPrinterCustom(t *testing.T) { func TestShowCommandHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob", "bork"}, @@ -413,7 +412,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) { func TestShowSubcommandHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob", "bork"}, @@ -435,7 +434,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) { func TestShowCommandHelp_Customtemplate(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Action: func(ctx *Context) error { @@ -477,7 +476,7 @@ EXAMPLES: func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", UsageText: "this is usage text", @@ -497,10 +496,10 @@ func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", - Subcommands: []Command{ + Subcommands: []*Command{ { Name: "bobbly", UsageText: "this is usage text", @@ -521,7 +520,7 @@ func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Action: func(ctx *Context) error { @@ -691,7 +690,7 @@ func TestShowAppHelp_HelpPrinterCustom(t *testing.T) { func TestShowAppHelp_CustomAppTemplate(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Action: func(ctx *Context) error { diff --git a/helpers_test.go b/helpers_test.go index 109ea7ad91..767f404e9d 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -12,6 +12,10 @@ var ( wd, _ = os.Getwd() ) +func init() { + _ = os.Setenv("CLI_TEMPLATE_REPANIC", "1") +} + func expect(t *testing.T, a interface{}, b interface{}) { _, fn, line, _ := runtime.Caller(1) fn = strings.Replace(fn, wd+"/", "", -1) diff --git a/helpers_unix_test.go b/helpers_unix_test.go deleted file mode 100644 index ae27fc5c8a..0000000000 --- a/helpers_unix_test.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build darwin dragonfly freebsd linux netbsd openbsd solaris - -package cli - -import "os" - -func clearenv() { - os.Clearenv() -} diff --git a/helpers_windows_test.go b/helpers_windows_test.go deleted file mode 100644 index 4eb84f9b9d..0000000000 --- a/helpers_windows_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package cli - -import ( - "os" - "syscall" -) - -// os.Clearenv() doesn't actually unset variables on Windows -// See: https://github.com/golang/go/issues/17902 -func clearenv() { - for _, s := range os.Environ() { - for j := 1; j < len(s); j++ { - if s[j] == '=' { - keyp, _ := syscall.UTF16PtrFromString(s[0:j]) - syscall.SetEnvironmentVariable(keyp, nil) - break - } - } - } -} diff --git a/template.go b/template.go index c631fb97dd..78e182f5c4 100644 --- a/template.go +++ b/template.go @@ -20,7 +20,6 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} @@ -63,7 +62,6 @@ USAGE: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} @@ -74,8 +72,9 @@ OPTIONS: ` var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }} - -% {{ .App.Author }} +{{ range $Author := .App.Authors}} +% {{ $Author.Name }} +{{- end}} # NAME