From 81fcf706ea1c2bdde52d04088a9c5b0bc1a97f7a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 21 May 2016 21:29:45 -0400 Subject: [PATCH 01/25] Replace all "stringly typed" fields with slice equivalents and alter the Flag interface accordingly Closes #415 --- README.md | 34 +++-- altsrc/flag.go | 78 +++++------ altsrc/flag_test.go | 20 +-- altsrc/yaml_command_test.go | 8 +- app.go | 7 +- command.go | 3 +- context.go | 2 +- flag.go | 264 +++++++++++++++++++++--------------- flag_test.go | 132 ++++++++++-------- help.go | 8 +- help_test.go | 14 +- 11 files changed, 314 insertions(+), 256 deletions(-) diff --git a/README.md b/README.md index de73458..0a1f624 100644 --- a/README.md +++ b/README.md @@ -265,8 +265,9 @@ For example this: ```go cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", } ``` @@ -285,9 +286,10 @@ You can set alternate (or short) names for flags by providing a comma-delimited ``` go app.Flags = []cli.Flag { cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", }, } ``` @@ -296,28 +298,30 @@ That flag can then be set with `--lang spanish` or `-l spanish`. Note that givin #### Values from the Environment -You can also have the default value set from the environment via `EnvVar`. e.g. +You can also have the default value set from the environment via `EnvVars`. e.g. ``` go app.Flags = []cli.Flag { cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "APP_LANG", + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, }, } ``` -The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. +If `EnvVars` contains more than one string, the first environment variable that resolves is used as the default. ``` go app.Flags = []cli.Flag { cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, }, } ``` diff --git a/altsrc/flag.go b/altsrc/flag.go index aea142e..6c68bf5 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "strconv" - "strings" "github.com/codegangsta/cli" ) @@ -78,15 +77,15 @@ func NewGenericFlag(flag cli.GenericFlag) *GenericFlag { // 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()) + } } } } @@ -116,19 +115,19 @@ func NewStringSliceFlag(flag cli.StringSliceFlag) *StringSliceFlag { // 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 = *(cli.NewStringSlice(value...)) - eachName(f.Name, func(name string) { - underlyingFlag := f.set.Lookup(f.Name) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } - }) + } } } } @@ -157,19 +156,19 @@ func NewIntSliceFlag(flag cli.IntSliceFlag) *IntSliceFlag { // 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 = *(cli.NewIntSlice(value...)) - eachName(f.Name, func(name string) { - underlyingFlag := f.set.Lookup(f.Name) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } - }) + } } } } @@ -198,15 +197,15 @@ func NewBoolFlag(flag cli.BoolFlag) *BoolFlag { // 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)) + } } } } @@ -235,15 +234,15 @@ func NewStringFlag(flag cli.StringFlag) *StringFlag { // 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)) { + 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, value) - }) + for _, name := range f.Names() { + f.set.Set(name, value) + } } } } @@ -273,15 +272,15 @@ func NewIntFlag(flag cli.IntFlag) *IntFlag { // 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)) + } } } } @@ -310,15 +309,15 @@ func NewDurationFlag(flag cli.DurationFlag) *DurationFlag { // 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()) + } } } } @@ -348,16 +347,16 @@ func NewFloat64Flag(flag cli.Float64Flag) *Float64Flag { // 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) + } } } } @@ -372,9 +371,8 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) { f.Float64Flag.Apply(set) } -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 envVal := os.Getenv(envVar); envVal != "" { // TODO: Can't use this for bools as // set means that it was true or false based on @@ -391,11 +389,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_test.go b/altsrc/flag_test.go index be7166f..0b188da 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -46,7 +46,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", @@ -76,7 +80,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: []string{"hello", "world"}, EnvVarName: "TEST", @@ -106,7 +110,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: []int{1, 2}, EnvVarName: "TEST", @@ -136,7 +140,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", @@ -166,7 +170,7 @@ func TestStringApplyInputSourceMethodContextSet(t *testing.T) { func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: "hello", EnvVarName: "TEST", @@ -196,7 +200,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", @@ -226,7 +230,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: time.Duration(30 * time.Second), EnvVarName: "TEST", @@ -256,7 +260,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", diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 519bd81..d1a15d7 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -68,7 +68,7 @@ func TestCommandYamlFileTestGlobalEnvVarWins(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, NewYamlSourceFromFlagFunc("load")) @@ -103,7 +103,7 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(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, NewYamlSourceFromFlagFunc("load")) @@ -268,7 +268,7 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(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, NewYamlSourceFromFlagFunc("load")) @@ -303,7 +303,7 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(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, NewYamlSourceFromFlagFunc("load")) diff --git a/app.go b/app.go index 066ea7a..622c7a1 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "sort" "time" ) @@ -121,7 +122,7 @@ func (a *App) Setup() { // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { a.appendFlag(HelpFlag) } } @@ -224,7 +225,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { a.appendFlag(HelpFlag) } } @@ -379,7 +380,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 } } diff --git a/command.go b/command.go index 7a77953..c2f39d0 100644 --- a/command.go +++ b/command.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "io/ioutil" + "reflect" "sort" "strings" ) @@ -69,7 +70,7 @@ func (c Command) Run(ctx *Context) (err error) { return c.startApp(ctx) } - if !c.HideHelp && (HelpFlag != BoolFlag{}) { + if !c.HideHelp && !reflect.DeepEqual(HelpFlag, BoolFlag{}) { // append help to flags c.Flags = append( c.Flags, diff --git a/context.go b/context.go index 45face1..3202c75 100644 --- a/context.go +++ b/context.go @@ -310,7 +310,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 } diff --git a/flag.go b/flag.go index 97e502c..ba55471 100644 --- a/flag.go +++ b/flag.go @@ -24,16 +24,18 @@ var BashCompletionFlag = BoolFlag{ // VersionFlag prints the version for the application var VersionFlag = BoolFlag{ - Name: "version, v", - Usage: "print the version", + 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) +// HelpFlag prints the help for all commands and subcommands. +// Set to the zero value (BoolFlag{}) to disable the flag. The subcommand +// will still be added unless HideHelp is set to true. var HelpFlag = BoolFlag{ - Name: "help, h", - Usage: "show help", + Name: "help", + Aliases: []string{"h"}, + Usage: "show help", } // FlagStringer converts a flag definition to a string. This is used by help @@ -52,7 +54,7 @@ type Flag interface { fmt.Stringer // Apply Flag settings to the given flag set Apply(*flag.FlagSet) - GetName() string + Names() []string } func flagSet(name string, flags []Flag) *flag.FlagSet { @@ -64,14 +66,6 @@ func flagSet(name string, flags []Flag) *flag.FlagSet { return set } -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} - // Generic is a generic parseable type identified by a specific flag type Generic interface { Set(value string) error @@ -80,11 +74,12 @@ type Generic interface { // GenericFlag is the flag type for types implementing Generic type GenericFlag struct { - Name string - Value Generic - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value Generic + Usage string + EnvVars []string + Hidden bool } // String returns the string representation of the generic flag to display the @@ -98,8 +93,8 @@ func (f GenericFlag) String() string { // provided by the user for parsing by the flag func (f GenericFlag) Apply(set *flag.FlagSet) { val := f.Value - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { val.Set(envVal) @@ -108,14 +103,14 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of a flag. -func (f GenericFlag) GetName() string { - return f.Name +// Names returns the names of a flag. +func (f GenericFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // StringSlice wraps a []string to satisfy flag.Value @@ -166,11 +161,12 @@ func (f *StringSlice) Value() []string { // StringSliceFlag is a string flag that can be specified multiple times on the // command-line type StringSliceFlag struct { - Name string - Value *StringSlice - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value *StringSlice + Usage string + EnvVars []string + Hidden bool } // String returns the usage @@ -180,8 +176,8 @@ func (f StringSliceFlag) String() string { // Apply populates the flag given the flag set and environment func (f StringSliceFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { newVal := NewStringSlice() @@ -199,14 +195,14 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { f.Value = NewStringSlice() } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of a flag. -func (f StringSliceFlag) GetName() string { - return f.Name +// Names returns the name of a flag. +func (f StringSliceFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // IntSlice wraps an []int to satisfy flag.Value @@ -272,11 +268,12 @@ func (i *IntSlice) Value() []int { // IntSliceFlag is an int flag that can be specified multiple times on the // command-line type IntSliceFlag struct { - Name string - Value *IntSlice - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value *IntSlice + Usage string + EnvVars []string + Hidden bool } // String returns the usage @@ -286,8 +283,8 @@ func (f IntSliceFlag) String() string { // Apply populates the flag given the flag set and environment func (f IntSliceFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { newVal := NewIntSlice() @@ -308,22 +305,23 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { f.Value = NewIntSlice() } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f IntSliceFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f IntSliceFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string + Aliases []string Value bool Usage string - EnvVar string + EnvVars []string Destination *bool Hidden bool } @@ -335,8 +333,8 @@ func (f BoolFlag) String() string { // Apply populates the flag given the flag set and environment func (f BoolFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValBool, err := strconv.ParseBool(envVal) @@ -348,26 +346,27 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.BoolVar(f.Destination, name, f.Value, f.Usage) return } set.Bool(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f BoolFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f BoolFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // StringFlag represents a flag that takes as string value type StringFlag struct { Name string + Aliases []string Value string Usage string - EnvVar string + EnvVars []string Destination *string Hidden bool } @@ -379,8 +378,8 @@ func (f StringFlag) String() string { // Apply populates the flag given the flag set and environment func (f StringFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { f.Value = envVal @@ -389,27 +388,28 @@ func (f StringFlag) Apply(set *flag.FlagSet) { } } - 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 } set.String(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f StringFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f StringFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // IntFlag is a flag that takes an integer // Errors if the value provided cannot be parsed type IntFlag struct { Name string + Aliases []string Value int Usage string - EnvVar string + EnvVars []string Destination *int Hidden bool } @@ -421,8 +421,8 @@ func (f IntFlag) String() string { // Apply populates the flag given the flag set and environment func (f IntFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValInt, err := strconv.ParseInt(envVal, 0, 64) @@ -434,27 +434,28 @@ func (f IntFlag) Apply(set *flag.FlagSet) { } } - 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 } set.Int(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f IntFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f IntFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // DurationFlag is a flag that takes a duration specified in Go's duration // format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { Name string + Aliases []string Value time.Duration Usage string - EnvVar string + EnvVars []string Destination *time.Duration Hidden bool } @@ -466,8 +467,8 @@ func (f DurationFlag) String() string { // Apply populates the flag given the flag set and environment func (f DurationFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValDuration, err := time.ParseDuration(envVal) @@ -479,27 +480,28 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { } } - 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 } set.Duration(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f DurationFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f DurationFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // Float64Flag is a flag that takes an float value // Errors if the value provided cannot be parsed type Float64Flag struct { Name string + Aliases []string Value float64 Usage string - EnvVar string + EnvVars []string Destination *float64 Hidden bool } @@ -511,8 +513,8 @@ func (f Float64Flag) String() string { // Apply populates the flag given the flag set and environment func (f Float64Flag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValFloat, err := strconv.ParseFloat(envVal, 10) @@ -523,18 +525,18 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { } } - 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 } set.Float64(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f Float64Flag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f Float64Flag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } func visibleFlags(fl []Flag) []Flag { @@ -574,25 +576,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 := ", $" @@ -601,21 +605,49 @@ func withEnvHint(envVar, str string) string { suffix = "%" sep = "%, %" } - envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix) } return str + envText } -func stringifyFlag(f Flag) string { +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 flagValue(f Flag) reflect.Value { fv := reflect.ValueOf(f) + for fv.Kind() == reflect.Ptr { + fv = reflect.Indirect(fv) + } + return fv +} + +func stringifyFlag(f Flag) string { + fv := flagValue(f) switch f.(type) { case IntSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyIntSliceFlag(f.(IntSliceFlag))) + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(IntSliceFlag))) case StringSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyStringSliceFlag(f.(StringSliceFlag))) + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(StringSliceFlag))) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) @@ -643,8 +675,8 @@ func stringifyFlag(f Flag) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) - return withEnvHint(fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) + return withEnvHint(flagStringSliceField(f, "EnvVars"), + fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } func stringifyIntSliceFlag(f IntSliceFlag) string { @@ -655,7 +687,7 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) } func stringifyStringSliceFlag(f StringSliceFlag) string { @@ -668,10 +700,10 @@ func stringifyStringSliceFlag(f StringSliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), 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 @@ -683,5 +715,15 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) + 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 } diff --git a/flag_test.go b/flag_test.go index a3414cf..8ff64fb 100644 --- a/flag_test.go +++ b/flag_test.go @@ -31,21 +31,22 @@ func TestBoolFlagHelpOutput(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} + flag := StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} output := flag.String() if output != test.expected { @@ -58,7 +59,7 @@ 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"} + flag := StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} output := flag.String() expectedSuffix := " [$APP_FOO]" @@ -73,19 +74,20 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { var stringSliceFlagTests = []struct { name string + aliases []string value *StringSlice expected string }{ - {"foo", NewStringSlice(""), "--foo value\t"}, - {"f", NewStringSlice(""), "-f value\t"}, - {"f", NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, - {"test", NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, - {"d, dee", NewStringSlice("Inka", "Dinka", "dooo"), "-d value, --dee value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, + {"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} + flag := StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -98,7 +100,7 @@ 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"} + flag := StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} output := flag.String() expectedSuffix := " [$APP_QWWX]" @@ -134,7 +136,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -170,7 +172,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2h3m6s") for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -185,17 +187,18 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { var intSliceFlagTests = []struct { name string + aliases []string value *IntSlice expected string }{ - {"heads", NewIntSlice(), "--heads value\t"}, - {"H", NewIntSlice(), "-H value\t"}, - {"H, heads", NewIntSlice(9, 3), "-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} + flag := IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -208,7 +211,7 @@ 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"} + flag := IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() expectedSuffix := " [$APP_SMURF]" @@ -244,7 +247,7 @@ 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"} + flag := Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} output := flag.String() expectedSuffix := " [$APP_BAZ]" @@ -281,7 +284,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_ZAP", "3") for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} + flag := GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} output := flag.String() expectedSuffix := " [$APP_ZAP]" @@ -297,7 +300,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { 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" { @@ -335,7 +338,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" { @@ -354,7 +357,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" { @@ -371,7 +374,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice()}, + StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -389,7 +392,7 @@ func TestParseMultiStringSlice(t *testing.T) { func TestParseMultiStringSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, + StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -407,7 +410,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, + StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { @@ -427,7 +430,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), 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"}) { @@ -447,7 +450,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "APP_INTERVALS"}, + 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"}) { @@ -467,7 +470,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), 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"}) { @@ -487,7 +490,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + 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"}) { @@ -504,7 +507,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { func TestParseMultiInt(t *testing.T) { a := App{ Flags: []Flag{ - IntFlag{Name: "serve, s"}, + IntFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Int("serve") != 10 { @@ -543,7 +546,7 @@ func TestParseMultiIntFromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := 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 { @@ -563,7 +566,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := 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 { @@ -581,7 +584,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { func TestParseMultiIntSlice(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice()}, + IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -598,7 +601,7 @@ func TestParseMultiIntSlice(t *testing.T) { func TestParseMultiIntSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, + IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -615,7 +618,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, + IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { @@ -635,7 +638,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), 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}) { @@ -655,7 +658,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(1, 2, 5), EnvVar: "APP_INTERVALS"}, + 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}) { @@ -675,7 +678,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), 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}) { @@ -692,7 +695,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { func TestParseMultiFloat64(t *testing.T) { a := App{ Flags: []Flag{ - Float64Flag{Name: "serve, s"}, + Float64Flag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Float64("serve") != 10.2 { @@ -731,7 +734,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := 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 { @@ -751,7 +754,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := 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 { @@ -769,7 +772,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { func TestParseMultiBool(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "serve, s"}, + BoolFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { @@ -808,7 +811,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := 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 { @@ -828,7 +831,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := 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 { @@ -846,7 +849,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { func TestParseMultiBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "implode, i", Value: true}, + BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, }, Action: func(ctx *Context) error { if ctx.Bool("implode") { @@ -888,9 +891,10 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) { a := App{ Flags: []Flag{ BoolFlag{ - Name: "debug, d", - Value: true, - EnvVar: "APP_DEBUG", + Name: "debug", + Aliases: []string{"d"}, + Value: true, + EnvVars: []string{"APP_DEBUG"}, }, }, Action: func(ctx *Context) error { @@ -912,9 +916,10 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { a := App{ Flags: []Flag{ BoolFlag{ - Name: "debug, d", - Value: true, - EnvVar: "COMPAT_DEBUG,APP_DEBUG", + Name: "debug", + Aliases: []string{"d"}, + Value: true, + EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}, }, }, Action: func(ctx *Context) error { @@ -951,7 +956,7 @@ func (p *Parser) String() string { func TestParseGeneric(t *testing.T) { a := 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"}) { @@ -971,7 +976,12 @@ func TestParseGenericFromEnv(t *testing.T) { os.Setenv("APP_SERVE", "20,30") a := 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"}) { @@ -991,7 +1001,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) { os.Setenv("APP_FOO", "99,2000") a := 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"}) { diff --git a/help.go b/help.go index 893b1be..2675289 100644 --- a/help.go +++ b/help.go @@ -208,11 +208,11 @@ func printHelp(out io.Writer, templ string, data interface{}) { func checkVersion(c *Context) bool { found := false if VersionFlag.Name != "" { - eachName(VersionFlag.Name, func(name string) { + for _, name := range VersionFlag.Names() { if c.Bool(name) { found = true } - }) + } } return found } @@ -220,11 +220,11 @@ func checkVersion(c *Context) bool { func checkHelp(c *Context) bool { found := false if HelpFlag.Name != "" { - eachName(HelpFlag.Name, func(name string) { + for _, name := range HelpFlag.Names() { if c.Bool(name) { found = true } - }) + } } return found } diff --git a/help_test.go b/help_test.go index d19bc4c..9ca4c0d 100644 --- a/help_test.go +++ b/help_test.go @@ -60,13 +60,14 @@ func Test_Help_Custom_Flags(t *testing.T) { }() HelpFlag = BoolFlag{ - Name: "help, x", - Usage: "show help", + 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 { @@ -90,13 +91,14 @@ func Test_Version_Custom_Flags(t *testing.T) { }() VersionFlag = BoolFlag{ - Name: "version, V", - Usage: "show version", + 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 { From 334e66cb8f24be3190fded4ad5952a07244fbc8b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 10:59:42 -0400 Subject: [PATCH 02/25] Remove unused (so far) func --- flag.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/flag.go b/flag.go index ba55471..067e9ee 100644 --- a/flag.go +++ b/flag.go @@ -717,13 +717,3 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { 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 -} From cd10b49473a6178b5225df751c143cf07359439e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 15:20:52 -0400 Subject: [PATCH 03/25] Minimize struct copying by using pointer func receivers and slices of struct pointers where possible. --- README.md | 28 ++-- altsrc/flag.go | 48 +++---- altsrc/flag_test.go | 48 +++---- altsrc/yaml_command_test.go | 36 ++--- app.go | 46 ++++--- app_test.go | 261 ++++++++++++++++++++---------------- category.go | 34 +++-- command.go | 48 ++++--- command_test.go | 8 +- context.go | 53 +++++--- context_test.go | 4 +- flag.go | 80 ++++++----- flag_test.go | 102 +++++++------- help.go | 14 +- help_test.go | 16 +-- 15 files changed, 453 insertions(+), 373 deletions(-) diff --git a/README.md b/README.md index 0a1f624..3fe7556 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ Setting and querying flags is simple. ``` go ... app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -232,7 +232,7 @@ You can also set a destination variable for a flag, to which the content will be ... var language string app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -264,7 +264,7 @@ indicated with back quotes. For example this: ```go -cli.StringFlag{ +&cli.StringFlag{ Name: "config", Aliases: []string{"c"}, Usage: "Load configuration from `FILE`", @@ -285,7 +285,7 @@ You can set alternate (or short) names for flags by providing a comma-delimited ``` go app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Aliases: []string{"l"}, Value: "english", @@ -302,7 +302,7 @@ You can also have the default value set from the environment via `EnvVars`. e.g ``` go app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Aliases: []string{"l"}, Value: "english", @@ -316,7 +316,7 @@ If `EnvVars` contains more than one string, the first environment variable that ``` go app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Aliases: []string{"l"}, Value: "english", @@ -333,7 +333,7 @@ There is a separate package altsrc that adds support for getting flag values fro 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"}) + 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. @@ -362,8 +362,8 @@ Here is a more complete sample of a command using YAML support: 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) @@ -375,7 +375,7 @@ Subcommands can be defined for a more git-like command line app. ```go ... -app.Commands = []cli.Command{ +app.Commands = []*cli.Command{ { Name: "add", Aliases: []string{"a"}, @@ -398,7 +398,7 @@ app.Commands = []cli.Command{ Name: "template", Aliases: []string{"r"}, Usage: "options for task templates", - Subcommands: []cli.Command{ + Subcommands: []*cli.Command{ { Name: "add", Usage: "add a new template", @@ -431,7 +431,7 @@ E.g. ```go ... - app.Commands = []cli.Command{ + app.Commands = []*cli.Command{ { Name: "noop", }, @@ -479,7 +479,7 @@ import ( func main() { app := cli.NewApp() app.Flags = []cli.Flag{ - cli.BoolFlag{ + &cli.BoolFlag{ Name: "ginger-crouton", Value: true, Usage: "is it in the soup?", @@ -508,7 +508,7 @@ the App or its subcommands. var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} app := cli.NewApp() app.EnableBashCompletion = true -app.Commands = []cli.Command{ +app.Commands = []*cli.Command{ { Name: "complete", Aliases: []string{"c"}, diff --git a/altsrc/flag.go b/altsrc/flag.go index 6c68bf5..1e6ebce 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -62,15 +62,15 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context } } -// GenericFlag is the flag type that wraps cli.GenericFlag to allow +// 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(flag cli.GenericFlag) *GenericFlag { +func NewGenericFlag(flag *cli.GenericFlag) *GenericFlag { return &GenericFlag{GenericFlag: flag, set: nil} } @@ -100,15 +100,15 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) { f.GenericFlag.Apply(set) } -// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow +// 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(flag cli.StringSliceFlag) *StringSliceFlag { +func NewStringSliceFlag(flag *cli.StringSliceFlag) *StringSliceFlag { return &StringSliceFlag{StringSliceFlag: flag, set: nil} } @@ -141,15 +141,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) { f.StringSliceFlag.Apply(set) } -// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow +// 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(flag cli.IntSliceFlag) *IntSliceFlag { +func NewIntSliceFlag(flag *cli.IntSliceFlag) *IntSliceFlag { return &IntSliceFlag{IntSliceFlag: flag, set: nil} } @@ -182,15 +182,15 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) { f.IntSliceFlag.Apply(set) } -// BoolFlag is the flag type that wraps cli.BoolFlag to allow +// 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(flag cli.BoolFlag) *BoolFlag { +func NewBoolFlag(flag *cli.BoolFlag) *BoolFlag { return &BoolFlag{BoolFlag: flag, set: nil} } @@ -219,15 +219,15 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) { f.BoolFlag.Apply(set) } -// StringFlag is the flag type that wraps cli.StringFlag to allow +// 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(flag cli.StringFlag) *StringFlag { +func NewStringFlag(flag *cli.StringFlag) *StringFlag { return &StringFlag{StringFlag: flag, set: nil} } @@ -257,15 +257,15 @@ func (f *StringFlag) Apply(set *flag.FlagSet) { f.StringFlag.Apply(set) } -// IntFlag is the flag type that wraps cli.IntFlag to allow +// 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(flag cli.IntFlag) *IntFlag { +func NewIntFlag(flag *cli.IntFlag) *IntFlag { return &IntFlag{IntFlag: flag, set: nil} } @@ -294,15 +294,15 @@ func (f *IntFlag) Apply(set *flag.FlagSet) { f.IntFlag.Apply(set) } -// DurationFlag is the flag type that wraps cli.DurationFlag to allow +// 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(flag cli.DurationFlag) *DurationFlag { +func NewDurationFlag(flag *cli.DurationFlag) *DurationFlag { return &DurationFlag{DurationFlag: flag, set: nil} } @@ -332,15 +332,15 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) { f.DurationFlag.Apply(set) } -// Float64Flag is the flag type that wraps cli.Float64Flag to allow +// 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(flag cli.Float64Flag) *Float64Flag { +func NewFloat64Flag(flag *cli.Float64Flag) *Float64Flag { return &Float64Flag{Float64Flag: flag, set: nil} } diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 0b188da..54055f3 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -26,7 +26,7 @@ type testApplyInputSource struct { 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 +36,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 +46,7 @@ func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{ + Flag: NewGenericFlag(&cli.GenericFlag{ Name: "test", Value: &Parser{}, EnvVars: []string{"TEST"}, @@ -61,7 +61,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: []string{"hello", "world"}, }) @@ -70,7 +70,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: []string{"hello", "world"}, ContextValueString: "ohno", @@ -80,7 +80,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []string{"hello", "world"}, EnvVarName: "TEST", @@ -91,7 +91,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: []int{1, 2}, }) @@ -100,7 +100,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: []int{1, 2}, ContextValueString: "3", @@ -110,7 +110,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []int{1, 2}, EnvVarName: "TEST", @@ -121,7 +121,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, }) @@ -130,7 +130,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", @@ -140,7 +140,7 @@ func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: false, EnvVarName: "TEST", @@ -151,7 +151,7 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestStringApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", MapValue: "hello", }) @@ -160,7 +160,7 @@ func TestStringApplyInputSourceMethodSet(t *testing.T) { func TestStringApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", MapValue: "hello", ContextValueString: "goodbye", @@ -170,7 +170,7 @@ func TestStringApplyInputSourceMethodContextSet(t *testing.T) { func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: "hello", EnvVarName: "TEST", @@ -181,7 +181,7 @@ func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { 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, }) @@ -190,7 +190,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", @@ -200,7 +200,7 @@ func TestIntApplyInputSourceMethodContextSet(t *testing.T) { func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 15, EnvVarName: "TEST", @@ -211,7 +211,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: time.Duration(30 * time.Second), }) @@ -220,7 +220,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: time.Duration(30 * time.Second), ContextValueString: time.Duration(15 * time.Second).String(), @@ -230,7 +230,7 @@ func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: time.Duration(30 * time.Second), EnvVarName: "TEST", @@ -241,7 +241,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, }) @@ -250,7 +250,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), @@ -260,7 +260,7 @@ func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 1.3, EnvVarName: "TEST", diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index d1a15d7..44e8351 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -35,8 +35,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) @@ -68,8 +68,8 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", EnvVars: []string{"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")) @@ -103,8 +103,8 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", EnvVars: []string{"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")) @@ -135,8 +135,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")) @@ -168,8 +168,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")) @@ -200,8 +200,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")) @@ -233,8 +233,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")) @@ -268,8 +268,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"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) @@ -303,8 +303,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *tes return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"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/app.go b/app.go index 622c7a1..68a92a2 100644 --- a/app.go +++ b/app.go @@ -27,7 +27,7 @@ type App struct { // Version of the program Version string // List of commands to execute - Commands []Command + Commands []*Command // List of flags to parse Flags []Flag // Boolean to enable bash completion commands @@ -37,7 +37,7 @@ type App struct { // 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 CommandCategories + categories *CommandCategories // An action to execute when the bash-completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -55,7 +55,7 @@ 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 // Writer writer to write output to @@ -104,7 +104,7 @@ func (a *App) Setup() { a.didSetup = true - newCmds := []Command{} + newCmds := []*Command{} for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) @@ -113,7 +113,7 @@ func (a *App) Setup() { } a.Commands = newCmds - a.categories = CommandCategories{} + a.categories = NewCommandCategories() for _, command := range a.Commands { a.categories = a.categories.AddCommand(command.Category, command) } @@ -121,8 +121,9 @@ func (a *App) Setup() { // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } @@ -224,14 +225,15 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // append help to commands if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } } - newCmds := []Command{} + newCmds := []*Command{} for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) @@ -331,7 +333,7 @@ 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 } } @@ -339,7 +341,7 @@ func (a *App) Command(name string) *Command { } // Categories returns a slice containing all the categories with the commands they contain -func (a *App) Categories() CommandCategories { +func (a *App) Categories() *CommandCategories { return a.categories } @@ -347,7 +349,7 @@ func (a *App) Categories() CommandCategories { // Hidden=false func (a *App) VisibleCategories() []*CommandCategory { ret := []*CommandCategory{} - for _, category := range a.categories { + for _, category := range a.categories.Categories { if visible := func() *CommandCategory { for _, command := range category.Commands { if !command.Hidden { @@ -363,8 +365,8 @@ func (a *App) VisibleCategories() []*CommandCategory { } // VisibleCommands returns a slice of the Commands with Hidden=false -func (a *App) VisibleCommands() []Command { - ret := []Command{} +func (a *App) VisibleCommands() []*Command { + ret := []*Command{} for _, command := range a.Commands { if !command.Hidden { ret = append(ret, command) @@ -398,9 +400,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) } } @@ -411,7 +419,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_test.go b/app_test.go index 8d81a5d..3c79766 100644 --- a/app_test.go +++ b/app_test.go @@ -24,14 +24,14 @@ func ExampleApp_Run() { app := NewApp() app.Name = "greet" app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + &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.UsageText = "app [first_arg] [second_arg]" - app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} + app.Authors = []*Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} app.Run(os.Args) // Output: // Hello Jeremy @@ -42,20 +42,20 @@ func ExampleApp_Run_subcommand() { os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} app := NewApp() app.Name = "say" - app.Commands = []Command{ + 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{ + Subcommands: []*Command{ { Name: "english", Aliases: []string{"en"}, Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "name", Value: "Bob", Usage: "Name of the person to greet", @@ -82,9 +82,9 @@ func ExampleApp_Run_help() { app := NewApp() app.Name = "greet" app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "describeit", Aliases: []string{"d"}, @@ -115,7 +115,7 @@ func ExampleApp_Run_bashComplete() { app := NewApp() app.Name = "greet" app.EnableBashCompletion = true - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "describeit", Aliases: []string{"d"}, @@ -175,9 +175,9 @@ 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 := &Command{Name: "foobar", Aliases: []string{"f"}} + batCommand := &Command{Name: "batbaz", Aliases: []string{"b"}} + app.Commands = []*Command{ fooCommand, batCommand, } @@ -191,7 +191,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context a := NewApp() - a.Commands = []Command{ + a.Commands = []*Command{ { Name: "foo", Action: func(c *Context) error { @@ -199,7 +199,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { return nil }, Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -216,13 +216,13 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string - var args []string + var args *Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - StringFlag{Name: "option", Value: "", Usage: "some option"}, + &StringFlag{Name: "option", Value: "", Usage: "some option"}, }, Action: func(c *Context) error { parsedOption = c.String("option") @@ -230,58 +230,58 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} 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 + var args *Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Action: func(c *Context) error { args = c.Args() return nil }, } - app.Commands = []Command{command} + 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 + var args *Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Action: func(c *Context) error { args = c.Args() return nil }, } - app.Commands = []Command{command} + 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{ + app.Commands = []*Command{ { Name: "frob", HelpName: "foo frob", @@ -296,7 +296,7 @@ func TestApp_VisibleCommands(t *testing.T) { } app.Setup() - expected := []Command{ + expected := []*Command{ app.Commands[0], app.Commands[2], // help } @@ -310,14 +310,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) + } + }() } } @@ -326,7 +334,7 @@ func TestApp_Float64Flag(t *testing.T) { app := NewApp() app.Flags = []Flag{ - Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, + &Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, } app.Action = func(c *Context) error { meters = c.Float64("height") @@ -343,11 +351,11 @@ func TestApp_ParseSliceFlags(t *testing.T) { var parsedStringSlice []string app := NewApp() - command := Command{ + command := &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"}, + &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") @@ -357,7 +365,7 @@ func TestApp_ParseSliceFlags(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"}) @@ -401,11 +409,11 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { var parsedStringSlice []string app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - IntSliceFlag{Name: "a", Usage: "set numbers"}, - StringSliceFlag{Name: "str", Usage: "set strings"}, + &IntSliceFlag{Name: "a", Usage: "set numbers"}, + &StringSliceFlag{Name: "str", Usage: "set strings"}, }, Action: func(c *Context) error { parsedIntSlice = c.IntSlice("a") @@ -413,7 +421,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"}) @@ -491,7 +499,7 @@ func TestApp_BeforeFunc(t *testing.T) { return nil } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "sub", Action: func(c *Context) error { @@ -503,7 +511,7 @@ func TestApp_BeforeFunc(t *testing.T) { } app.Flags = []Flag{ - StringFlag{Name: "opt"}, + &StringFlag{Name: "opt"}, } // run with the Before() func succeeding @@ -583,7 +591,7 @@ func TestApp_AfterFunc(t *testing.T) { return nil } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "sub", Action: func(c *Context) error { @@ -595,7 +603,7 @@ func TestApp_AfterFunc(t *testing.T) { } app.Flags = []Flag{ - StringFlag{Name: "opt"}, + &StringFlag{Name: "opt"}, } // run with the After() func succeeding @@ -639,7 +647,7 @@ func TestAppNoHelpFlag(t *testing.T) { HelpFlag = oldFlag }() - HelpFlag = BoolFlag{} + HelpFlag = nil app := NewApp() app.Writer = ioutil.Discard @@ -698,7 +706,7 @@ func TestApp_CommandNotFound(t *testing.T) { counts.CommandNotFound = counts.Total } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Action: func(c *Context) error { @@ -765,7 +773,7 @@ func TestApp_OrderOfOperations(t *testing.T) { } app.After = afterNoError - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Action: func(c *Context) error { @@ -871,21 +879,21 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { 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}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run(flagSet) if err != nil { @@ -916,16 +924,16 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { 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 { @@ -933,11 +941,14 @@ 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) } } @@ -946,17 +957,17 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { 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 { @@ -964,11 +975,15 @@ 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) } } @@ -977,17 +992,17 @@ func TestApp_Run_CommandHelpName(t *testing.T) { 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 { @@ -995,11 +1010,15 @@ 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) } } @@ -1008,17 +1027,17 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { 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 { @@ -1026,11 +1045,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) } } @@ -1100,7 +1123,7 @@ func TestApp_Run_Version(t *testing.T) { func TestApp_Run_Categories(t *testing.T) { app := NewApp() app.Name = "categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1119,18 +1142,20 @@ func TestApp_Run_Categories(t *testing.T) { app.Run([]string{"categories"}) - expect := CommandCategories{ - &CommandCategory{ - Name: "1", - Commands: []Command{ - app.Commands[0], - app.Commands[1], + expect := &CommandCategories{ + Categories: []*CommandCategory{ + { + Name: "1", + Commands: []*Command{ + app.Commands[0], + app.Commands[1], + }, }, - }, - &CommandCategory{ - Name: "2", - Commands: []Command{ - app.Commands[2], + { + Name: "2", + Commands: []*Command{ + app.Commands[2], + }, }, }, } @@ -1149,7 +1174,7 @@ func TestApp_Run_Categories(t *testing.T) { func TestApp_VisibleCategories(t *testing.T) { app := NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1171,13 +1196,13 @@ func TestApp_VisibleCategories(t *testing.T) { expected := []*CommandCategory{ { Name: "2", - Commands: []Command{ + Commands: []*Command{ app.Commands[1], }, }, { Name: "3", - Commands: []Command{ + Commands: []*Command{ app.Commands[2], }, }, @@ -1188,7 +1213,7 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1211,7 +1236,7 @@ func TestApp_VisibleCategories(t *testing.T) { expected = []*CommandCategory{ { Name: "3", - Commands: []Command{ + Commands: []*Command{ app.Commands[2], }, }, @@ -1222,7 +1247,7 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1270,9 +1295,9 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { - Subcommands: []Command{ + Subcommands: []*Command{ { Name: "sub", }, @@ -1299,7 +1324,7 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() app.Flags = []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, } app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { if isSubcommand { @@ -1310,7 +1335,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { } return errors.New("intercepted: " + err.Error()) } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", }, @@ -1329,7 +1354,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { app := NewApp() app.Flags = []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, } app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { if isSubcommand { @@ -1340,7 +1365,7 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { } return errors.New("intercepted: " + err.Error()) } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", }, diff --git a/category.go b/category.go index 1a60550..9fb00f7 100644 --- a/category.go +++ b/category.go @@ -1,40 +1,48 @@ package cli // CommandCategories is a slice of *CommandCategory. -type CommandCategories []*CommandCategory +type CommandCategories struct { + Categories []*CommandCategory +} + +func NewCommandCategories() *CommandCategories { + return &CommandCategories{Categories: []*CommandCategory{}} +} // CommandCategory is a category containing commands. type CommandCategory struct { Name string - Commands Commands + Commands []*Command } -func (c CommandCategories) Less(i, j int) bool { - return c[i].Name < c[j].Name +func (c *CommandCategories) Less(i, j int) bool { + return c.Categories[i].Name < c.Categories[j].Name } -func (c CommandCategories) Len() int { - return len(c) +func (c *CommandCategories) Len() int { + return len(c.Categories) } -func (c CommandCategories) Swap(i, j int) { - c[i], c[j] = c[j], c[i] +func (c *CommandCategories) Swap(i, j int) { + c.Categories[i], c.Categories[j] = c.Categories[j], c.Categories[i] } // AddCommand adds a command to a category. -func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { - for _, commandCategory := range c { +func (c *CommandCategories) AddCommand(category string, command *Command) *CommandCategories { + for _, commandCategory := range c.Categories { if commandCategory.Name == category { commandCategory.Commands = append(commandCategory.Commands, command) return c } } - return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) + c.Categories = append(c.Categories, + &CommandCategory{Name: category, Commands: []*Command{command}}) + return c } // VisibleCommands returns a slice of the Commands with Hidden=false -func (c *CommandCategory) VisibleCommands() []Command { - ret := []Command{} +func (c *CommandCategory) VisibleCommands() []*Command { + ret := []*Command{} for _, command := range c.Commands { if !command.Hidden { ret = append(ret, command) diff --git a/command.go b/command.go index c2f39d0..fe31a36 100644 --- a/command.go +++ b/command.go @@ -3,7 +3,6 @@ package cli import ( "fmt" "io/ioutil" - "reflect" "sort" "strings" ) @@ -37,7 +36,7 @@ type Command struct { // 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 @@ -54,32 +53,26 @@ type Command struct { // 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 && !reflect.DeepEqual(HelpFlag, BoolFlag{}) { + if !c.HideHelp && HelpFlag != nil { // append help to flags - c.Flags = append( - c.Flags, - HelpFlag, - ) + c.appendFlag(HelpFlag) } if ctx.App.EnableBashCompletion { - c.Flags = append(c.Flags, BashCompletionFlag) + c.appendFlag(BashCompletionFlag) } set := flagSet(c.Name, c.Flags) @@ -156,13 +149,12 @@ func (c Command) Run(ctx *Context) (err error) { } // Names returns the names including short names and aliases. -func (c Command) Names() []string { - names := []string{c.Name} - return append(names, c.Aliases...) +func (c *Command) Names() []string { + return append([]string{c.Name}, c.Aliases...) } // HasName returns true if Command.Name matches given name -func (c Command) HasName(name string) bool { +func (c *Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { return true @@ -171,7 +163,7 @@ func (c Command) HasName(name string) bool { return false } -func (c Command) startApp(ctx *Context) error { +func (c *Command) startApp(ctx *Context) error { app := NewApp() app.Metadata = ctx.App.Metadata // set the name and usage @@ -201,7 +193,7 @@ func (c Command) startApp(ctx *Context) error { app.Compiled = ctx.App.Compiled app.Writer = ctx.App.Writer - app.categories = CommandCategories{} + app.categories = NewCommandCategories() for _, command := range c.Subcommands { app.categories = app.categories.AddCommand(command.Category, command) } @@ -231,6 +223,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 a03f8bb..033149f 100644 --- a/command_test.go +++ b/command_test.go @@ -42,13 +42,13 @@ func TestCommandFlagParsing(t *testing.T) { err := command.Run(context) expect(t, err, c.expectedErr) - expect(t, []string(context.Args()), c.testArgs) + expect(t, context.Args().Slice(), c.testArgs) } } func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Before: func(c *Context) error { @@ -75,11 +75,11 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Flags: []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, }, OnUsageError: func(c *Context, err error, _ bool) error { if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { diff --git a/context.go b/context.go index 3202c75..0728da9 100644 --- a/context.go +++ b/context.go @@ -14,7 +14,7 @@ import ( // parsed command-line options. type Context struct { App *App - Command Command + Command *Command flagSet *flag.FlagSet parentContext *Context @@ -147,56 +147,69 @@ func (c *Context) Lineage() []*Context { return lineage } -// 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 +func (c *Context) Args() *Args { + return &Args{slice: c.flagSet.Args()} } // NArg returns the number of the command line arguments. func (c *Context) NArg() int { - return len(c.Args()) + return c.Args().Len() +} + +// Args wraps a string slice with some convenience methods +type Args struct { + slice []string } // Get returns the nth argument, or else a blank string -func (a Args) Get(n int) string { - if len(a) > n { - return a[n] +func (a *Args) Get(n int) string { + if len(a.slice) > n { + return a.slice[n] } return "" } // First returns the first argument, or else a blank string -func (a Args) First() string { +func (a *Args) First() string { return a.Get(0) } // 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:] +func (a *Args) Tail() []string { + if a.Len() >= 2 { + return a.slice[1:] } return []string{} } +// Len returns the length of the wrapped slice +func (a *Args) Len() int { + return len(a.slice) +} + // Present checks if there are any arguments present -func (a Args) Present() bool { - return len(a) != 0 +func (a *Args) Present() bool { + return a.Len() != 0 } // Swap swaps arguments at the given indexes -func (a Args) Swap(from, to int) error { - if from >= len(a) || to >= len(a) { +func (a *Args) Swap(from, to int) error { + if from >= a.Len() || to >= a.Len() { return errors.New("index out of range") } - a[from], a[to] = a[to], a[from] + a.slice[from], a.slice[to] = a.slice[to], a.slice[from] return nil } +// Slice returns a copy of the internal slice +func (a *Args) Slice() []string { + ret := make([]string, len(a.slice)) + copy(ret, a.slice) + return ret +} + func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { for _, c := range ctx.Lineage() { if f := c.flagSet.Lookup(name); f != nil { diff --git a/context_test.go b/context_test.go index 8147965..229275c 100644 --- a/context_test.go +++ b/context_test.go @@ -15,7 +15,7 @@ func TestNewContext(t *testing.T) { globalSet.Int("myflag", 42, "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) @@ -63,7 +63,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) } diff --git a/flag.go b/flag.go index 067e9ee..dd07d27 100644 --- a/flag.go +++ b/flag.go @@ -17,22 +17,22 @@ const defaultPlaceholder = "value" var slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) // BashCompletionFlag enables bash-completion for all commands and subcommands -var BashCompletionFlag = BoolFlag{ +var BashCompletionFlag = &BoolFlag{ Name: "generate-bash-completion", Hidden: true, } // VersionFlag prints the version for the application -var VersionFlag = BoolFlag{ +var VersionFlag = &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 the flag. The subcommand +// Set to nil to disable the flag. The subcommand // will still be added unless HideHelp is set to true. -var HelpFlag = BoolFlag{ +var HelpFlag = &BoolFlag{ Name: "help", Aliases: []string{"h"}, Usage: "show help", @@ -85,13 +85,13 @@ type GenericFlag struct { // String returns the string representation of the generic flag to display the // help text to the user (uses the String() method of the generic flag to show // the value) -func (f GenericFlag) String() string { +func (f *GenericFlag) String() string { return FlagStringer(f) } // Apply 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) Apply(set *flag.FlagSet) { +func (f *GenericFlag) Apply(set *flag.FlagSet) { val := f.Value if f.EnvVars != nil { for _, envVar := range f.EnvVars { @@ -109,7 +109,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { } // Names returns the names of a flag. -func (f GenericFlag) Names() []string { +func (f *GenericFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -170,12 +170,12 @@ type StringSliceFlag struct { } // String returns the usage -func (f StringSliceFlag) String() string { +func (f *StringSliceFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f StringSliceFlag) Apply(set *flag.FlagSet) { +func (f *StringSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -201,7 +201,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { } // Names returns the name of a flag. -func (f StringSliceFlag) Names() []string { +func (f *StringSliceFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -277,12 +277,12 @@ type IntSliceFlag struct { } // String returns the usage -func (f IntSliceFlag) String() string { +func (f *IntSliceFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f IntSliceFlag) Apply(set *flag.FlagSet) { +func (f *IntSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -311,7 +311,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f IntSliceFlag) Names() []string { +func (f *IntSliceFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -327,12 +327,12 @@ type BoolFlag struct { } // String returns a readable representation of this value (for usage defaults) -func (f BoolFlag) String() string { +func (f *BoolFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f BoolFlag) Apply(set *flag.FlagSet) { +func (f *BoolFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -356,7 +356,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f BoolFlag) Names() []string { +func (f *BoolFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -372,12 +372,12 @@ type StringFlag struct { } // String returns the usage -func (f StringFlag) String() string { +func (f *StringFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f StringFlag) Apply(set *flag.FlagSet) { +func (f *StringFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -398,7 +398,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f StringFlag) Names() []string { +func (f *StringFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -415,12 +415,12 @@ type IntFlag struct { } // String returns the usage -func (f IntFlag) String() string { +func (f *IntFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f IntFlag) Apply(set *flag.FlagSet) { +func (f *IntFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -444,7 +444,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f IntFlag) Names() []string { +func (f *IntFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -461,12 +461,12 @@ type DurationFlag struct { } // String returns a readable representation of this value (for usage defaults) -func (f DurationFlag) String() string { +func (f *DurationFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f DurationFlag) Apply(set *flag.FlagSet) { +func (f *DurationFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -490,7 +490,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f DurationFlag) Names() []string { +func (f *DurationFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -507,12 +507,12 @@ type Float64Flag struct { } // String returns the usage -func (f Float64Flag) String() string { +func (f *Float64Flag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f Float64Flag) Apply(set *flag.FlagSet) { +func (f *Float64Flag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -535,14 +535,14 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f Float64Flag) Names() []string { +func (f *Float64Flag) Names() []string { return append([]string{f.Name}, f.Aliases...) } func visibleFlags(fl []Flag) []Flag { visible := []Flag{} for _, flag := range fl { - if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { + if !flagValue(flag).FieldByName("Hidden").Bool() { visible = append(visible, flag) } } @@ -644,10 +644,10 @@ func stringifyFlag(f Flag) string { fv := flagValue(f) switch f.(type) { - case IntSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(IntSliceFlag))) - case StringSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(StringSliceFlag))) + case *IntSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(*IntSliceFlag))) + case *StringSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(*StringSliceFlag))) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) @@ -679,7 +679,7 @@ func stringifyFlag(f Flag) string { fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } -func stringifyIntSliceFlag(f IntSliceFlag) string { +func stringifyIntSliceFlag(f *IntSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { @@ -690,7 +690,7 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) } -func stringifyStringSliceFlag(f StringSliceFlag) string { +func stringifyStringSliceFlag(f *StringSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, s := range f.Value.Value() { @@ -717,3 +717,13 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { 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 +} diff --git a/flag_test.go b/flag_test.go index 8ff64fb..15166c7 100644 --- a/flag_test.go +++ b/flag_test.go @@ -20,7 +20,7 @@ var boolFlagTests = []struct { func TestBoolFlagHelpOutput(t *testing.T) { for _, test := range boolFlagTests { - flag := BoolFlag{Name: test.name} + flag := &BoolFlag{Name: test.name} output := flag.String() if output != test.expected { @@ -46,7 +46,7 @@ var stringFlagTests = []struct { func TestStringFlagHelpOutput(t *testing.T) { for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + flag := &StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} output := flag.String() if output != test.expected { @@ -59,7 +59,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_FOO", "derp") for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} + flag := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} output := flag.String() expectedSuffix := " [$APP_FOO]" @@ -87,7 +87,7 @@ var stringSliceFlagTests = []struct { func TestStringSliceFlagHelpOutput(t *testing.T) { for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -100,7 +100,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_QWWX", "11,4") for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} + flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} output := flag.String() expectedSuffix := " [$APP_QWWX]" @@ -123,7 +123,7 @@ var intFlagTests = []struct { func TestIntFlagHelpOutput(t *testing.T) { for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, Value: 9} + flag := &IntFlag{Name: test.name, Value: 9} output := flag.String() if output != test.expected { @@ -136,7 +136,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -159,7 +159,7 @@ var durationFlagTests = []struct { func TestDurationFlagHelpOutput(t *testing.T) { for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, Value: 1 * time.Second} + flag := &DurationFlag{Name: test.name, Value: 1 * time.Second} output := flag.String() if output != test.expected { @@ -172,7 +172,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2h3m6s") for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -198,7 +198,7 @@ var intSliceFlagTests = []struct { func TestIntSliceFlagHelpOutput(t *testing.T) { for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -211,7 +211,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_SMURF", "42,3") for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} + flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() expectedSuffix := " [$APP_SMURF]" @@ -234,7 +234,7 @@ var float64FlagTests = []struct { func TestFloat64FlagHelpOutput(t *testing.T) { for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, Value: float64(0.1)} + flag := &Float64Flag{Name: test.name, Value: float64(0.1)} output := flag.String() if output != test.expected { @@ -247,7 +247,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAZ", "99.4") for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} + flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} output := flag.String() expectedSuffix := " [$APP_BAZ]" @@ -271,7 +271,7 @@ var genericFlagTests = []struct { func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} + flag := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} output := flag.String() if output != test.expected { @@ -284,7 +284,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_ZAP", "3") for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} + flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} output := flag.String() expectedSuffix := " [$APP_ZAP]" @@ -300,7 +300,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { func TestParseMultiString(t *testing.T) { (&App{ Flags: []Flag{ - StringFlag{Name: "serve", Aliases: []string{"s"}}, + &StringFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.String("serve") != "10" { @@ -318,7 +318,7 @@ func TestParseDestinationString(t *testing.T) { var dest string a := App{ Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "dest", Destination: &dest, }, @@ -338,7 +338,7 @@ func TestParseMultiStringFromEnv(t *testing.T) { os.Setenv("APP_COUNT", "20") (&App{ Flags: []Flag{ - StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, + &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -357,7 +357,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { os.Setenv("APP_COUNT", "20") (&App{ Flags: []Flag{ - StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"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" { @@ -374,7 +374,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -392,7 +392,7 @@ func TestParseMultiStringSlice(t *testing.T) { func TestParseMultiStringSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -410,7 +410,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { @@ -430,7 +430,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"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"}) { @@ -450,7 +450,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"APP_INTERVALS"}}, + &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"}) { @@ -470,7 +470,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"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"}) { @@ -490,7 +490,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, + &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"}) { @@ -507,7 +507,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { func TestParseMultiInt(t *testing.T) { a := App{ Flags: []Flag{ - IntFlag{Name: "serve", Aliases: []string{"s"}}, + &IntFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Int("serve") != 10 { @@ -526,7 +526,7 @@ func TestParseDestinationInt(t *testing.T) { var dest int a := App{ Flags: []Flag{ - IntFlag{ + &IntFlag{ Name: "dest", Destination: &dest, }, @@ -546,7 +546,7 @@ func TestParseMultiIntFromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := App{ Flags: []Flag{ - IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, + &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -566,7 +566,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := App{ Flags: []Flag{ - IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"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 { @@ -584,7 +584,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { func TestParseMultiIntSlice(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -601,7 +601,7 @@ func TestParseMultiIntSlice(t *testing.T) { func TestParseMultiIntSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -618,7 +618,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { @@ -638,7 +638,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"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}) { @@ -658,7 +658,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(1, 2, 5), EnvVars: []string{"APP_INTERVALS"}}, + &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}) { @@ -678,7 +678,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"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}) { @@ -695,7 +695,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { func TestParseMultiFloat64(t *testing.T) { a := App{ Flags: []Flag{ - Float64Flag{Name: "serve", Aliases: []string{"s"}}, + &Float64Flag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Float64("serve") != 10.2 { @@ -714,7 +714,7 @@ func TestParseDestinationFloat64(t *testing.T) { var dest float64 a := App{ Flags: []Flag{ - Float64Flag{ + &Float64Flag{ Name: "dest", Destination: &dest, }, @@ -734,7 +734,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := App{ Flags: []Flag{ - Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"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 { @@ -754,7 +754,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := App{ Flags: []Flag{ - Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"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 { @@ -772,7 +772,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { func TestParseMultiBool(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &BoolFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { @@ -791,7 +791,7 @@ func TestParseDestinationBool(t *testing.T) { var dest bool a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Destination: &dest, }, @@ -811,7 +811,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := App{ Flags: []Flag{ - BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -831,7 +831,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := App{ Flags: []Flag{ - BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"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 { @@ -849,7 +849,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { func TestParseMultiBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, + &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, }, Action: func(ctx *Context) error { if ctx.Bool("implode") { @@ -869,7 +869,7 @@ func TestParseDestinationBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Value: true, Destination: &dest, @@ -890,7 +890,7 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "0") a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "debug", Aliases: []string{"d"}, Value: true, @@ -915,7 +915,7 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "0") a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "debug", Aliases: []string{"d"}, Value: true, @@ -956,7 +956,7 @@ func (p *Parser) String() string { func TestParseGeneric(t *testing.T) { a := App{ Flags: []Flag{ - GenericFlag{Name: "serve", Aliases: []string{"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"}) { @@ -976,7 +976,7 @@ func TestParseGenericFromEnv(t *testing.T) { os.Setenv("APP_SERVE", "20,30") a := App{ Flags: []Flag{ - GenericFlag{ + &GenericFlag{ Name: "serve", Aliases: []string{"s"}, Value: &Parser{}, @@ -1001,7 +1001,7 @@ func TestParseGenericFromEnvCascade(t *testing.T) { os.Setenv("APP_FOO", "99,2000") a := App{ Flags: []Flag{ - GenericFlag{ + &GenericFlag{ Name: "foos", Value: &Parser{}, EnvVars: []string{"COMPAT_FOO", "APP_FOO"}, diff --git a/help.go b/help.go index 2675289..d02f7fd 100644 --- a/help.go +++ b/help.go @@ -74,7 +74,7 @@ OPTIONS: {{end}}{{end}} ` -var helpCommand = Command{ +var helpCommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -90,7 +90,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", @@ -158,7 +158,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 diff --git a/help_test.go b/help_test.go index 9ca4c0d..4d1dedc 100644 --- a/help_test.go +++ b/help_test.go @@ -59,7 +59,7 @@ func Test_Help_Custom_Flags(t *testing.T) { HelpFlag = oldFlag }() - HelpFlag = BoolFlag{ + HelpFlag = &BoolFlag{ Name: "help", Aliases: []string{"x"}, Usage: "show help", @@ -67,7 +67,7 @@ func Test_Help_Custom_Flags(t *testing.T) { app := App{ Flags: []Flag{ - BoolFlag{Name: "foo", Aliases: []string{"h"}}, + &BoolFlag{Name: "foo", Aliases: []string{"h"}}, }, Action: func(ctx *Context) error { if ctx.Bool("h") != true { @@ -90,7 +90,7 @@ func Test_Version_Custom_Flags(t *testing.T) { VersionFlag = oldFlag }() - VersionFlag = BoolFlag{ + VersionFlag = &BoolFlag{ Name: "version", Aliases: []string{"V"}, Usage: "show version", @@ -98,7 +98,7 @@ func Test_Version_Custom_Flags(t *testing.T) { app := App{ Flags: []Flag{ - BoolFlag{Name: "foo", Aliases: []string{"v"}}, + &BoolFlag{Name: "foo", Aliases: []string{"v"}}, }, Action: func(ctx *Context) error { if ctx.Bool("v") != true { @@ -173,7 +173,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"}, @@ -195,7 +195,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) { func TestShowCommandHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob", "bork"}, @@ -221,7 +221,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"}, @@ -243,7 +243,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Action: func(ctx *Context) error { From 024b4c6240a085f57e3c7e7378106a3252dc0a77 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 22 May 2016 14:42:23 -0700 Subject: [PATCH 04/25] Update references to codegangsta to urfave (new org name) Also add notice to README --- CHANGELOG.md | 52 +++++++++++++++++----------------- README.md | 52 +++++++++++++++++++++------------- altsrc/flag.go | 2 +- altsrc/flag_test.go | 2 +- altsrc/input_source_context.go | 2 +- altsrc/map_input_source.go | 2 +- altsrc/yaml_command_test.go | 2 +- altsrc/yaml_file_loader.go | 2 +- app.go | 2 +- runtests | 2 +- 10 files changed, 66 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a3ed2..b1e9485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ makes it easier to script around apps built using `cli` since they can trust that a 0 exit code indicated a successful execution. - cleanups based on [Go Report Card - feedback](https://goreportcard.com/report/github.com/codegangsta/cli) + feedback](https://goreportcard.com/report/github.com/urfave/cli) ## [1.16.0] - 2016-05-02 ### Added @@ -288,28 +288,28 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/codegangsta/cli/compare/v1.17.0...HEAD -[1.17.0]: https://github.com/codegangsta/cli/compare/v1.16.0...v1.17.0 -[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0 -[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0 -[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 -[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 -[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0 -[1.11.1]: https://github.com/codegangsta/cli/compare/v1.11.0...v1.11.1 -[1.11.0]: https://github.com/codegangsta/cli/compare/v1.10.2...v1.11.0 -[1.10.2]: https://github.com/codegangsta/cli/compare/v1.10.1...v1.10.2 -[1.10.1]: https://github.com/codegangsta/cli/compare/v1.10.0...v1.10.1 -[1.10.0]: https://github.com/codegangsta/cli/compare/v1.9.0...v1.10.0 -[1.9.0]: https://github.com/codegangsta/cli/compare/v1.8.0...v1.9.0 -[1.8.0]: https://github.com/codegangsta/cli/compare/v1.7.1...v1.8.0 -[1.7.1]: https://github.com/codegangsta/cli/compare/v1.7.0...v1.7.1 -[1.7.0]: https://github.com/codegangsta/cli/compare/v1.6.0...v1.7.0 -[1.6.0]: https://github.com/codegangsta/cli/compare/v1.5.0...v1.6.0 -[1.5.0]: https://github.com/codegangsta/cli/compare/v1.4.1...v1.5.0 -[1.4.1]: https://github.com/codegangsta/cli/compare/v1.4.0...v1.4.1 -[1.4.0]: https://github.com/codegangsta/cli/compare/v1.3.1...v1.4.0 -[1.3.1]: https://github.com/codegangsta/cli/compare/v1.3.0...v1.3.1 -[1.3.0]: https://github.com/codegangsta/cli/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/codegangsta/cli/compare/v1.1.0...v1.2.0 -[1.1.0]: https://github.com/codegangsta/cli/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/codegangsta/cli/compare/v0.1.0...v1.0.0 +[Unreleased]: https://github.com/urfave/cli/compare/v1.17.0...HEAD +[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 +[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 +[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 +[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0 +[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0 +[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0 +[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1 +[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0 +[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2 +[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1 +[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0 +[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0 +[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0 +[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0 +[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0 diff --git a/README.md b/README.md index eb62b2c..e7024c1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ -[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) -[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) -[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) -[![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli) -[![top level coverage](https://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) / -[![altsrc coverage](https://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc) +[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) +[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) +[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / +[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) # cli +**Notice:** This is the library formally known as +`github.com/codegangsta/cli` -- Github will automatically redirect requests +to this repository, but we recommend updating your references for clarity. + cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. ## Overview @@ -25,7 +29,7 @@ instructions](http://golang.org/doc/install.html). To install cli, simply run: ``` -$ go get github.com/codegangsta/cli +$ go get github.com/urfave/cli ``` Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: @@ -44,13 +48,13 @@ that, please use whatever version pinning of your preference, such as via `gopkg.in`: ``` -$ go get gopkg.in/codegangsta/cli.v2 +$ go get gopkg.in/urfave/cli.v2 ``` ``` go ... import ( - "gopkg.in/codegangsta/cli.v2" // imports as package "cli" + "gopkg.in/urfave/cli.v2" // imports as package "cli" ) ... ``` @@ -62,13 +66,13 @@ to avoid any unexpected compatibility pains once `v2` becomes `master`, then pinning to the `v1` branch is an acceptable option, e.g.: ``` -$ go get gopkg.in/codegangsta/cli.v1 +$ go get gopkg.in/urfave/cli.v1 ``` ``` go ... import ( - "gopkg.in/codegangsta/cli.v1" // imports as package "cli" + "gopkg.in/urfave/cli.v1" // imports as package "cli" ) ... ``` @@ -82,7 +86,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -102,7 +106,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -136,7 +140,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -254,7 +258,7 @@ app.Action = func(c *cli.Context) error { ... ``` -See full list of flags at http://godoc.org/github.com/codegangsta/cli +See full list of flags at http://godoc.org/github.com/urfave/cli #### Placeholder Values @@ -469,7 +473,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -568,7 +572,7 @@ import ( "io" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -617,8 +621,16 @@ VERSION: ## Contribution Guidelines -Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. +Feel free to put up a pull request to fix a bug or maybe add a feature. I will +give it a code review and make sure that it does not break backwards +compatibility. If I or any other collaborators agree that it is in line with +the vision of the project, we will work with you to get the code into +a mergeable state and merge it into the master branch. -If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. +If you have contributed something significant to the project, we will most +likely add you as a collaborator. As a collaborator you are given the ability +to merge others pull requests. It is very important that new code does not +break existing code, so be careful about what code you do choose to merge. -If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. +If you feel like you have contributed to the project but have not yet been +added as a collaborator, we probably forgot to add you, please open an issue. diff --git a/altsrc/flag.go b/altsrc/flag.go index 0a11ad5..3e44d02 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // FlagInputSourceExtension is an extension interface of cli.Flag that diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 4e25be6..218e9b8 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) type testApplyInputSource struct { diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 6d695ff..8ea7e92 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -3,7 +3,7 @@ package altsrc import ( "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // InputSourceContext is an interface used to allow diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 19f87af..b1c8e4f 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // MapInputSource implements InputSourceContext to return diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 519bd81..31f78ce 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func TestCommandYamlFileTest(t *testing.T) { diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 01797ad..b4e3365 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -12,7 +12,7 @@ import ( "net/url" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" "gopkg.in/yaml.v2" ) diff --git a/app.go b/app.go index 7c9b958..9c7f679 100644 --- a/app.go +++ b/app.go @@ -12,7 +12,7 @@ import ( ) var ( - changeLogURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md" + changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) diff --git a/runtests b/runtests index 9288f11..b0fd06f 100755 --- a/runtests +++ b/runtests @@ -10,7 +10,7 @@ from subprocess import check_call, check_output PACKAGE_NAME = os.environ.get( - 'CLI_PACKAGE_NAME', 'github.com/codegangsta/cli' + 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' ) From 6d6f7da978ced32210df3312cbf0d2d4bf79fa1b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 18:30:07 -0400 Subject: [PATCH 05/25] Merging from master/v1 --- CHANGELOG.md | 52 +++++++++++++++++----------------- README.md | 52 +++++++++++++++++++++------------- altsrc/flag.go | 2 +- altsrc/flag_test.go | 2 +- altsrc/input_source_context.go | 2 +- altsrc/map_input_source.go | 2 +- altsrc/yaml_command_test.go | 2 +- altsrc/yaml_file_loader.go | 2 +- runtests | 2 +- 9 files changed, 65 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c20a2..541bdbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,7 @@ makes it easier to script around apps built using `cli` since they can trust that a 0 exit code indicated a successful execution. - cleanups based on [Go Report Card - feedback](https://goreportcard.com/report/github.com/codegangsta/cli) + feedback](https://goreportcard.com/report/github.com/urfave/cli) ## [1.16.0] - 2016-05-02 ### Added @@ -317,28 +317,28 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/codegangsta/cli/compare/v1.17.0...HEAD -[1.17.0]: https://github.com/codegangsta/cli/compare/v1.16.0...v1.17.0 -[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0 -[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0 -[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 -[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 -[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0 -[1.11.1]: https://github.com/codegangsta/cli/compare/v1.11.0...v1.11.1 -[1.11.0]: https://github.com/codegangsta/cli/compare/v1.10.2...v1.11.0 -[1.10.2]: https://github.com/codegangsta/cli/compare/v1.10.1...v1.10.2 -[1.10.1]: https://github.com/codegangsta/cli/compare/v1.10.0...v1.10.1 -[1.10.0]: https://github.com/codegangsta/cli/compare/v1.9.0...v1.10.0 -[1.9.0]: https://github.com/codegangsta/cli/compare/v1.8.0...v1.9.0 -[1.8.0]: https://github.com/codegangsta/cli/compare/v1.7.1...v1.8.0 -[1.7.1]: https://github.com/codegangsta/cli/compare/v1.7.0...v1.7.1 -[1.7.0]: https://github.com/codegangsta/cli/compare/v1.6.0...v1.7.0 -[1.6.0]: https://github.com/codegangsta/cli/compare/v1.5.0...v1.6.0 -[1.5.0]: https://github.com/codegangsta/cli/compare/v1.4.1...v1.5.0 -[1.4.1]: https://github.com/codegangsta/cli/compare/v1.4.0...v1.4.1 -[1.4.0]: https://github.com/codegangsta/cli/compare/v1.3.1...v1.4.0 -[1.3.1]: https://github.com/codegangsta/cli/compare/v1.3.0...v1.3.1 -[1.3.0]: https://github.com/codegangsta/cli/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/codegangsta/cli/compare/v1.1.0...v1.2.0 -[1.1.0]: https://github.com/codegangsta/cli/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/codegangsta/cli/compare/v0.1.0...v1.0.0 +[Unreleased]: https://github.com/urfave/cli/compare/v1.17.0...HEAD +[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 +[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 +[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 +[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0 +[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0 +[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0 +[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1 +[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0 +[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2 +[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1 +[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0 +[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0 +[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0 +[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0 +[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0 diff --git a/README.md b/README.md index 0a1f624..52124e4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ -[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) -[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) -[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) -[![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli) -[![top level coverage](https://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) / -[![altsrc coverage](https://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc) +[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) +[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) +[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / +[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) # cli +**Notice:** This is the library formally known as +`github.com/codegangsta/cli` -- Github will automatically redirect requests +to this repository, but we recommend updating your references for clarity. + cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. ## Overview @@ -25,7 +29,7 @@ instructions](http://golang.org/doc/install.html). To install cli, simply run: ``` -$ go get github.com/codegangsta/cli +$ go get github.com/urfave/cli ``` Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: @@ -44,13 +48,13 @@ that, please use whatever version pinning of your preference, such as via `gopkg.in`: ``` -$ go get gopkg.in/codegangsta/cli.v2 +$ go get gopkg.in/urfave/cli.v2 ``` ``` go ... import ( - "gopkg.in/codegangsta/cli.v2" // imports as package "cli" + "gopkg.in/urfave/cli.v2" // imports as package "cli" ) ... ``` @@ -62,13 +66,13 @@ to avoid any unexpected compatibility pains once `v2` becomes `master`, then pinning to the `v1` branch is an acceptable option, e.g.: ``` -$ go get gopkg.in/codegangsta/cli.v1 +$ go get gopkg.in/urfave/cli.v1 ``` ``` go ... import ( - "gopkg.in/codegangsta/cli.v1" // imports as package "cli" + "gopkg.in/urfave/cli.v1" // imports as package "cli" ) ... ``` @@ -82,7 +86,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -102,7 +106,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -136,7 +140,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -254,7 +258,7 @@ app.Action = func(c *cli.Context) error { ... ``` -See full list of flags at http://godoc.org/github.com/codegangsta/cli +See full list of flags at http://godoc.org/github.com/urfave/cli #### Placeholder Values @@ -473,7 +477,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -573,7 +577,7 @@ import ( "io" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -622,8 +626,16 @@ VERSION: ## Contribution Guidelines -Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. +Feel free to put up a pull request to fix a bug or maybe add a feature. I will +give it a code review and make sure that it does not break backwards +compatibility. If I or any other collaborators agree that it is in line with +the vision of the project, we will work with you to get the code into +a mergeable state and merge it into the master branch. -If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. +If you have contributed something significant to the project, we will most +likely add you as a collaborator. As a collaborator you are given the ability +to merge others pull requests. It is very important that new code does not +break existing code, so be careful about what code you do choose to merge. -If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. +If you feel like you have contributed to the project but have not yet been +added as a collaborator, we probably forgot to add you, please open an issue. diff --git a/altsrc/flag.go b/altsrc/flag.go index 6c68bf5..acf68ae 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -6,7 +6,7 @@ import ( "os" "strconv" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // FlagInputSourceExtension is an extension interface of cli.Flag that diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 0b188da..5acdcfd 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) type testApplyInputSource struct { diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 0f391b2..56603cf 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -3,7 +3,7 @@ package altsrc import ( "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // InputSourceContext is an interface used to allow diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index a7fc628..68a749c 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // MapInputSource implements InputSourceContext to return diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index d1a15d7..02a51cb 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func TestCommandYamlFileTest(t *testing.T) { diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 01797ad..b4e3365 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -12,7 +12,7 @@ import ( "net/url" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" "gopkg.in/yaml.v2" ) diff --git a/runtests b/runtests index 9288f11..b0fd06f 100755 --- a/runtests +++ b/runtests @@ -10,7 +10,7 @@ from subprocess import check_call, check_output PACKAGE_NAME = os.environ.get( - 'CLI_PACKAGE_NAME', 'github.com/codegangsta/cli' + 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' ) From 97e55662b14cd0c2ddef86ee4fabb185884b8cc5 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 18:52:21 -0400 Subject: [PATCH 06/25] Add Windows (appveyor) badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e7024c1..0af6ed4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/meatballhat/cli) [![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) From 288d62118aa5ad0b6f980d89d3970bd2c68285a6 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:15:05 -0400 Subject: [PATCH 07/25] Try to get appveyor.yml working again --- appveyor.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3ca7afa..f32e013 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,14 +2,25 @@ version: "{build}" os: Windows Server 2012 R2 +environment: + GOPATH: c:\gopath + GOVERSION: 1.6 + install: + - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - set NEW_BUILD_DIR_DEST=c:\gopath\src\github.com\urfave + - mkdir %NEW_BUILD_DIR_DEST% + - move %APPVEYOR_BUILD_FOLDER% %NEW_BUILD_DIR_DEST% + - set APPVEYOR_BUILD_FOLDER=%NEW_BUILD_DIR%\cli - go version - go env + - go get github.com/urfave/gfmxr/... build_script: - cd %APPVEYOR_BUILD_FOLDER% - - go vet ./... - - go test -v ./... + - ./runtests vet + - ./runtests test + - ./runtests gfmxr test: off From 531c7defe7856ab78c8541195bc1b627734e6b6c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:18:38 -0400 Subject: [PATCH 08/25] Trying xcopy instead of move --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index f32e013..87a3209 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,8 +10,8 @@ install: - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - set NEW_BUILD_DIR_DEST=c:\gopath\src\github.com\urfave - mkdir %NEW_BUILD_DIR_DEST% - - move %APPVEYOR_BUILD_FOLDER% %NEW_BUILD_DIR_DEST% - - set APPVEYOR_BUILD_FOLDER=%NEW_BUILD_DIR%\cli + - xcopy /s %APPVEYOR_BUILD_FOLDER% %NEW_BUILD_DIR_DEST%\cli + - set APPVEYOR_BUILD_FOLDER=%NEW_BUILD_DIR_DEST%\cli - go version - go env - go get github.com/urfave/gfmxr/... From d277cbf893248383a11a041d2cb15fdf7750e54a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:32:48 -0400 Subject: [PATCH 09/25] Use clone_folder in appveyor.yml instead of dir moving shenanigans --- appveyor.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 87a3209..bc8a8d7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,22 +2,20 @@ version: "{build}" os: Windows Server 2012 R2 +clone_folder: c:\gopath\src\github.com\urfave\cli + environment: GOPATH: c:\gopath GOVERSION: 1.6 install: - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - - set NEW_BUILD_DIR_DEST=c:\gopath\src\github.com\urfave - - mkdir %NEW_BUILD_DIR_DEST% - - xcopy /s %APPVEYOR_BUILD_FOLDER% %NEW_BUILD_DIR_DEST%\cli - - set APPVEYOR_BUILD_FOLDER=%NEW_BUILD_DIR_DEST%\cli - go version - go env - go get github.com/urfave/gfmxr/... + - go get -v -t ./... build_script: - - cd %APPVEYOR_BUILD_FOLDER% - ./runtests vet - ./runtests test - ./runtests gfmxr From 9491a913365076b3400ea60a53440de5c18b6819 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:37:59 -0400 Subject: [PATCH 10/25] Try to run a python script --- appveyor.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bc8a8d7..542ef2a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,20 +5,23 @@ os: Windows Server 2012 R2 clone_folder: c:\gopath\src\github.com\urfave\cli environment: - GOPATH: c:\gopath + GOPATH: C:\gopath GOVERSION: 1.6 + PYTHON: C:\Python27-x64 + PYTHON_VERSION: 2.7.x + PYTHON_ARCH: 64 install: - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env - go get github.com/urfave/gfmxr/... - go get -v -t ./... build_script: - - ./runtests vet - - ./runtests test - - ./runtests gfmxr + - python runtests vet + - python runtests test + - python runtests gfmxr test: off From 4566119b39b91e708320cad678d04131e5197a3b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:44:21 -0400 Subject: [PATCH 11/25] Close temporary file before running go tool cover --- runtests | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/runtests b/runtests index b0fd06f..72c1f0d 100755 --- a/runtests +++ b/runtests @@ -49,9 +49,9 @@ def _test(): ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') ]) - combined = _combine_coverprofiles(coverprofiles) - _run('go tool cover -func={}'.format(combined.name).split()) - combined.close() + combined_name = _combine_coverprofiles(coverprofiles) + _run('go tool cover -func={}'.format(combined_name).split()) + os.remove(combined_name) def _gfmxr(): @@ -78,7 +78,9 @@ def _is_go_runnable(line): def _combine_coverprofiles(coverprofiles): - combined = tempfile.NamedTemporaryFile(suffix='.coverprofile') + combined = tempfile.NamedTemporaryFile( + suffix='.coverprofile', delete=False + ) combined.write('mode: set\n') for coverprofile in coverprofiles: @@ -88,7 +90,9 @@ def _combine_coverprofiles(coverprofiles): combined.write(line) combined.flush() - return combined + name = combined.name + combined.close() + return name if __name__ == '__main__': From b37df9de86ebf33dd689debd867420484530b66f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 20:35:25 -0400 Subject: [PATCH 12/25] See why README examples are unhappy on Windows --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 542ef2a..5470392 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ environment: PYTHON: C:\Python27-x64 PYTHON_VERSION: 2.7.x PYTHON_ARCH: 64 + GFMXR_DEBUG: 1 install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% From 377aca16cf86f6171a25565625a8ad9eaa763495 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 19:39:04 -0400 Subject: [PATCH 13/25] Trivial updates to appveyor config --- appveyor.yml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5470392..173086e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,17 +13,13 @@ environment: GFMXR_DEBUG: 1 install: - - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - - go version - - go env - - go get github.com/urfave/gfmxr/... - - go get -v -t ./... +- set PATH=%GOPATH%\bin;C:\go\bin;%PATH% +- go version +- go env +- go get github.com/urfave/gfmxr/... +- go get -v -t ./... build_script: - - python runtests vet - - python runtests test - - python runtests gfmxr - -test: off - -deploy: off +- python runtests vet +- python runtests test +- python runtests gfmxr From 61710ff108fd7c860d0ef572ac71ecab8f06db3c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:15:35 -0400 Subject: [PATCH 14/25] Make slice wrapping pattern more consistent and move *Args out into its own file --- app.go | 10 ++++----- app_test.go | 12 +++++------ args.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++ category.go | 49 ++++++++++++++++++++++++------------------- command.go | 2 +- context.go | 53 ---------------------------------------------- errors.go | 10 ++++----- 7 files changed, 104 insertions(+), 92 deletions(-) create mode 100644 args.go diff --git a/app.go b/app.go index 68a92a2..f6470b7 100644 --- a/app.go +++ b/app.go @@ -115,7 +115,7 @@ func (a *App) Setup() { 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) @@ -349,12 +349,10 @@ func (a *App) Categories() *CommandCategories { // Hidden=false func (a *App) VisibleCategories() []*CommandCategory { ret := []*CommandCategory{} - for _, category := range a.categories.Categories { + for _, category := range a.categories.Categories() { if visible := func() *CommandCategory { - for _, command := range category.Commands { - if !command.Hidden { - return category - } + if len(category.VisibleCommands()) > 0 { + return category } return nil }(); visible != nil { diff --git a/app_test.go b/app_test.go index 3c79766..7bac196 100644 --- a/app_test.go +++ b/app_test.go @@ -1143,17 +1143,17 @@ func TestApp_Run_Categories(t *testing.T) { app.Run([]string{"categories"}) expect := &CommandCategories{ - Categories: []*CommandCategory{ + slice: []*CommandCategory{ { Name: "1", - Commands: []*Command{ + commands: []*Command{ app.Commands[0], app.Commands[1], }, }, { Name: "2", - Commands: []*Command{ + commands: []*Command{ app.Commands[2], }, }, @@ -1196,13 +1196,13 @@ func TestApp_VisibleCategories(t *testing.T) { expected := []*CommandCategory{ { Name: "2", - Commands: []*Command{ + commands: []*Command{ app.Commands[1], }, }, { Name: "3", - Commands: []*Command{ + commands: []*Command{ app.Commands[2], }, }, @@ -1236,7 +1236,7 @@ func TestApp_VisibleCategories(t *testing.T) { expected = []*CommandCategory{ { Name: "3", - Commands: []*Command{ + commands: []*Command{ app.Commands[2], }, }, diff --git a/args.go b/args.go new file mode 100644 index 0000000..ab44f43 --- /dev/null +++ b/args.go @@ -0,0 +1,60 @@ +package cli + +import "errors" + +var ( + argsRangeErr = errors.New("index out of range") +) + +// Args wraps a string slice with some convenience methods +type Args struct { + slice []string +} + +// Get returns the nth argument, or else a blank string +func (a *Args) Get(n int) string { + if len(a.slice) > n { + return a.slice[n] + } + return "" +} + +// First returns the first argument, or else a blank string +func (a *Args) First() string { + return a.Get(0) +} + +// Tail returns the rest of the arguments (not the first one) +// or else an empty string slice +func (a *Args) Tail() []string { + if a.Len() >= 2 { + return a.slice[1:] + } + return []string{} +} + +// Len returns the length of the wrapped slice +func (a *Args) Len() int { + return len(a.slice) +} + +// Present checks if there are any arguments present +func (a *Args) Present() bool { + return a.Len() != 0 +} + +// Swap swaps arguments at the given indexes +func (a *Args) Swap(from, to int) error { + if from >= a.Len() || to >= a.Len() { + return argsRangeErr + } + a.slice[from], a.slice[to] = a.slice[to], a.slice[from] + return nil +} + +// Slice returns a copy of the internal slice +func (a *Args) Slice() []string { + ret := make([]string, len(a.slice)) + copy(ret, a.slice) + return ret +} diff --git a/category.go b/category.go index 9fb00f7..5899338 100644 --- a/category.go +++ b/category.go @@ -1,49 +1,56 @@ package cli -// CommandCategories is a slice of *CommandCategory. +// CommandCategories wraps a slice of *CommandCategory. type CommandCategories struct { - Categories []*CommandCategory + slice []*CommandCategory } func NewCommandCategories() *CommandCategories { - return &CommandCategories{Categories: []*CommandCategory{}} -} - -// CommandCategory is a category containing commands. -type CommandCategory struct { - Name string - Commands []*Command + return &CommandCategories{slice: []*CommandCategory{}} } func (c *CommandCategories) Less(i, j int) bool { - return c.Categories[i].Name < c.Categories[j].Name + return c.slice[i].Name < c.slice[j].Name } func (c *CommandCategories) Len() int { - return len(c.Categories) + return len(c.slice) } func (c *CommandCategories) Swap(i, j int) { - c.Categories[i], c.Categories[j] = c.Categories[j], c.Categories[i] + c.slice[i], c.slice[j] = c.slice[j], c.slice[i] } -// AddCommand adds a command to a category. -func (c *CommandCategories) AddCommand(category string, command *Command) *CommandCategories { - for _, commandCategory := range c.Categories { +// AddCommand adds a command to a category, creating a new category if necessary. +func (c *CommandCategories) AddCommand(category string, command *Command) { + for _, commandCategory := range c.slice { if commandCategory.Name == category { - commandCategory.Commands = append(commandCategory.Commands, command) - return c + commandCategory.commands = append(commandCategory.commands, command) + return } } - c.Categories = append(c.Categories, - &CommandCategory{Name: category, Commands: []*Command{command}}) - return c + c.slice = append(c.slice, + &CommandCategory{Name: category, commands: []*Command{command}}) +} + +// Categories returns a copy of the category slice +func (c *CommandCategories) Categories() []*CommandCategory { + ret := make([]*CommandCategory, len(c.slice)) + copy(ret, c.slice) + return ret +} + +// CommandCategory is a category containing commands. +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 { + for _, command := range c.commands { if !command.Hidden { ret = append(ret, command) } diff --git a/command.go b/command.go index fe31a36..b732a8e 100644 --- a/command.go +++ b/command.go @@ -195,7 +195,7 @@ func (c *Command) startApp(ctx *Context) error { 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) diff --git a/context.go b/context.go index 0728da9..39aa9be 100644 --- a/context.go +++ b/context.go @@ -157,59 +157,6 @@ func (c *Context) NArg() int { return c.Args().Len() } -// Args wraps a string slice with some convenience methods -type Args struct { - slice []string -} - -// Get returns the nth argument, or else a blank string -func (a *Args) Get(n int) string { - if len(a.slice) > n { - return a.slice[n] - } - return "" -} - -// First returns the first argument, or else a blank string -func (a *Args) First() string { - return a.Get(0) -} - -// Tail returns the rest of the arguments (not the first one) -// or else an empty string slice -func (a *Args) Tail() []string { - if a.Len() >= 2 { - return a.slice[1:] - } - return []string{} -} - -// Len returns the length of the wrapped slice -func (a *Args) Len() int { - return len(a.slice) -} - -// Present checks if there are any arguments present -func (a *Args) Present() bool { - return a.Len() != 0 -} - -// Swap swaps arguments at the given indexes -func (a *Args) Swap(from, to int) error { - if from >= a.Len() || to >= a.Len() { - return errors.New("index out of range") - } - a.slice[from], a.slice[to] = a.slice[to], a.slice[from] - return nil -} - -// Slice returns a copy of the internal slice -func (a *Args) Slice() []string { - ret := make([]string, len(a.slice)) - copy(ret, a.slice) - return ret -} - func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { for _, c := range ctx.Lineage() { if f := c.flagSet.Lookup(name); f != nil { diff --git a/errors.go b/errors.go index ea551be..a8091f4 100644 --- a/errors.go +++ b/errors.go @@ -16,18 +16,18 @@ var ErrWriter io.Writer = os.Stderr // MultiError is an error that wraps multiple errors. type MultiError struct { - Errors []error + errors []error } // NewMultiError creates a new MultiError. Pass in one or more errors. func NewMultiError(err ...error) MultiError { - return MultiError{Errors: err} + return MultiError{errors: err} } // Error implents the error interface. func (m MultiError) Error() string { - errs := make([]string, len(m.Errors)) - for i, err := range m.Errors { + errs := make([]string, len(m.errors)) + for i, err := range m.errors { errs[i] = err.Error() } @@ -85,7 +85,7 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.Errors { + for _, merr := range multiErr.errors { HandleExitCoder(merr) } } From dcc28a1b2b13f64eac3041fc6fbf316635e16517 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:19:54 -0400 Subject: [PATCH 15/25] Enable osx testing --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index bb886b8..796b40c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: go sudo: false +os: +- linux +- osx + go: - 1.2.2 - 1.3.3 From f136df348e2ce8cc5dadbb94d82c40fd90c013af Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:21:14 -0400 Subject: [PATCH 16/25] Only test latest go on osx --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 796b40c..96dedf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,6 @@ language: go sudo: false -os: -- linux -- osx - go: - 1.2.2 - 1.3.3 @@ -18,6 +14,8 @@ matrix: allow_failures: - go: master include: + - go: 1.6.2 + os: osx - go: 1.1.2 install: go get -v . before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION From e4666418bb09211416bfabc686f74bdbb3baeb56 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:34:33 -0400 Subject: [PATCH 17/25] Add (and backfill) some notes about platform support --- CHANGELOG.md | 2 ++ README.md | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e9485..0d51d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ## [Unreleased] ### Added - `./runtests` test runner with coverage tracking by default +- testing on OS X +- testing on Windows ### Fixed - Printing of command aliases in help text diff --git a/README.md b/README.md index 0af6ed4..581bb9a 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands c export PATH=$PATH:$GOPATH/bin ``` +### Supported platforms + +cli is tested against multiple versions of Go on Linux, and against the latest +released version of Go on OS X and Windows. For full details, see +[`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml). + ### Using the `v2` branch There is currently a long-lived branch named `v2` that is intended to land as From ec05a8d31b5a959b0ece090bae3b205c396dabf9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:55:03 -0400 Subject: [PATCH 18/25] Ensure flag "Name" field values are un-stringly-fied to (hopefully) help with bug triage & pinpointing usage issues since ripping out stringly typed "Name". --- flag.go | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/flag.go b/flag.go index 067e9ee..43e806f 100644 --- a/flag.go +++ b/flag.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "reflect" + "regexp" "runtime" "strconv" "strings" @@ -14,7 +15,11 @@ import ( const defaultPlaceholder = "value" -var slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) +var ( + slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) + + commaWhitespace = regexp.MustCompile("[, ]+.*") +) // BashCompletionFlag enables bash-completion for all commands and subcommands var BashCompletionFlag = BoolFlag{ @@ -95,7 +100,6 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { val := f.Value if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { val.Set(envVal) break @@ -110,7 +114,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { // Names returns the names of a flag. func (f GenericFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // StringSlice wraps a []string to satisfy flag.Value @@ -178,7 +182,6 @@ func (f StringSliceFlag) String() string { func (f StringSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { newVal := NewStringSlice() for _, s := range strings.Split(envVal, ",") { @@ -202,7 +205,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { // Names returns the name of a flag. func (f StringSliceFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // IntSlice wraps an []int to satisfy flag.Value @@ -285,7 +288,6 @@ func (f IntSliceFlag) String() string { func (f IntSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { newVal := NewIntSlice() for _, s := range strings.Split(envVal, ",") { @@ -312,7 +314,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f IntSliceFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // BoolFlag is a switch that defaults to false @@ -335,7 +337,6 @@ func (f BoolFlag) String() string { func (f BoolFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValBool, err := strconv.ParseBool(envVal) if err == nil { @@ -357,7 +358,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f BoolFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // StringFlag represents a flag that takes as string value @@ -380,7 +381,6 @@ func (f StringFlag) String() string { func (f StringFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { f.Value = envVal break @@ -399,7 +399,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f StringFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // IntFlag is a flag that takes an integer @@ -423,7 +423,6 @@ func (f IntFlag) String() string { func (f IntFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err == nil { @@ -445,7 +444,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f IntFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // DurationFlag is a flag that takes a duration specified in Go's duration @@ -469,7 +468,6 @@ func (f DurationFlag) String() string { func (f DurationFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValDuration, err := time.ParseDuration(envVal) if err == nil { @@ -491,7 +489,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f DurationFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // Float64Flag is a flag that takes an float value @@ -515,7 +513,6 @@ func (f Float64Flag) String() string { func (f Float64Flag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValFloat, err := strconv.ParseFloat(envVal, 10) if err == nil { @@ -536,7 +533,7 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f Float64Flag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } func visibleFlags(fl []Flag) []Flag { @@ -610,6 +607,19 @@ func withEnvHint(envVars []string, str string) string { return str + envText } +func flagNames(f Flag) []string { + ret := []string{} + + name := flagStringField(f, "Name") + aliases := flagStringSliceField(f, "Aliases") + + for _, part := range append([]string{name}, aliases...) { + ret = append(ret, commaWhitespace.ReplaceAllString(part, "")) + } + + return ret +} + func flagStringSliceField(f Flag, name string) []string { fv := flagValue(f) field := fv.FieldByName(name) From 8f25dbb615a179cf7c8e2f32a051cfd5ba684f30 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 22:00:59 -0400 Subject: [PATCH 19/25] Ensure all flag aliases are set when Destination given and extend "Incorrect Usage" message to show the error. Closes #430 --- app.go | 4 +-- command.go | 2 +- flag.go | 12 ++++---- flag_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index 622c7a1..2cc50ad 100644 --- a/app.go +++ b/app.go @@ -164,7 +164,7 @@ func (a *App) Run(arguments []string) (err error) { HandleExitCoder(err) return err } - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + fmt.Fprintf(a.Writer, "Incorrect Usage: %s\n\n", err) ShowAppHelp(context) return err } @@ -273,7 +273,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + fmt.Fprintf(a.Writer, "Incorrect Usage: %s\n\n", err) ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index c2f39d0..c578ce7 100644 --- a/command.go +++ b/command.go @@ -97,7 +97,7 @@ func (c Command) Run(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage: %s\n\n", err) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) return err diff --git a/flag.go b/flag.go index 067e9ee..3d338e8 100644 --- a/flag.go +++ b/flag.go @@ -104,7 +104,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { } for _, name := range f.Names() { - set.Var(f.Value, name, f.Usage) + set.Var(val, name, f.Usage) } } @@ -349,7 +349,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.BoolVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Bool(name, f.Value, f.Usage) } @@ -391,7 +391,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { 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) } @@ -437,7 +437,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { 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) } @@ -483,7 +483,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { 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) } @@ -528,7 +528,7 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { 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) } diff --git a/flag_test.go b/flag_test.go index 8ff64fb..eb3661f 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1,6 +1,7 @@ package cli import ( + "flag" "fmt" "os" "reflect" @@ -29,6 +30,17 @@ 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) +} + var stringFlagTests = []struct { name string aliases []string @@ -72,6 +84,17 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { } } +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 stringSliceFlagTests = []struct { name string aliases []string @@ -113,6 +136,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 @@ -149,6 +181,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 durationFlagTests = []struct { name string expected string @@ -185,6 +228,17 @@ 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 @@ -224,6 +278,15 @@ 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 float64FlagTests = []struct { name string expected string @@ -260,6 +323,17 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestFloat64FlagApply_SetsAllNames(t *testing.T) { + v := float64(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 genericFlagTests = []struct { name string value Generic @@ -297,6 +371,15 @@ 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{ From a41a43b00f21883beafae537a98308ee2078efec Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 22:03:19 -0400 Subject: [PATCH 20/25] Switch to fmt.Fprintf for displaying usage error --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index c578ce7..8bf0d8e 100644 --- a/command.go +++ b/command.go @@ -97,7 +97,7 @@ func (c Command) Run(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage: %s\n\n", err) + fmt.Fprintf(ctx.App.Writer, "Incorrect Usage: %s\n\n", err) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) return err From 2b288769c74e51a5fb1b449103900069f7b42e9b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 24 May 2016 04:00:37 -0400 Subject: [PATCH 21/25] Add comment about commaWhitespace stripping of flag.Name --- flag.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flag.go b/flag.go index a1c1913..8e8bd70 100644 --- a/flag.go +++ b/flag.go @@ -614,6 +614,10 @@ func flagNames(f Flag) []string { 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, "")) } From f2d92acb5d28d88360fee09a73e966f7046de864 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 24 May 2016 16:07:30 -0400 Subject: [PATCH 22/25] Point at correct gfmxr location --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 96dedf9..657e96a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ matrix: - ./runtests test before_script: -- go get github.com/meatballhat/gfmxr/... +- go get github.com/urfave/gfmxr/... script: - ./runtests vet From 3d75e9e7112f8e14896483dfefbf734ff05952cf Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 25 May 2016 12:05:14 -0400 Subject: [PATCH 23/25] Go with interfaces + private opaque types rather than public types that wrap slices --- app.go | 27 +++++++----------- app_test.go | 57 ++++++++++++++++++------------------- args.go | 66 +++++++++++++++++++++--------------------- category.go | 76 +++++++++++++++++++++++++++++++++---------------- command.go | 8 +++--- context.go | 7 +++-- errors.go | 50 +++++++++++++++++++------------- errors_test.go | 6 ++-- help.go | 21 +++++++++++--- help_test.go | 8 +++--- helpers_test.go | 4 +++ 11 files changed, 188 insertions(+), 142 deletions(-) diff --git a/app.go b/app.go index 15ee743..b16f991 100644 --- a/app.go +++ b/app.go @@ -36,8 +36,8 @@ 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 *CommandCategories + // Categories contains the categorized commands and is populated on app startup + Categories CommandCategories // An action to execute when the bash-completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -113,11 +113,11 @@ func (a *App) Setup() { } a.Commands = newCmds - a.categories = NewCommandCategories() + a.Categories = newCommandCategories() for _, command := range a.Commands { - a.categories.AddCommand(command.Category, command) + a.Categories.AddCommand(command.Category, command) } - sort.Sort(a.categories) + sort.Sort(a.Categories.(*commandCategories)) // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { @@ -184,7 +184,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 } @@ -296,7 +296,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if afterErr != nil { HandleExitCoder(err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -340,17 +340,12 @@ func (a *App) Command(name string) *Command { 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.Categories() { - if visible := func() *CommandCategory { +func (a *App) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + for _, category := range a.Categories.Categories() { + if visible := func() CommandCategory { if len(category.VisibleCommands()) > 0 { return category } diff --git a/app_test.go b/app_test.go index 7bac196..99bd6a3 100644 --- a/app_test.go +++ b/app_test.go @@ -216,7 +216,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string - var args *Args + var args Args app := NewApp() command := &Command{ @@ -241,7 +241,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { } func TestApp_CommandWithDash(t *testing.T) { - var args *Args + var args Args app := NewApp() command := &Command{ @@ -260,7 +260,7 @@ func TestApp_CommandWithDash(t *testing.T) { } func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { - var args *Args + var args Args app := NewApp() command := &Command{ @@ -1142,25 +1142,24 @@ func TestApp_Run_Categories(t *testing.T) { app.Run([]string{"categories"}) - expect := &CommandCategories{ - slice: []*CommandCategory{ - { - Name: "1", - commands: []*Command{ - app.Commands[0], - app.Commands[1], - }, + expect := commandCategories([]*commandCategory{ + { + name: "1", + commands: []*Command{ + app.Commands[0], + app.Commands[1], }, - { - Name: "2", - commands: []*Command{ - app.Commands[2], - }, + }, + { + 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() @@ -1193,15 +1192,15 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected := []*CommandCategory{ - { - Name: "2", + expected := []CommandCategory{ + &commandCategory{ + name: "2", commands: []*Command{ app.Commands[1], }, }, - { - Name: "3", + &commandCategory{ + name: "3", commands: []*Command{ app.Commands[2], }, @@ -1233,9 +1232,9 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected = []*CommandCategory{ - { - Name: "3", + expected = []CommandCategory{ + &commandCategory{ + name: "3", commands: []*Command{ app.Commands[2], }, @@ -1268,10 +1267,8 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected = []*CommandCategory{} - app.Setup() - expect(t, expected, app.VisibleCategories()) + expect(t, []CommandCategory{}, app.VisibleCategories()) } func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { diff --git a/args.go b/args.go index ab44f43..5618a47 100644 --- a/args.go +++ b/args.go @@ -6,55 +6,55 @@ var ( argsRangeErr = errors.New("index out of range") ) -// Args wraps a string slice with some convenience methods -type Args struct { - slice []string -} - -// Get returns the nth argument, or else a blank string -func (a *Args) Get(n int) string { - if len(a.slice) > n { - return a.slice[n] +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 "" } -// First returns the first argument, or else a blank string -func (a *Args) First() string { +func (a *args) First() string { return a.Get(0) } -// Tail returns the rest of the arguments (not the first one) -// or else an empty string slice -func (a *Args) Tail() []string { +func (a *args) Tail() []string { if a.Len() >= 2 { - return a.slice[1:] + tail := []string((*a)[1:]) + ret := make([]string, len(tail)) + copy(ret, tail) + return ret } return []string{} } -// Len returns the length of the wrapped slice -func (a *Args) Len() int { - return len(a.slice) +func (a *args) Len() int { + return len(*a) } -// Present checks if there are any arguments present -func (a *Args) Present() bool { +func (a *args) Present() bool { return a.Len() != 0 } -// Swap swaps arguments at the given indexes -func (a *Args) Swap(from, to int) error { - if from >= a.Len() || to >= a.Len() { - return argsRangeErr - } - a.slice[from], a.slice[to] = a.slice[to], a.slice[from] - return nil -} - -// Slice returns a copy of the internal slice -func (a *Args) Slice() []string { - ret := make([]string, len(a.slice)) - copy(ret, a.slice) +func (a *args) Slice() []string { + ret := make([]string, len(*a)) + copy(ret, []string(*a)) return ret } diff --git a/category.go b/category.go index 5899338..f2cb939 100644 --- a/category.go +++ b/category.go @@ -1,54 +1,80 @@ package cli -// CommandCategories wraps a slice of *CommandCategory. -type CommandCategories struct { - slice []*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 } -func NewCommandCategories() *CommandCategories { - return &CommandCategories{slice: []*CommandCategory{}} +type commandCategories []*commandCategory + +func newCommandCategories() CommandCategories { + ret := commandCategories([]*commandCategory{}) + return &ret } -func (c *CommandCategories) Less(i, j int) bool { - return c.slice[i].Name < c.slice[j].Name +func (c *commandCategories) Less(i, j int) bool { + return (*c)[i].Name() < (*c)[j].Name() } -func (c *CommandCategories) Len() int { - return len(c.slice) +func (c *commandCategories) Len() int { + return len(*c) } -func (c *CommandCategories) Swap(i, j int) { - c.slice[i], c.slice[j] = c.slice[j], c.slice[i] +func (c *commandCategories) Swap(i, j int) { + (*c)[i], (*c)[j] = (*c)[j], (*c)[i] } -// AddCommand adds a command to a category, creating a new category if necessary. -func (c *CommandCategories) AddCommand(category string, command *Command) { - for _, commandCategory := range c.slice { - if commandCategory.Name == category { +func (c *commandCategories) AddCommand(category string, command *Command) { + for _, commandCategory := range []*commandCategory(*c) { + if commandCategory.name == category { commandCategory.commands = append(commandCategory.commands, command) return } } - c.slice = append(c.slice, - &CommandCategory{Name: category, commands: []*Command{command}}) + newVal := commandCategories(append(*c, + &commandCategory{name: category, commands: []*Command{command}})) + (*c) = newVal } -// Categories returns a copy of the category slice -func (c *CommandCategories) Categories() []*CommandCategory { - ret := make([]*CommandCategory, len(c.slice)) - copy(ret, c.slice) +func (c *commandCategories) Categories() []CommandCategory { + ret := []CommandCategory{} + for _, cat := range *c { + ret = append(ret, cat) + } return ret } // CommandCategory is a category containing commands. -type CommandCategory struct { - Name string +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 { +func newCommandCategory(name string) *commandCategory { + return &commandCategory{ + name: name, + commands: []*Command{}, + } +} + +func (c *commandCategory) Name() string { + return c.name +} + +func (c *commandCategory) VisibleCommands() []*Command { + if c.commands == nil { + c.commands = []*Command{} + } + ret := []*Command{} for _, command := range c.commands { if !command.Hidden { diff --git a/command.go b/command.go index c8f6099..f05f1e2 100644 --- a/command.go +++ b/command.go @@ -120,7 +120,7 @@ func (c *Command) Run(ctx *Context) (err error) { if afterErr != nil { HandleExitCoder(err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -193,12 +193,12 @@ func (c *Command) startApp(ctx *Context) error { app.Compiled = ctx.App.Compiled app.Writer = ctx.App.Writer - app.categories = NewCommandCategories() + app.Categories = newCommandCategories() for _, command := range c.Subcommands { - 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 diff --git a/context.go b/context.go index 39aa9be..b43c957 100644 --- a/context.go +++ b/context.go @@ -10,7 +10,7 @@ import ( // 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 { App *App @@ -148,8 +148,9 @@ func (c *Context) Lineage() []*Context { } // Args returns the command line arguments associated with the context. -func (c *Context) Args() *Args { - return &Args{slice: c.flagSet.Args()} +func (c *Context) Args() Args { + ret := args(c.flagSet.Args()) + return &ret } // NArg returns the number of the command line arguments. diff --git a/errors.go b/errors.go index a8091f4..ba01537 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 } -// Error implents the error interface. -func (m MultiError) Error() string { - errs := make([]string, len(m.errors)) - for i, err := range m.errors { +type multiError []error + +// Error implements the error interface. +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 +} + // ExitCoder is the interface checked by `App` and `Command` for a custom exit // code type ExitCoder interface { @@ -41,29 +55,25 @@ type ExitCoder interface { ExitCode() int } -// ExitError fulfills both the builtin `error` interface and `ExitCoder` -type ExitError struct { +type exitError struct { exitCode int message string } -// NewExitError makes a new *ExitError -func NewExitError(message string, exitCode int) *ExitError { - return &ExitError{ +// Exit wraps a message and exit code into an ExitCoder suitable for handling by +// HandleExitCoder +func Exit(message string, exitCode int) ExitCoder { + return &exitError{ exitCode: exitCode, message: message, } } -// Error returns the string message, fulfilling the interface required by -// `error` -func (ee *ExitError) Error() string { +func (ee *exitError) Error() string { return 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 } @@ -85,7 +95,7 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.errors { + for _, merr := range multiErr.Errors() { HandleExitCoder(merr) } } diff --git a/errors_test.go b/errors_test.go index 8f5f284..5b4981f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - HandleExitCoder(NewExitError("galactic perimeter breach", 9)) + HandleExitCoder(Exit("galactic perimeter breach", 9)) expect(t, exitCode, 9) expect(t, called, true) @@ -51,8 +51,8 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - exitErr := NewExitError("galactic perimeter breach", 9) - err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) + exitErr := Exit("galactic perimeter breach", 9) + err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) HandleExitCoder(err) expect(t, exitCode, 9) diff --git a/help.go b/help.go index d02f7fd..c21639d 100644 --- a/help.go +++ b/help.go @@ -149,7 +149,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) @@ -201,15 +201,28 @@ func printHelp(out io.Writer, templ string, data interface{}) { w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + + errDebug := os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" + + defer func() { + if r := recover(); r != nil { + if errDebug { + fmt.Fprintf(ErrWriter, "CLI TEMPLATE PANIC: %#v\n", r) + } + if os.Getenv("CLI_TEMPLATE_REPANIC") != "" { + panic(r) + } + } + }() + err := t.Execute(w, data) if err != nil { - // If the writer is closed, t.Execute will fail, and there's nothing - // we can do to recover. - if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { + if errDebug { fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) } return } + w.Flush() } diff --git a/help_test.go b/help_test.go index 4d1dedc..b81701c 100644 --- a/help_test.go +++ b/help_test.go @@ -129,9 +129,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { 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") { @@ -157,9 +157,9 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { 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") { diff --git a/helpers_test.go b/helpers_test.go index 109ea7a..bcfa46b 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) From ed8f4ac40891a2c1c566447c7d6f005b111e4d39 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 25 May 2016 12:07:49 -0400 Subject: [PATCH 24/25] Fix example that uses ExitCoder --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9ebe31..11a3ea4 100644 --- a/README.md +++ b/README.md @@ -498,7 +498,7 @@ func main() { } app.Action = func(ctx *cli.Context) error { if !ctx.Bool("ginger-crouton") { - return cli.NewExitError("it is not in the soup", 86) + return cli.Exit("it is not in the soup", 86) } return nil } From d616529afc6477226f2941ff03f8da9e91e5ec4d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 27 May 2016 14:38:51 -0400 Subject: [PATCH 25/25] Use make once instead of a loop with append --- category.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/category.go b/category.go index f2cb939..3b405c0 100644 --- a/category.go +++ b/category.go @@ -39,9 +39,9 @@ func (c *commandCategories) AddCommand(category string, command *Command) { } func (c *commandCategories) Categories() []CommandCategory { - ret := []CommandCategory{} - for _, cat := range *c { - ret = append(ret, cat) + ret := make([]CommandCategory, len(*c)) + for i, cat := range *c { + ret[i] = cat } return ret }