From 8b46886de806f530a31802bd135a808ff33d5761 Mon Sep 17 00:00:00 2001 From: Kaushal Subedi Date: Sat, 24 Oct 2015 23:37:21 -0600 Subject: [PATCH 01/24] added flag to have a custom text on the USAGE section of help --- command.go | 2 ++ help.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index fac754d..500f235 100644 --- a/command.go +++ b/command.go @@ -16,6 +16,8 @@ type Command struct { Aliases []string // A short description of the usage of this command Usage string + // Custom text to show on USAGE section of help + UsageText string // A longer explanation of how the command works Description string // A short description of the arguments of this command diff --git a/help.go b/help.go index a246f63..ff7c4cb 100644 --- a/help.go +++ b/help.go @@ -15,7 +15,7 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} {{if .Version}} VERSION: {{.Version}} From c70ad9b688cec7cea81b9886467987bf478bb5c5 Mon Sep 17 00:00:00 2001 From: Kaushal Subedi Date: Sat, 24 Oct 2015 23:51:06 -0600 Subject: [PATCH 02/24] fixed tests --- app.go | 3 +++ app_test.go | 1 + 2 files changed, 4 insertions(+) diff --git a/app.go b/app.go index 9a15c0c..df6ecaf 100644 --- a/app.go +++ b/app.go @@ -17,6 +17,8 @@ type App struct { HelpName string // Description of the program. Usage string + // Text to override the USAGE section of help + UsageText string // Description of the program argument format. ArgsUsage string // Version of the program @@ -73,6 +75,7 @@ func NewApp() *App { Name: os.Args[0], HelpName: os.Args[0], Usage: "A new cli application", + UsageText: "", Version: "0.0.0", BashComplete: DefaultAppComplete, Action: helpCommand.Action, diff --git a/app_test.go b/app_test.go index ada5d69..93a67d0 100644 --- a/app_test.go +++ b/app_test.go @@ -22,6 +22,7 @@ func ExampleApp() { app.Action = func(c *Context) { fmt.Printf("Hello %v\n", c.String("name")) } + app.UsageText = "app [first_arg] [second_arg]" app.Author = "Harrison" app.Email = "harrison@lolwut.com" app.Authors = []Author{Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} From c538c376c9859540836cbbf2096fa90efa9a602f Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 25 Sep 2015 14:13:36 -0700 Subject: [PATCH 03/24] Do not return error when flag parsing should be skipped Signed-off-by: Nathan LeClaire --- command.go | 6 ++++++ command_test.go | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/command.go b/command.go index fac754d..7a1dcab 100644 --- a/command.go +++ b/command.go @@ -102,6 +102,12 @@ func (c Command) Run(ctx *Context) error { err = set.Parse(append(flagArgs, regularArgs...)) } else { err = set.Parse(ctx.Args().Tail()) + + // Work around issue where if the first arg in ctx.Args.Tail() + // is a flag, set.Parse returns an error + if c.SkipFlagParsing { + err = nil + } } if err != nil { diff --git a/command_test.go b/command_test.go index fd39548..bf7cc99 100644 --- a/command_test.go +++ b/command_test.go @@ -45,3 +45,25 @@ func TestCommandIgnoreFlags(t *testing.T) { expect(t, err, nil) } + +// Fix bug with ignoring flag parsing that would still parse the first flag +func TestCommandIgnoreFlagsIncludingFirstArgument(t *testing.T) { + app := NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"blah", "-break"} + set.Parse(test) + + c := NewContext(app, set, nil) + + command := Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) {}, + SkipFlagParsing: true, + } + err := command.Run(c) + + expect(t, err, nil) +} From bc3cb33cef749380a3e1ae4c1a04312521c5f0be Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 17 Oct 2015 11:36:09 -0700 Subject: [PATCH 04/24] Actually skip parsing of flags if SkipFlagParsing is set Previous just skipped if firstFlagIndex was > -1 --- command.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/command.go b/command.go index 7a1dcab..c63b049 100644 --- a/command.go +++ b/command.go @@ -86,27 +86,23 @@ func (c Command) Run(ctx *Context) error { } var err error - if firstFlagIndex > -1 && !c.SkipFlagParsing { - args := ctx.Args() - regularArgs := make([]string, len(args[1:firstFlagIndex])) - copy(regularArgs, args[1:firstFlagIndex]) - - var flagArgs []string - if terminatorIndex > -1 { - flagArgs = args[firstFlagIndex:terminatorIndex] - regularArgs = append(regularArgs, args[terminatorIndex:]...) + if !c.SkipFlagParsing { + if firstFlagIndex > -1 { + args := ctx.Args() + regularArgs := make([]string, len(args[1:firstFlagIndex])) + copy(regularArgs, args[1:firstFlagIndex]) + + var flagArgs []string + if terminatorIndex > -1 { + flagArgs = args[firstFlagIndex:terminatorIndex] + regularArgs = append(regularArgs, args[terminatorIndex:]...) + } else { + flagArgs = args[firstFlagIndex:] + } + + err = set.Parse(append(flagArgs, regularArgs...)) } else { - flagArgs = args[firstFlagIndex:] - } - - err = set.Parse(append(flagArgs, regularArgs...)) - } else { - err = set.Parse(ctx.Args().Tail()) - - // Work around issue where if the first arg in ctx.Args.Tail() - // is a flag, set.Parse returns an error - if c.SkipFlagParsing { - err = nil + err = set.Parse(ctx.Args().Tail()) } } From 6191d931b7fc228924bd8dd6a3952301b14078af Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 26 Oct 2015 21:40:03 -0700 Subject: [PATCH 05/24] When skipping flag parsing, still parse into arguments Fool the FlagSet into thinking that all arguments are actually arguments rather than attempting to parse them as flags. --- command.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/command.go b/command.go index c63b049..824e77b 100644 --- a/command.go +++ b/command.go @@ -74,19 +74,19 @@ func (c Command) Run(ctx *Context) error { set := flagSet(c.Name, c.Flags) set.SetOutput(ioutil.Discard) - firstFlagIndex := -1 - terminatorIndex := -1 - for index, arg := range ctx.Args() { - if arg == "--" { - terminatorIndex = index - break - } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { - firstFlagIndex = index - } - } - var err error if !c.SkipFlagParsing { + firstFlagIndex := -1 + terminatorIndex := -1 + for index, arg := range ctx.Args() { + if arg == "--" { + terminatorIndex = index + break + } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { + firstFlagIndex = index + } + } + if firstFlagIndex > -1 { args := ctx.Args() regularArgs := make([]string, len(args[1:firstFlagIndex])) @@ -104,6 +104,10 @@ func (c Command) Run(ctx *Context) error { } else { err = set.Parse(ctx.Args().Tail()) } + } else { + if c.SkipFlagParsing { + err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) + } } if err != nil { From 8cd49b108c0b76b4de71d89a106c7f6e59e536fe Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 26 Oct 2015 21:45:28 -0700 Subject: [PATCH 06/24] Update TestCommandIgnoreFlagsIncludingFirstArgument to test for arguments --- command_test.go | 3 ++- helpers_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/command_test.go b/command_test.go index bf7cc99..1e9e7fa 100644 --- a/command_test.go +++ b/command_test.go @@ -64,6 +64,7 @@ func TestCommandIgnoreFlagsIncludingFirstArgument(t *testing.T) { SkipFlagParsing: true, } err := command.Run(c) - expect(t, err, nil) + + expect(t, []string(c.Args()), []string{"blah", "-break"}) } diff --git a/helpers_test.go b/helpers_test.go index 3ce8e93..b1b7339 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -7,13 +7,13 @@ import ( /* Test Helpers */ func expect(t *testing.T, a interface{}, b interface{}) { - if a != b { + if !reflect.DeepEqual(a, b) { t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } func refute(t *testing.T, a interface{}, b interface{}) { - if a == b { + if reflect.DeepEqual(a, b) { t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } From 3323ab44608a53d356d25fa462a114b111950f2f Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Tue, 27 Oct 2015 13:33:55 -0700 Subject: [PATCH 07/24] Use a test table and add --help test Signed-off-by: Nathan LeClaire --- command_test.go | 81 +++++++++++++++++-------------------------------- 1 file changed, 27 insertions(+), 54 deletions(-) diff --git a/command_test.go b/command_test.go index 1e9e7fa..dd9fc87 100644 --- a/command_test.go +++ b/command_test.go @@ -1,70 +1,43 @@ package cli import ( + "errors" "flag" "testing" ) -func TestCommandDoNotIgnoreFlags(t *testing.T) { - app := NewApp() - set := flag.NewFlagSet("test", 0) - test := []string{"blah", "blah", "-break"} - set.Parse(test) - - c := NewContext(app, set, nil) - - command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) {}, +func TestCommandFlagParsing(t *testing.T) { + cases := []struct { + testArgs []string + skipFlagParsing bool + expectedErr error + }{ + {[]string{"blah", "blah", "-break"}, false, errors.New("flag provided but not defined: -break")}, // Test normal "not ignoring flags" flow + {[]string{"blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags + {[]string{"blah", "-break"}, true, nil}, // Test SkipFlagParsing with random flag arg + {[]string{"blah", "-help"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg } - err := command.Run(c) - - expect(t, err.Error(), "flag provided but not defined: -break") -} -func TestCommandIgnoreFlags(t *testing.T) { - app := NewApp() - set := flag.NewFlagSet("test", 0) - test := []string{"blah", "blah", "-break"} - set.Parse(test) + for _, c := range cases { + app := NewApp() + set := flag.NewFlagSet("test", 0) + set.Parse(c.testArgs) - c := NewContext(app, set, nil) + context := NewContext(app, set, nil) - command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) {}, - SkipFlagParsing: true, - } - err := command.Run(c) - - expect(t, err, nil) -} + command := Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) {}, + } -// Fix bug with ignoring flag parsing that would still parse the first flag -func TestCommandIgnoreFlagsIncludingFirstArgument(t *testing.T) { - app := NewApp() - set := flag.NewFlagSet("test", 0) - test := []string{"blah", "-break"} - set.Parse(test) + command.SkipFlagParsing = c.skipFlagParsing - c := NewContext(app, set, nil) + err := command.Run(context) - command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) {}, - SkipFlagParsing: true, + expect(t, err, c.expectedErr) + expect(t, []string(context.Args()), c.testArgs) } - err := command.Run(c) - expect(t, err, nil) - - expect(t, []string(c.Args()), []string{"blah", "-break"}) } From 7b94fd3aad1db29bdf260402c6edaa6e5488bfe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Haugen?= Date: Sat, 28 Nov 2015 18:26:10 +0100 Subject: [PATCH 08/24] test: avoid output from "go test" when tests pass Some tests where printing to os.Stdout as a side effect even if the output was not used/checked in the test. --- app_test.go | 2 ++ command_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app_test.go b/app_test.go index 28d8e0f..59fa75a 100644 --- a/app_test.go +++ b/app_test.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io" + "io/ioutil" "os" "strings" "testing" @@ -556,6 +557,7 @@ func TestAppNoHelpFlag(t *testing.T) { HelpFlag = BoolFlag{} app := NewApp() + app.Writer = ioutil.Discard err := app.Run([]string{"test", "-h"}) if err != flag.ErrHelp { diff --git a/command_test.go b/command_test.go index dd9fc87..ac10652 100644 --- a/command_test.go +++ b/command_test.go @@ -3,6 +3,7 @@ package cli import ( "errors" "flag" + "io/ioutil" "testing" ) @@ -20,6 +21,7 @@ func TestCommandFlagParsing(t *testing.T) { for _, c := range cases { app := NewApp() + app.Writer = ioutil.Discard set := flag.NewFlagSet("test", 0) set.Parse(c.testArgs) From 4a8406ac8979d1df509f963e538a5b843fae53c1 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Sun, 13 Dec 2015 09:31:32 +1000 Subject: [PATCH 09/24] Run check completion before error checking Running check completion before error checking allows for completion of flags when no character has been typed yet --- app.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app.go b/app.go index 9a15c0c..0805fd6 100644 --- a/app.go +++ b/app.go @@ -126,6 +126,10 @@ func (a *App) Run(arguments []string) (err error) { } context := NewContext(a, set, nil) + if checkCompletions(context) { + return nil + } + if err != nil { fmt.Fprintln(a.Writer, "Incorrect Usage.") fmt.Fprintln(a.Writer) @@ -133,10 +137,6 @@ func (a *App) Run(arguments []string) (err error) { return err } - if checkCompletions(context) { - return nil - } - if !a.HideHelp && checkHelp(context) { ShowAppHelp(context) return nil @@ -233,6 +233,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { return nerr } + if checkCompletions(context) { + return nil + } + if err != nil { fmt.Fprintln(a.Writer, "Incorrect Usage.") fmt.Fprintln(a.Writer) @@ -240,10 +244,6 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { return err } - if checkCompletions(context) { - return nil - } - if len(a.Commands) > 0 { if checkSubcommandHelp(context) { return nil From f101a0001843491f80a9cf991cf1649598db9f57 Mon Sep 17 00:00:00 2001 From: Jille Timmermans Date: Tue, 15 Dec 2015 17:29:03 +0000 Subject: [PATCH 10/24] Export cli.Flag.GetName (previously cli.Flag.getName) --- context.go | 6 +++--- flag.go | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/context.go b/context.go index f541f41..0513d34 100644 --- a/context.go +++ b/context.go @@ -163,7 +163,7 @@ func (c *Context) GlobalIsSet(name string) bool { // Returns a slice of flag names used in this context. func (c *Context) FlagNames() (names []string) { for _, flag := range c.Command.Flags { - name := strings.Split(flag.getName(), ",")[0] + name := strings.Split(flag.GetName(), ",")[0] if name == "help" { continue } @@ -175,7 +175,7 @@ func (c *Context) FlagNames() (names []string) { // Returns a slice of global flag names used by the app. func (c *Context) GlobalFlagNames() (names []string) { for _, flag := range c.App.Flags { - name := strings.Split(flag.getName(), ",")[0] + name := strings.Split(flag.GetName(), ",")[0] if name == "help" || name == "version" { continue } @@ -360,7 +360,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { visited[f.Name] = true }) for _, f := range flags { - parts := strings.Split(f.getName(), ",") + parts := strings.Split(f.GetName(), ",") if len(parts) == 1 { continue } diff --git a/flag.go b/flag.go index 9b22d7f..49f3099 100644 --- a/flag.go +++ b/flag.go @@ -35,7 +35,7 @@ type Flag interface { fmt.Stringer // Apply Flag settings to the given flag set Apply(*flag.FlagSet) - getName() string + GetName() string } func flagSet(name string, flags []Flag) *flag.FlagSet { @@ -95,7 +95,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { }) } -func (f GenericFlag) getName() string { +func (f GenericFlag) GetName() string { return f.Name } @@ -159,7 +159,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { }) } -func (f StringSliceFlag) getName() string { +func (f StringSliceFlag) GetName() string { return f.Name } @@ -231,7 +231,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { }) } -func (f IntSliceFlag) getName() string { +func (f IntSliceFlag) GetName() string { return f.Name } @@ -273,7 +273,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { }) } -func (f BoolFlag) getName() string { +func (f BoolFlag) GetName() string { return f.Name } @@ -316,7 +316,7 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { }) } -func (f BoolTFlag) getName() string { +func (f BoolTFlag) GetName() string { return f.Name } @@ -364,7 +364,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { }) } -func (f StringFlag) getName() string { +func (f StringFlag) GetName() string { return f.Name } @@ -407,7 +407,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { }) } -func (f IntFlag) getName() string { +func (f IntFlag) GetName() string { return f.Name } @@ -450,7 +450,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { }) } -func (f DurationFlag) getName() string { +func (f DurationFlag) GetName() string { return f.Name } @@ -492,7 +492,7 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { }) } -func (f Float64Flag) getName() string { +func (f Float64Flag) GetName() string { return f.Name } From b0b9bd5dac340942e2f28cff01395f9f674ec3ae Mon Sep 17 00:00:00 2001 From: Yagnesh Mistry Date: Fri, 18 Dec 2015 23:28:32 +0530 Subject: [PATCH 11/24] use path.Base in Name & HelpName as default values --- app.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 0805fd6..2f992d0 100644 --- a/app.go +++ b/app.go @@ -5,13 +5,14 @@ import ( "io" "io/ioutil" "os" + "path" "time" ) // App is the main structure of a cli application. It is recomended that // an app be created with the cli.NewApp() function type App struct { - // The name of the program. Defaults to os.Args[0] + // The name of the program. Defaults to path.Base(os.Args[0]) Name string // Full name of command for help, defaults to Name HelpName string @@ -70,8 +71,8 @@ func compileTime() time.Time { // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. func NewApp() *App { return &App{ - Name: os.Args[0], - HelpName: os.Args[0], + Name: path.Base(os.Args[0]), + HelpName: path.Base(os.Args[0]), Usage: "A new cli application", Version: "0.0.0", BashComplete: DefaultAppComplete, From f90cd5664724a17d49d488c26c3b4676540e844a Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Fri, 25 Dec 2015 21:45:58 +0100 Subject: [PATCH 12/24] Handle Before and After of Command without handling it as subCommand if there is no subCommand. --- app_test.go | 5 +++++ command.go | 26 +++++++++++++++++++++++--- command_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/app_test.go b/app_test.go index 59fa75a..9a09405 100644 --- a/app_test.go +++ b/app_test.go @@ -942,6 +942,11 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Commands = []Command{ Command{ + Subcommands: []Command{ + Command{ + Name: "sub", + }, + }, Name: "bar", Before: func(c *Context) error { return fmt.Errorf("before error") }, After: func(c *Context) error { return fmt.Errorf("after error") }, diff --git a/command.go b/command.go index 824e77b..23eca5a 100644 --- a/command.go +++ b/command.go @@ -54,8 +54,8 @@ func (c Command) FullName() string { } // Invokes the command given the context, parses ctx.Args() to generate command-specific flags -func (c Command) Run(ctx *Context) error { - if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { +func (c Command) Run(ctx *Context) (err error) { + if len(c.Subcommands) > 0 { return c.startApp(ctx) } @@ -74,7 +74,6 @@ func (c Command) Run(ctx *Context) error { set := flagSet(c.Name, c.Flags) set.SetOutput(ioutil.Discard) - var err error if !c.SkipFlagParsing { firstFlagIndex := -1 terminatorIndex := -1 @@ -133,6 +132,27 @@ func (c Command) Run(ctx *Context) error { if checkCommandHelp(context, c.Name) { return nil } + + if c.After != nil { + defer func() { + afterErr := c.After(context) + if afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if c.Before != nil { + err := c.Before(context) + if err != nil { + return err + } + } + context.Command = c c.Action(context) return nil diff --git a/command_test.go b/command_test.go index ac10652..13ebccb 100644 --- a/command_test.go +++ b/command_test.go @@ -5,6 +5,8 @@ import ( "flag" "io/ioutil" "testing" + "fmt" + "strings" ) func TestCommandFlagParsing(t *testing.T) { @@ -43,3 +45,26 @@ func TestCommandFlagParsing(t *testing.T) { expect(t, []string(context.Args()), c.testArgs) } } + +func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + Command{ + Name: "bar", + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + if err == nil { + t.Fatalf("expected to recieve error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} From f3c8e07836a9d73d1183d8990c2017a589cd1eb3 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Fri, 25 Dec 2015 22:08:22 +0100 Subject: [PATCH 13/24] Also show help if App/Command Before produces error. --- app.go | 3 +++ command.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app.go b/app.go index 2f992d0..6884920 100644 --- a/app.go +++ b/app.go @@ -164,6 +164,9 @@ func (a *App) Run(arguments []string) (err error) { if a.Before != nil { err := a.Before(context) if err != nil { + fmt.Fprintln(a.Writer, err) + fmt.Fprintln(a.Writer) + ShowAppHelp(context) return err } } diff --git a/command.go b/command.go index 23eca5a..e42178e 100644 --- a/command.go +++ b/command.go @@ -149,6 +149,9 @@ func (c Command) Run(ctx *Context) (err error) { if c.Before != nil { err := c.Before(context) if err != nil { + fmt.Fprintln(ctx.App.Writer, err) + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) return err } } From 01fdb2cca9c904b2dc6f6125ddcba83e484e6105 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Wed, 20 Jan 2016 10:56:46 +0100 Subject: [PATCH 14/24] #315 fixed typo --- command_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command_test.go b/command_test.go index 13ebccb..50bd875 100644 --- a/command_test.go +++ b/command_test.go @@ -58,7 +58,7 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { err := app.Run([]string{"foo", "bar"}) if err == nil { - t.Fatalf("expected to recieve error from Run, got none") + t.Fatalf("expected to receive error from Run, got none") } if !strings.Contains(err.Error(), "before error") { From 54b6cca78e5a5193d358055b8d6585d6b2307d09 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Jan 2016 14:51:55 -0700 Subject: [PATCH 15/24] Remove panic from help. There is a panic in printHelp that can be trivially triggered when the shell closes os.Stdout. This happens, for example, when data is piped between a cli app and something else. See https://github.com/helm/helm/issues/387 --- help.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/help.go b/help.go index a246f63..ecb67c2 100644 --- a/help.go +++ b/help.go @@ -180,7 +180,9 @@ func printHelp(out io.Writer, templ string, data interface{}) { t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) err := t.Execute(w, data) if err != nil { - panic(err) + // If the writer is closed, t.Execute will fail, and there's nothing + // we can do to recover. We could send this to os.Stderr if we need. + return } w.Flush() } From 82ddbd9a0748a666ee92c658532fd669aa242498 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Fri, 22 Jan 2016 15:08:27 +0100 Subject: [PATCH 16/24] * Improve GenericFlag.String() by suppressing empty "" for GenericFlags on nil or empty Generic.String() * Cleanup StringFlag.String() * Add os specific envHint handling for Windows (%ENV_VAR% instead of $ENV_VAR on posix systems) --- flag.go | 38 ++++++++++++++++++++++++++--------- flag_test.go | 57 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/flag.go b/flag.go index 49f3099..9cf562d 100644 --- a/flag.go +++ b/flag.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" "time" + "runtime" ) // This flag enables bash-completion for all commands and subcommands @@ -73,7 +74,18 @@ type GenericFlag struct { // help text to the user (uses the String() method of the generic flag to show // the value) func (f GenericFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage)) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) +} + +func (f GenericFlag) FormatValueHelp() string { + if f.Value == nil { + return "" + } + s := f.Value.String() + if len(s) == 0 { + return "" + } + return fmt.Sprintf("\"%s\"", s) } // Apply takes the flagset and calls Set on the generic flag with the value @@ -331,16 +343,16 @@ type StringFlag struct { // String returns the usage func (f StringFlag) String() string { - var fmtString string - fmtString = "%s %v\t%v" + return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) +} - if len(f.Value) > 0 { - fmtString = "%s \"%v\"\t%v" +func (f StringFlag) FormatValueHelp() string { + s := f.Value + if len(s) == 0 { + return "" } else { - fmtString = "%s %v\t%v" + return fmt.Sprintf("\"%s\"", s) } - - return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) } // Apply populates the flag given the flag set and environment @@ -521,7 +533,15 @@ func prefixedNames(fullName string) (prefixed string) { func withEnvHint(envVar, str string) string { envText := "" if envVar != "" { - envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $")) + prefix := "$" + suffix := "" + sep := ", $" + if runtime.GOOS == "windows" { + prefix = "%" + suffix = "%" + sep = "%, %" + } + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) } return str + envText } diff --git a/flag_test.go b/flag_test.go index 4462d3f..3caa70a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -6,6 +6,7 @@ import ( "reflect" "strings" "testing" + "runtime" ) var boolFlagTests = []struct { @@ -58,8 +59,12 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_FOO]") { - t.Errorf("%s does not end with [$APP_FOO]", output) + expectedSuffix := " [$APP_FOO]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_FOO%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } @@ -110,8 +115,12 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_QWWX]") { - t.Errorf("%q does not end with [$APP_QWWX]", output) + expectedSuffix := " [$APP_QWWX]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_QWWX%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with" + expectedSuffix, output) } } } @@ -143,8 +152,12 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_BAR]") { - t.Errorf("%s does not end with [$APP_BAR]", output) + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } @@ -176,8 +189,12 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_BAR]") { - t.Errorf("%s does not end with [$APP_BAR]", output) + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } @@ -216,8 +233,12 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_SMURF]") { - t.Errorf("%q does not end with [$APP_SMURF]", output) + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with" + expectedSuffix, output) } } } @@ -249,8 +270,12 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_BAZ]") { - t.Errorf("%s does not end with [$APP_BAZ]", output) + expectedSuffix := " [$APP_BAZ]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAZ%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } @@ -283,8 +308,12 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_ZAP]") { - t.Errorf("%s does not end with [$APP_ZAP]", output) + expectedSuffix := " [$APP_ZAP]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_ZAP%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } From 09e2c89597afd31cb88f66c404cd7fc0b783238b Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 12:01:49 +0100 Subject: [PATCH 17/24] * Changed the way how to return the result. Because of strange ci failure --- flag.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flag.go b/flag.go index 9cf562d..c8fb68b 100644 --- a/flag.go +++ b/flag.go @@ -350,9 +350,8 @@ func (f StringFlag) FormatValueHelp() string { s := f.Value if len(s) == 0 { return "" - } else { - return fmt.Sprintf("\"%s\"", s) } + return fmt.Sprintf("\"%s\"", s) } // Apply populates the flag given the flag set and environment From 7319f042e4ef05856815f00ee070f567816e98ed Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 12:47:24 +0100 Subject: [PATCH 18/24] * Added ability to customize usage error messages --- app.go | 30 +++++++++++++++++------- app_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ command.go | 17 ++++++++++---- command_test.go | 27 ++++++++++++++++++++++ 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/app.go b/app.go index 6884920..be35114 100644 --- a/app.go +++ b/app.go @@ -44,6 +44,10 @@ type App struct { Action func(context *Context) // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) + // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to manipulate the original error in another. + // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error, isSubcommand bool) error // Compilation date Compiled time.Time // List of all authors who contributed @@ -132,10 +136,15 @@ func (a *App) Run(arguments []string) (err error) { } if err != nil { - fmt.Fprintln(a.Writer, "Incorrect Usage.") - fmt.Fprintln(a.Writer) - ShowAppHelp(context) - return err + if a.OnUsageError != nil { + err := a.OnUsageError(context, err, false) + return err + } else { + fmt.Fprintln(a.Writer, "Incorrect Usage.") + fmt.Fprintln(a.Writer) + ShowAppHelp(context) + return err + } } if !a.HideHelp && checkHelp(context) { @@ -242,10 +251,15 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if err != nil { - fmt.Fprintln(a.Writer, "Incorrect Usage.") - fmt.Fprintln(a.Writer) - ShowSubcommandHelp(context) - return err + if a.OnUsageError != nil { + err = a.OnUsageError(context, err, true) + return err + } else { + fmt.Fprintln(a.Writer, "Incorrect Usage.") + fmt.Fprintln(a.Writer) + ShowSubcommandHelp(context) + return err + } } if len(a.Commands) > 0 { diff --git a/app_test.go b/app_test.go index 9a09405..fa8e2b1 100644 --- a/app_test.go +++ b/app_test.go @@ -9,6 +9,7 @@ import ( "os" "strings" "testing" +"errors" ) func ExampleApp_Run() { @@ -965,3 +966,63 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } } + +func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect no subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + Command{ + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + Command{ + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong", "bar"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} diff --git a/command.go b/command.go index e42178e..e448e2a 100644 --- a/command.go +++ b/command.go @@ -30,6 +30,10 @@ type Command struct { After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) + // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to manipulate the original error in another. + // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error) error // List of child commands Subcommands []Command // List of flags to parse @@ -110,10 +114,15 @@ func (c Command) Run(ctx *Context) (err error) { } if err != nil { - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return err + if c.OnUsageError != nil { + err := c.OnUsageError(ctx, err) + return err + } else { + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err + } } nerr := normalizeFlags(c.Flags, set) diff --git a/command_test.go b/command_test.go index 50bd875..87ede75 100644 --- a/command_test.go +++ b/command_test.go @@ -68,3 +68,30 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } } + +func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + Command{ + Name: "bar", + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} From cb7a7c56eea7f9210e93e75ac149b90c357b7f97 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 14:50:25 +0100 Subject: [PATCH 19/24] * Fixed typos --- app.go | 6 +++--- command.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index be35114..d4d278f 100644 --- a/app.go +++ b/app.go @@ -44,9 +44,9 @@ type App struct { Action func(context *Context) // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) - // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to manipulate the original error in another. - // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error, isSubcommand bool) error // Compilation date Compiled time.Time diff --git a/command.go b/command.go index e448e2a..b2d797f 100644 --- a/command.go +++ b/command.go @@ -30,9 +30,9 @@ type Command struct { After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) - // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to manipulate the original error in another. - // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error) error // List of child commands Subcommands []Command From bb4e78eb6a9155dc7de6630f16551f71e0ce5ac4 Mon Sep 17 00:00:00 2001 From: Andreas Kupries Date: Tue, 26 Jan 2016 15:34:53 -0800 Subject: [PATCH 20/24] Fixed mishandling of a "-"(dash)-argument causing reordering of cli non-flag arguments. Added test demonstrating issue (PASS with fix, FAIL without). --- app_test.go | 18 ++++++++++++++++++ command.go | 3 +++ 2 files changed, 21 insertions(+) diff --git a/app_test.go b/app_test.go index 9a09405..28f96a6 100644 --- a/app_test.go +++ b/app_test.go @@ -249,6 +249,24 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { expect(t, args[2], "--notARealFlag") } +func TestApp_CommandWithDash(t *testing.T) { + var args []string + + app := NewApp() + command := Command{ + Name: "cmd", + Action: func(c *Context) { + args = c.Args() + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "-"}) + + expect(t, args[0], "my-arg") + expect(t, args[1], "-") +} + func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { var args []string diff --git a/command.go b/command.go index e42178e..aebb4bb 100644 --- a/command.go +++ b/command.go @@ -81,6 +81,9 @@ func (c Command) Run(ctx *Context) (err error) { if arg == "--" { terminatorIndex = index break + } else if arg == "-" { + // Do nothing. A dash alone is not really a flag. + continue } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { firstFlagIndex = index } From 918b268473e8e81aa8bbea2b41fa361213cf2711 Mon Sep 17 00:00:00 2001 From: Timothy Cyrus Date: Thu, 28 Jan 2016 20:42:14 -0500 Subject: [PATCH 21/24] Update README.md Replace PNG Badge With SVG and other minor fixes --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 26a1838..ae0a4ca 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ [![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) -[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) +[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) # cli.go + `cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. ## Overview + Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. **This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive! ## Installation + Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). To install `cli.go`, simply run: @@ -24,6 +27,7 @@ export PATH=$PATH:$GOPATH/bin ``` ## Getting Started + One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. ``` go @@ -123,6 +127,7 @@ GLOBAL OPTIONS ``` ### Arguments + You can lookup arguments by calling the `Args` function on `cli.Context`. ``` go @@ -134,7 +139,9 @@ app.Action = func(c *cli.Context) { ``` ### Flags + Setting and querying flags is simple. + ``` go ... app.Flags = []cli.Flag { @@ -159,6 +166,7 @@ app.Action = func(c *cli.Context) { ``` You can also set a destination variable for a flag, to which the content will be scanned. + ``` go ... var language string @@ -233,6 +241,7 @@ app.Flags = []cli.Flag { ### Subcommands Subcommands can be defined for a more git-like command line app. + ```go ... app.Commands = []cli.Command{ @@ -283,6 +292,7 @@ You can enable completion commands by setting the `EnableBashCompletion` flag on the `App` object. By default, this setting will only auto-complete to show an app's subcommands, but you can write your own completion methods for the App or its subcommands. + ```go ... var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} @@ -325,8 +335,8 @@ automatically install it there if you are distributing a package). Don't forget to source the file to make it active in the current shell. ``` - sudo cp src/bash_autocomplete /etc/bash_completion.d/ - source /etc/bash_completion.d/ +sudo cp src/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ ``` Alternatively, you can just document that users should source the generic @@ -334,6 +344,7 @@ Alternatively, you can just document that users should source the generic to the name of their program (as above). ## Contribution Guidelines + Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. From 6f6e8caf6c31a0505991cfa6983fbd9f75f564fa Mon Sep 17 00:00:00 2001 From: leonardyp Date: Thu, 4 Feb 2016 15:25:41 +0800 Subject: [PATCH 22/24] Repeat context statement because of a is a pointer performance optimization gofmt code --- app.go | 17 ++++++----------- command.go | 9 +++------ command_test.go | 4 ++-- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/app.go b/app.go index 6884920..d3db6ab 100644 --- a/app.go +++ b/app.go @@ -119,21 +119,19 @@ func (a *App) Run(arguments []string) (err error) { set.SetOutput(ioutil.Discard) err = set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) + context := NewContext(a, set, nil) if nerr != nil { fmt.Fprintln(a.Writer, nerr) - context := NewContext(a, set, nil) ShowAppHelp(context) return nerr } - context := NewContext(a, set, nil) if checkCompletions(context) { return nil } if err != nil { - fmt.Fprintln(a.Writer, "Incorrect Usage.") - fmt.Fprintln(a.Writer) + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowAppHelp(context) return err } @@ -150,8 +148,7 @@ func (a *App) Run(arguments []string) (err error) { if a.After != nil { defer func() { - afterErr := a.After(context) - if afterErr != nil { + if afterErr := a.After(context); afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) } else { @@ -162,10 +159,9 @@ func (a *App) Run(arguments []string) (err error) { } if a.Before != nil { - err := a.Before(context) + err = a.Before(context) if err != nil { - fmt.Fprintln(a.Writer, err) - fmt.Fprintln(a.Writer) + fmt.Fprintf(a.Writer, "%v\n\n", err) ShowAppHelp(context) return err } @@ -242,8 +238,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if err != nil { - fmt.Fprintln(a.Writer, "Incorrect Usage.") - fmt.Fprintln(a.Writer) + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index aebb4bb..077d8e2 100644 --- a/command.go +++ b/command.go @@ -192,7 +192,7 @@ func (c Command) startApp(ctx *Context) error { if c.HelpName == "" { app.HelpName = c.HelpName } else { - app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + app.HelpName = app.Name } if c.Description != "" { @@ -231,12 +231,9 @@ func (c Command) startApp(ctx *Context) error { app.Action = helpSubcommand.Action } - var newCmds []Command - for _, cc := range app.Commands { - cc.commandNamePath = []string{c.Name, cc.Name} - newCmds = append(newCmds, cc) + for index, cc := range app.Commands { + app.Commands[index].commandNamePath = []string{c.Name, cc.Name} } - app.Commands = newCmds return app.RunAsSubcommand(ctx) } diff --git a/command_test.go b/command_test.go index 50bd875..536392f 100644 --- a/command_test.go +++ b/command_test.go @@ -3,10 +3,10 @@ package cli import ( "errors" "flag" - "io/ioutil" - "testing" "fmt" + "io/ioutil" "strings" + "testing" ) func TestCommandFlagParsing(t *testing.T) { From c462071a52c9343b4174380c1ae0eb0306ebe531 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 12:47:24 +0100 Subject: [PATCH 23/24] * Added ability to customize usage error messages --- app.go | 26 ++++++++++++++++----- app_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ command.go | 17 ++++++++++---- command_test.go | 27 ++++++++++++++++++++++ 4 files changed, 121 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index d3db6ab..857f422 100644 --- a/app.go +++ b/app.go @@ -44,6 +44,10 @@ type App struct { Action func(context *Context) // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) + // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to manipulate the original error in another. + // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error, isSubcommand bool) error // Compilation date Compiled time.Time // List of all authors who contributed @@ -131,9 +135,14 @@ func (a *App) Run(arguments []string) (err error) { } if err != nil { - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") - ShowAppHelp(context) - return err + if a.OnUsageError != nil { + err := a.OnUsageError(context, err, false) + return err + } else { + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + ShowAppHelp(context) + return err + } } if !a.HideHelp && checkHelp(context) { @@ -238,9 +247,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if err != nil { - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") - ShowSubcommandHelp(context) - return err + if a.OnUsageError != nil { + err = a.OnUsageError(context, err, true) + return err + } else { + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + ShowSubcommandHelp(context) + return err + } } if len(a.Commands) > 0 { diff --git a/app_test.go b/app_test.go index 28f96a6..09032d1 100644 --- a/app_test.go +++ b/app_test.go @@ -9,6 +9,7 @@ import ( "os" "strings" "testing" +"errors" ) func ExampleApp_Run() { @@ -983,3 +984,63 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } } + +func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect no subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + Command{ + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + Command{ + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong", "bar"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} diff --git a/command.go b/command.go index 077d8e2..980094f 100644 --- a/command.go +++ b/command.go @@ -30,6 +30,10 @@ type Command struct { After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) + // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to manipulate the original error in another. + // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error) error // List of child commands Subcommands []Command // List of flags to parse @@ -113,10 +117,15 @@ func (c Command) Run(ctx *Context) (err error) { } if err != nil { - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return err + if c.OnUsageError != nil { + err := c.OnUsageError(ctx, err) + return err + } else { + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err + } } nerr := normalizeFlags(c.Flags, set) diff --git a/command_test.go b/command_test.go index 536392f..827da1d 100644 --- a/command_test.go +++ b/command_test.go @@ -68,3 +68,30 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } } + +func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + Command{ + Name: "bar", + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} From 66c17420121916c62683c3d60b2131eacd8aff48 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 14:50:25 +0100 Subject: [PATCH 24/24] * Fixed typos --- app.go | 6 +++--- command.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index 857f422..634c825 100644 --- a/app.go +++ b/app.go @@ -44,9 +44,9 @@ type App struct { Action func(context *Context) // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) - // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to manipulate the original error in another. - // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error, isSubcommand bool) error // Compilation date Compiled time.Time diff --git a/command.go b/command.go index 980094f..f987b52 100644 --- a/command.go +++ b/command.go @@ -30,9 +30,9 @@ type Command struct { After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) - // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to manipulate the original error in another. - // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error) error // List of child commands Subcommands []Command