diff --git a/README.md b/README.md index 9e4537b..11a3ea4 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,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", @@ -243,7 +243,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", @@ -275,7 +275,7 @@ indicated with back quotes. For example this: ```go -cli.StringFlag{ +&cli.StringFlag{ Name: "config", Aliases: []string{"c"}, Usage: "Load configuration from `FILE`", @@ -296,7 +296,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", @@ -313,7 +313,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", @@ -327,7 +327,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", @@ -344,7 +344,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. @@ -373,8 +373,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) @@ -386,7 +386,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"}, @@ -409,7 +409,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", @@ -442,7 +442,7 @@ E.g. ```go ... - app.Commands = []cli.Command{ + app.Commands = []*cli.Command{ { Name: "noop", }, @@ -490,7 +490,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?", @@ -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 } @@ -519,7 +519,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 acf68ae..f14ea1c 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 5acdcfd..e5b4c05 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 02a51cb..d5082af 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 2cc50ad..b16f991 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 @@ -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 @@ -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,16 +113,17 @@ 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) + 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 { - a.Commands = append(a.Commands, helpCommand) - if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } @@ -183,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 } @@ -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) @@ -294,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 } @@ -331,28 +333,21 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { func (a *App) Command(name string) *Command { for _, c := range a.Commands { if c.HasName(name) { - return &c + return c } } return nil } -// Categories returns a slice containing all the categories with the commands they contain -func (a *App) Categories() CommandCategories { - return a.categories -} - // VisibleCategories returns a slice of categories and commands that are // Hidden=false -func (a *App) VisibleCategories() []*CommandCategory { - ret := []*CommandCategory{} - for _, category := range a.categories { - if visible := func() *CommandCategory { - for _, command := range category.Commands { - if !command.Hidden { - return category - } +func (a *App) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + for _, category := range a.Categories.Categories() { + if visible := func() CommandCategory { + if len(category.VisibleCommands()) > 0 { + return category } return nil }(); visible != nil { @@ -363,8 +358,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 +393,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 +412,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..99bd6a3 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,23 +1142,24 @@ func TestApp_Run_Categories(t *testing.T) { app.Run([]string{"categories"}) - expect := CommandCategories{ - &CommandCategory{ - Name: "1", - Commands: []Command{ + expect := commandCategories([]*commandCategory{ + { + name: "1", + commands: []*Command{ app.Commands[0], app.Commands[1], }, }, - &CommandCategory{ - Name: "2", - Commands: []Command{ + { + name: "2", + commands: []*Command{ app.Commands[2], }, }, - } - if !reflect.DeepEqual(app.Categories(), expect) { - t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) + }) + + if !reflect.DeepEqual(app.Categories, &expect) { + t.Fatalf("expected categories %#v, to equal %#v", app.Categories, &expect) } output := buf.String() @@ -1149,7 +1173,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", @@ -1168,16 +1192,16 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected := []*CommandCategory{ - { - Name: "2", - Commands: []Command{ + expected := []CommandCategory{ + &commandCategory{ + name: "2", + commands: []*Command{ app.Commands[1], }, }, - { - Name: "3", - Commands: []Command{ + &commandCategory{ + name: "3", + commands: []*Command{ app.Commands[2], }, }, @@ -1188,7 +1212,7 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1208,10 +1232,10 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected = []*CommandCategory{ - { - Name: "3", - Commands: []Command{ + expected = []CommandCategory{ + &commandCategory{ + name: "3", + commands: []*Command{ app.Commands[2], }, }, @@ -1222,7 +1246,7 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1243,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) { @@ -1270,9 +1292,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 +1321,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 +1332,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { } return errors.New("intercepted: " + err.Error()) } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", }, @@ -1329,7 +1351,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 +1362,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/args.go b/args.go new file mode 100644 index 0000000..5618a47 --- /dev/null +++ b/args.go @@ -0,0 +1,60 @@ +package cli + +import "errors" + +var ( + argsRangeErr = errors.New("index out of range") +) + +type Args interface { + // Get returns the nth argument, or else a blank string + Get(n int) string + // First returns the first argument, or else a blank string + First() string + // Tail returns the rest of the arguments (not the first one) + // or else an empty string slice + Tail() []string + // Len returns the length of the wrapped slice + Len() int + // Present checks if there are any arguments present + Present() bool + // Slice returns a copy of the internal slice + Slice() []string +} + +type args []string + +func (a *args) Get(n int) string { + if len(*a) > n { + return (*a)[n] + } + return "" +} + +func (a *args) First() string { + return a.Get(0) +} + +func (a *args) Tail() []string { + if a.Len() >= 2 { + tail := []string((*a)[1:]) + ret := make([]string, len(tail)) + copy(ret, tail) + return ret + } + return []string{} +} + +func (a *args) Len() int { + return len(*a) +} + +func (a *args) Present() bool { + return a.Len() != 0 +} + +func (a *args) Slice() []string { + ret := make([]string, len(*a)) + copy(ret, []string(*a)) + return ret +} diff --git a/category.go b/category.go index 1a60550..3b405c0 100644 --- a/category.go +++ b/category.go @@ -1,41 +1,82 @@ package cli -// CommandCategories is a slice of *CommandCategory. -type CommandCategories []*CommandCategory +type CommandCategories interface { + // AddCommand adds a command to a category, creating a new category if necessary. + AddCommand(category string, command *Command) + // Categories returns a copy of the category slice + Categories() []CommandCategory +} -// CommandCategory is a category containing commands. -type CommandCategory struct { - Name string - Commands Commands +type commandCategories []*commandCategory + +func newCommandCategories() CommandCategories { + ret := commandCategories([]*commandCategory{}) + return &ret } -func (c CommandCategories) Less(i, j int) bool { - return c[i].Name < c[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) +func (c *commandCategories) Len() int { + return len(*c) } -func (c CommandCategories) Swap(i, j int) { - c[i], c[j] = c[j], c[i] +func (c *commandCategories) Swap(i, j int) { + (*c)[i], (*c)[j] = (*c)[j], (*c)[i] } -// AddCommand adds a command to a category. -func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { - for _, commandCategory := range c { - if commandCategory.Name == category { - commandCategory.Commands = append(commandCategory.Commands, command) - return c +func (c *commandCategories) AddCommand(category string, command *Command) { + for _, commandCategory := range []*commandCategory(*c) { + if commandCategory.name == category { + commandCategory.commands = append(commandCategory.commands, command) + return } } - return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) + newVal := commandCategories(append(*c, + &commandCategory{name: category, commands: []*Command{command}})) + (*c) = newVal +} + +func (c *commandCategories) Categories() []CommandCategory { + ret := make([]CommandCategory, len(*c)) + for i, cat := range *c { + ret[i] = cat + } + return ret +} + +// CommandCategory is a category containing commands. +type CommandCategory interface { + // Name returns the category name string + Name() string + // VisibleCommands returns a slice of the Commands with Hidden=false + VisibleCommands() []*Command } -// VisibleCommands returns a slice of the Commands with Hidden=false -func (c *CommandCategory) VisibleCommands() []Command { - ret := []Command{} - for _, command := range c.Commands { +type commandCategory struct { + name string + commands []*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 { ret = append(ret, command) } diff --git a/command.go b/command.go index 8bf0d8e..f05f1e2 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) @@ -127,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 } @@ -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,12 +193,12 @@ 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) + app.Categories.AddCommand(command.Category, command) } - sort.Sort(app.categories) + sort.Sort(app.Categories.(*commandCategories)) // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion @@ -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..b43c957 100644 --- a/context.go +++ b/context.go @@ -10,11 +10,11 @@ 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 - Command Command + Command *Command flagSet *flag.FlagSet parentContext *Context @@ -147,54 +147,15 @@ 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 + ret := args(c.flagSet.Args()) + return &ret } // NArg returns the number of the command line arguments. func (c *Context) NArg() int { - return len(c.Args()) -} - -// Get returns the nth argument, or else a blank string -func (a Args) Get(n int) string { - if len(a) > n { - return a[n] - } - return "" -} - -// 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 len(a) >= 2 { - return []string(a)[1:] - } - return []string{} -} - -// Present checks if there are any arguments present -func (a Args) Present() bool { - return len(a) != 0 -} - -// Swap swaps arguments at the given indexes -func (a Args) Swap(from, to int) error { - if from >= len(a) || to >= len(a) { - return errors.New("index out of range") - } - a[from], a[to] = a[to], a[from] - return nil + return c.Args().Len() } func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { 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/errors.go b/errors.go index ea551be..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/flag.go b/flag.go index 8e8bd70..a018fef 100644 --- a/flag.go +++ b/flag.go @@ -22,22 +22,22 @@ var ( ) // 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", @@ -90,13 +90,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 { @@ -113,7 +113,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 flagNames(f) } @@ -174,12 +174,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 { if envVal := os.Getenv(envVar); envVal != "" { @@ -204,7 +204,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 flagNames(f) } @@ -280,12 +280,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 { if envVal := os.Getenv(envVar); envVal != "" { @@ -313,7 +313,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 flagNames(f) } @@ -329,12 +329,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 { if envVal := os.Getenv(envVar); envVal != "" { @@ -357,7 +357,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 flagNames(f) } @@ -373,12 +373,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 { if envVal := os.Getenv(envVar); envVal != "" { @@ -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 flagNames(f) } @@ -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 { if envVal := os.Getenv(envVar); envVal != "" { @@ -443,7 +443,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 flagNames(f) } @@ -460,12 +460,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 { if envVal := os.Getenv(envVar); envVal != "" { @@ -488,7 +488,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 flagNames(f) } @@ -505,12 +505,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 { if envVal := os.Getenv(envVar); envVal != "" { @@ -532,14 +532,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 flagNames(f) } 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) } } @@ -658,10 +658,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()) @@ -693,7 +693,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() { @@ -704,7 +704,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() { @@ -731,3 +731,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 eb3661f..2d08af1 100644 --- a/flag_test.go +++ b/flag_test.go @@ -21,7 +21,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 { @@ -58,7 +58,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 { @@ -71,7 +71,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]" @@ -110,7 +110,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 { @@ -123,7 +123,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]" @@ -155,7 +155,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 { @@ -168,7 +168,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]" @@ -202,7 +202,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 { @@ -215,7 +215,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]" @@ -252,7 +252,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 { @@ -265,7 +265,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]" @@ -297,7 +297,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 { @@ -310,7 +310,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]" @@ -345,7 +345,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 { @@ -358,7 +358,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]" @@ -383,7 +383,7 @@ func TestGenericFlagApply_SetsAllNames(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" { @@ -401,7 +401,7 @@ func TestParseDestinationString(t *testing.T) { var dest string a := App{ Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "dest", Destination: &dest, }, @@ -421,7 +421,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" { @@ -440,7 +440,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" { @@ -457,7 +457,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"} @@ -475,7 +475,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"} @@ -493,7 +493,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"}) { @@ -513,7 +513,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"}) { @@ -533,7 +533,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"}) { @@ -553,7 +553,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"}) { @@ -573,7 +573,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"}) { @@ -590,7 +590,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 { @@ -609,7 +609,7 @@ func TestParseDestinationInt(t *testing.T) { var dest int a := App{ Flags: []Flag{ - IntFlag{ + &IntFlag{ Name: "dest", Destination: &dest, }, @@ -629,7 +629,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 { @@ -649,7 +649,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 { @@ -667,7 +667,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}) { @@ -684,7 +684,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}) { @@ -701,7 +701,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}) { @@ -721,7 +721,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}) { @@ -741,7 +741,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}) { @@ -761,7 +761,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}) { @@ -778,7 +778,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 { @@ -797,7 +797,7 @@ func TestParseDestinationFloat64(t *testing.T) { var dest float64 a := App{ Flags: []Flag{ - Float64Flag{ + &Float64Flag{ Name: "dest", Destination: &dest, }, @@ -817,7 +817,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 { @@ -837,7 +837,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 { @@ -855,7 +855,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 { @@ -874,7 +874,7 @@ func TestParseDestinationBool(t *testing.T) { var dest bool a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Destination: &dest, }, @@ -894,7 +894,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 { @@ -914,7 +914,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 { @@ -932,7 +932,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") { @@ -952,7 +952,7 @@ func TestParseDestinationBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Value: true, Destination: &dest, @@ -973,7 +973,7 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "0") a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "debug", Aliases: []string{"d"}, Value: true, @@ -998,7 +998,7 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "0") a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "debug", Aliases: []string{"d"}, Value: true, @@ -1039,7 +1039,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"}) { @@ -1059,7 +1059,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{}, @@ -1084,7 +1084,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..c21639d 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", @@ -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) @@ -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 @@ -193,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 9ca4c0d..b81701c 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 { @@ -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") { @@ -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 { 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)