diff --git a/app_test.go b/app_test.go index 00b12e9..796f665 100644 --- a/app_test.go +++ b/app_test.go @@ -187,17 +187,20 @@ func ExampleApp_Run_noAction() { app.Run([]string{"greet"}) // Output: // NAME: - // greet + // greet - A new cli application // // USAGE: - // [global options] command [command options] [arguments...] + // greet [global options] command [command options] [arguments...] + // + // VERSION: + // 0.0.0 // // COMMANDS: // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: - // --help, -h show help - // --version, -v print the version + // --help, -h show help (default: false) + // --version, -v print the version (default: false) } func ExampleApp_Run_subcommandNoAction() { @@ -215,19 +218,22 @@ func ExampleApp_Run_subcommandNoAction() { app.Run([]string{"greet", "describeit"}) // Output: // NAME: - // describeit - use it to see a description + // greet describeit - use it to see a description // // USAGE: - // describeit [arguments...] + // greet describeit [command options] [arguments...] // // DESCRIPTION: // This is how we describe describeit the function + // + // OPTIONS: + // --help, -h show help (default: false) } func ExampleApp_Run_shellComplete() { // set args for examples sake - os.Args = []string{"greet", "--generate-completion"} + os.Args = []string{"greet", fmt.Sprintf("--%s", genCompName())} app := &App{ Name: "greet", @@ -311,30 +317,6 @@ func TestApp_Setup_defaultsWriter(t *testing.T) { expect(t, app.Writer, os.Stdout) } -func TestApp_CommandWithArgBeforeFlags(t *testing.T) { - var parsedOption, firstArg string - - app := &App{ - Commands: []*Command{ - { - Name: "cmd", - Flags: []Flag{ - &StringFlag{Name: "option", Value: "", Usage: "some option"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - firstArg = c.Args().First() - return nil - }, - }, - }, - } - app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) - - expect(t, parsedOption, "my-option") - expect(t, firstArg, "my-arg") -} - func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context @@ -907,6 +889,7 @@ func TestApp_OrderOfOperations(t *testing.T) { app := &App{ EnableShellCompletion: true, ShellComplete: func(c *Context) { + fmt.Fprintf(os.Stderr, "---> ShellComplete(%#v)\n", c) counts.Total++ counts.ShellComplete = counts.Total }, @@ -971,7 +954,7 @@ func TestApp_OrderOfOperations(t *testing.T) { resetCounts() - _ = app.Run([]string{"command", "--generate-completion"}) + _ = app.Run([]string{"command", fmt.Sprintf("--%s", genCompName())}) expect(t, counts.ShellComplete, 1) expect(t, counts.Total, 1) diff --git a/command.go b/command.go index bf0acb2..4f0d142 100644 --- a/command.go +++ b/command.go @@ -159,6 +159,10 @@ func (c *Command) Run(ctx *Context) (err error) { } } + if c.Action == nil { + c.Action = helpSubcommand.Action + } + context.Command = c err = c.Action(context) diff --git a/context.go b/context.go index aa015b9..9802594 100644 --- a/context.go +++ b/context.go @@ -121,7 +121,7 @@ func (c *Context) Lineage() []*Context { return lineage } -// value returns the value of the flag coressponding to `name` +// value returns the value of the flag corresponding to `name` func (c *Context) value(name string) interface{} { return c.flagSet.Lookup(name).Value.(flag.Getter).Get() } diff --git a/errors.go b/errors.go index 236ef02..6259699 100644 --- a/errors.go +++ b/errors.go @@ -103,9 +103,8 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.Errors() { - HandleExitCoder(merr) - } + code := handleMultiError(multiErr) + OsExiter(code) return } } @@ -115,7 +114,7 @@ func handleMultiError(multiErr MultiError) int { for _, merr := range multiErr.Errors() { if multiErr2, ok := merr.(MultiError); ok { code = handleMultiError(multiErr2) - } else { + } else if merr != nil { fmt.Fprintln(ErrWriter, merr) if exitErr, ok := merr.(ExitCoder); ok { code = exitErr.ExitCode() diff --git a/flag.go b/flag.go index 8e6920a..41a59af 100644 --- a/flag.go +++ b/flag.go @@ -23,8 +23,9 @@ var ( // GenerateCompletionFlag enables completion for all commands and subcommands var GenerateCompletionFlag Flag = &BoolFlag{ - Name: "generate-completion", - Hidden: true, + Name: "generate-completion", + Aliases: []string{"compgen"}, + Hidden: true, } func genCompName() string { @@ -218,7 +219,7 @@ func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as string value for flag %s: %s", envVal, f.Name, err) } } f.Value = newVal @@ -322,7 +323,7 @@ func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", envVal, f.Name, err) } } f.Value = newVal @@ -406,7 +407,7 @@ func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", envVal, f.Name, err) } } f.Value = newVal @@ -436,11 +437,16 @@ func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { - envValBool, err := strconv.ParseBool(envVal) - if err == nil { - f.Value = envValBool + if envVal == "" { + f.Value = false + break } + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %q as bool value for flag %s: %s", envVal, f.Name, err) + } + f.Value = envValBool break } } @@ -496,7 +502,7 @@ func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as int value for flag %s: %s", envVal, f.Name, err) } f.Value = int(envValInt) break @@ -527,7 +533,7 @@ func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as int value for flag %s: %s", envVal, f.Name, err) } f.Value = envValInt @@ -559,7 +565,7 @@ func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseUint(envVal, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as uint value for flag %s: %s", envVal, f.Name, err) } f.Value = uint(envValInt) @@ -591,7 +597,7 @@ func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseUint(envVal, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", envVal, f.Name, err) } f.Value = uint64(envValInt) @@ -623,7 +629,7 @@ func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValDuration, err := time.ParseDuration(envVal) if err != nil { - return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as duration for flag %s: %s", envVal, f.Name, err) } f.Value = envValDuration @@ -655,7 +661,7 @@ func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValFloat, err := strconv.ParseFloat(envVal, 10) if err != nil { - return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", envVal, f.Name, err) } f.Value = float64(envValFloat) diff --git a/flag_test.go b/flag_test.go index 1e44db6..2c42176 100644 --- a/flag_test.go +++ b/flag_test.go @@ -43,6 +43,24 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) { } func TestFlagsFromEnv(t *testing.T) { + newSetIntSlice := func(defaults ...int) IntSlice { + s := NewIntSlice(defaults...) + s.hasBeenSet = true + return *s + } + + newSetInt64Slice := func(defaults ...int64) Int64Slice { + s := NewInt64Slice(defaults...) + s.hasBeenSet = true + return *s + } + + newSetStringSlice := func(defaults ...string) StringSlice { + s := NewStringSlice(defaults...) + s.hasBeenSet = true + return *s + } + var flagTests = []struct { input string output interface{} @@ -52,54 +70,55 @@ func TestFlagsFromEnv(t *testing.T) { {"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, - {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, + {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value for flag debug: .*`}, {"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""}, - {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)}, + {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration for flag time: .*`}, {"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, {"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)}, + {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value for flag seconds: .*`}, {"1", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, - {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, + {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, + {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, {"1", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, - {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, + {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, + {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, - {"1,2", NewIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2,2", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)}, - {"foobar", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)}, + {"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`}, + {"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`}, - {"1,2", NewInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2,2", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)}, - {"foobar", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)}, + {"1,2", newSetInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value for flag seconds: .*`}, + {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value for flag seconds: .*`}, {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""}, - {"foo,bar", NewStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, + {"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, {"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)}, - {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)}, + {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value for flag seconds: .*`}, + {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value for flag seconds: .*`}, {"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)}, - {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)}, + {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value for flag seconds: .*`}, + {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value for flag seconds: .*`}, {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } - for _, test := range flagTests { + for i, test := range flagTests { clearenv() - os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVars").Slice(0, 1).String(), test.input) + envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) + os.Setenv(envVarSlice.Index(0).String(), test.input) a := App{ Flags: []Flag{test.flag}, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.value(test.flag.Names()[0]), test.output) { - t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.value(test.flag.Names()[0])) + t.Errorf("ex:%01d expected %q to be parsed as %#v, instead was %#v", i, test.input, test.output, ctx.value(test.flag.Names()[0])) } return nil }, @@ -109,15 +128,15 @@ func TestFlagsFromEnv(t *testing.T) { if test.errRegexp != "" { if err == nil { - t.Errorf("expected error to match %s, got none", test.errRegexp) + t.Errorf("expected error to match %q, got none", test.errRegexp) } else { if matched, _ := regexp.MatchString(test.errRegexp, err.Error()); !matched { - t.Errorf("expected error to match %s, got error %s", test.errRegexp, err) + t.Errorf("expected error to match %q, got error %s", test.errRegexp, err) } } } else { if err != nil && test.errRegexp == "" { - t.Errorf("expected no error got %s", err) + t.Errorf("expected no error got %q", err) } } } diff --git a/help.go b/help.go index 6c4f213..a722564 100644 --- a/help.go +++ b/help.go @@ -318,29 +318,21 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { } func checkCompletions(c *Context) bool { - if !c.Bool(genCompName()) && !c.App.EnableShellCompletion { - return false + if c.shellComplete { + ShowCompletions(c) + return true } - if args := c.Args(); args.Present() { - name := args.First() - if cmd := c.App.Command(name); cmd != nil { - // let the command handle the completion - return false - } - } - - ShowCompletions(c) - return true + return false } func checkCommandCompletions(c *Context, name string) bool { - if !c.Bool(genCompName()) && !c.App.EnableShellCompletion { - return false + if c.shellComplete { + ShowCommandCompletions(c, name) + return true } - ShowCommandCompletions(c, name) - return true + return false } func checkInitCompletion(c *Context) (bool, error) { @@ -366,12 +358,12 @@ func bashCompletionCode(progName string) string { local cur opts base; COMPREPLY=(); cur="${COMP_WORDS[COMP_CWORD]}"; - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-completion ); + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --%s ); COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ); return 0; }; complete -F _cli_bash_autocomplete %s` - return fmt.Sprintf(template, progName) + return fmt.Sprintf(template, genCompName(), progName) } func zshCompletionCode(progName string) string {