From 60e3dcaf6dd45d08ad1974c70ff8c93242a5c635 Mon Sep 17 00:00:00 2001 From: John Hopper Date: Thu, 12 Jun 2014 00:39:13 -0700 Subject: [PATCH 01/21] Allow a writer to be set that represents Stdout so that redirection of App output may occur. --- app.go | 36 +++++++++++++++++++++++++++++------- app_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ command.go | 11 ++++++----- help.go | 26 ++++++++------------------ 4 files changed, 87 insertions(+), 30 deletions(-) diff --git a/app.go b/app.go index 4efba5e..0125dae 100644 --- a/app.go +++ b/app.go @@ -2,8 +2,11 @@ package cli import ( "fmt" + "io" "io/ioutil" "os" + "text/tabwriter" + "text/template" "time" ) @@ -37,6 +40,8 @@ type App struct { Author string // Author e-mail Email string + // Stdout writer to write output to + Stdout io.Writer } // Tries to find out when this binary was compiled. @@ -60,11 +65,28 @@ func NewApp() *App { Compiled: compileTime(), Author: "Author", Email: "unknown@email", + Stdout: os.Stdout, } } // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination func (a *App) Run(arguments []string) error { + if HelpPrinter == nil { + defer func() { + HelpPrinter = nil + }() + + HelpPrinter = func(templ string, data interface{}) { + w := tabwriter.NewWriter(a.Stdout, 0, 8, 1, '\t', 0) + t := template.Must(template.New("help").Parse(templ)) + err := t.Execute(w, data) + if err != nil { + panic(err) + } + w.Flush() + } + } + // append help to commands if a.Command(helpCommand.Name) == nil { a.Commands = append(a.Commands, helpCommand) @@ -83,18 +105,18 @@ func (a *App) Run(arguments []string) error { err := set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) if nerr != nil { - fmt.Println(nerr) + io.WriteString(a.Stdout, fmt.Sprintln(nerr)) context := NewContext(a, set, set) ShowAppHelp(context) - fmt.Println("") + io.WriteString(a.Stdout, fmt.Sprintln("")) return nerr } context := NewContext(a, set, set) if err != nil { - fmt.Printf("Incorrect Usage.\n\n") + io.WriteString(a.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) ShowAppHelp(context) - fmt.Println("") + io.WriteString(a.Stdout, fmt.Sprintln("")) return err } @@ -154,18 +176,18 @@ func (a *App) RunAsSubcommand(ctx *Context) error { context := NewContext(a, set, set) if nerr != nil { - fmt.Println(nerr) + io.WriteString(a.Stdout, fmt.Sprintln(nerr)) if len(a.Commands) > 0 { ShowSubcommandHelp(context) } else { ShowCommandHelp(ctx, context.Args().First()) } - fmt.Println("") + io.WriteString(a.Stdout, fmt.Sprintln("")) return nerr } if err != nil { - fmt.Printf("Incorrect Usage.\n\n") + io.WriteString(a.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) ShowSubcommandHelp(context) return err } diff --git a/app_test.go b/app_test.go index 0b9e154..e8937e1 100644 --- a/app_test.go +++ b/app_test.go @@ -262,6 +262,50 @@ func TestApp_ParseSliceFlags(t *testing.T) { } } +func TestApp_DefaultStdout(t *testing.T) { + app := cli.NewApp() + + if app.Stdout != os.Stdout { + t.Error("Default output writer not set.") + } +} + +type fakeWriter struct { + written []byte +} + +func (fw *fakeWriter) Write(p []byte) (n int, err error) { + if fw.written == nil { + fw.written = p + } else { + fw.written = append(fw.written, p...) + } + + return len(p), nil +} + +func (fw *fakeWriter) GetWritten() (b []byte) { + return fw.written +} + +func TestApp_SetStdout(t *testing.T) { + mockWriter := &fakeWriter{} + + app := cli.NewApp() + app.Name = "test" + app.Stdout = mockWriter + + err := app.Run([]string{"help"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if len(mockWriter.written) == 0 { + t.Error("App did not write output to desired writer.") + } +} + func TestApp_BeforeFunc(t *testing.T) { beforeRun, subcommandRun := false, false beforeError := fmt.Errorf("fail") diff --git a/command.go b/command.go index 9d8fff4..3d470c8 100644 --- a/command.go +++ b/command.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "io" "io/ioutil" "strings" ) @@ -70,18 +71,18 @@ func (c Command) Run(ctx *Context) error { } if err != nil { - fmt.Printf("Incorrect Usage.\n\n") + io.WriteString(ctx.App.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) ShowCommandHelp(ctx, c.Name) - fmt.Println("") + io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) return err } nerr := normalizeFlags(c.Flags, set) if nerr != nil { - fmt.Println(nerr) - fmt.Println("") + io.WriteString(ctx.App.Stdout, fmt.Sprintln(nerr)) + io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) ShowCommandHelp(ctx, c.Name) - fmt.Println("") + io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) return nerr } context := NewContext(ctx.App, set, ctx.globalSet) diff --git a/help.go b/help.go index 7c04005..ae60a13 100644 --- a/help.go +++ b/help.go @@ -2,9 +2,7 @@ package cli import ( "fmt" - "os" - "text/tabwriter" - "text/template" + "io" ) // The text template for the Default help topic. @@ -90,7 +88,9 @@ var helpSubcommand = Command{ } // Prints help for the App -var HelpPrinter = printHelp +type helpPrinter func(templ string, data interface{}) + +var HelpPrinter helpPrinter = nil func ShowAppHelp(c *Context) { HelpPrinter(AppHelpTemplate, c.App) @@ -99,9 +99,9 @@ func ShowAppHelp(c *Context) { // Prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { for _, command := range c.App.Commands { - fmt.Println(command.Name) + io.WriteString(c.App.Stdout, fmt.Sprintln(command.Name)) if command.ShortName != "" { - fmt.Println(command.ShortName) + io.WriteString(c.App.Stdout, fmt.Sprintln(command.ShortName)) } } } @@ -118,7 +118,7 @@ func ShowCommandHelp(c *Context, command string) { if c.App.CommandNotFound != nil { c.App.CommandNotFound(c, command) } else { - fmt.Printf("No help topic for '%v'\n", command) + io.WriteString(c.App.Stdout, fmt.Sprintf("No help topic for '%v'\n", command)) } } @@ -129,7 +129,7 @@ func ShowSubcommandHelp(c *Context) { // Prints the version number of the App func ShowVersion(c *Context) { - fmt.Printf("%v version %v\n", c.App.Name, c.App.Version) + io.WriteString(c.App.Stdout, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) } // Prints the lists of commands within a given context @@ -148,16 +148,6 @@ func ShowCommandCompletions(ctx *Context, command string) { } } -func printHelp(templ string, data interface{}) { - w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) - t := template.Must(template.New("help").Parse(templ)) - err := t.Execute(w, data) - if err != nil { - panic(err) - } - w.Flush() -} - func checkVersion(c *Context) bool { if c.GlobalBool("version") { ShowVersion(c) From 4db56687faebc35a767fa36dbfe9b223865241e0 Mon Sep 17 00:00:00 2001 From: Nima Jahanshahi Date: Mon, 11 Aug 2014 11:27:26 +0430 Subject: [PATCH 02/21] Fixed ShowSubcommandHelp ShowSubcommandHelp should show help for the subcommand not the app --- help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.go b/help.go index 5020cb6..01c86e1 100644 --- a/help.go +++ b/help.go @@ -131,7 +131,7 @@ func ShowCommandHelp(c *Context, command string) { // Prints help for the given subcommand func ShowSubcommandHelp(c *Context) { - HelpPrinter(SubcommandHelpTemplate, c.App) + ShowCommandHelp(c, c.Command.Name) } // Prints the version number of the App From 78d497e4cfaed53897f4194671c076077885d136 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 22 Sep 2014 23:24:08 -0400 Subject: [PATCH 03/21] Adding support for multiple env var "cascade" --- flag.go | 111 ++++++++++++++++++++++----------- flag_test.go | 170 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 237 insertions(+), 44 deletions(-) diff --git a/flag.go b/flag.go index b30bca3..ddd6ef8 100644 --- a/flag.go +++ b/flag.go @@ -74,8 +74,12 @@ func (f GenericFlag) String() string { func (f GenericFlag) Apply(set *flag.FlagSet) { val := f.Value if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - val.Set(envVal) + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + val.Set(envVal) + break + } } } @@ -118,12 +122,17 @@ func (f StringSliceFlag) String() string { func (f StringSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - newVal := &StringSlice{} - for _, s := range strings.Split(envVal, ",") { - newVal.Set(s) + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + newVal.Set(s) + } + f.Value = newVal + break } - f.Value = newVal } } @@ -172,15 +181,20 @@ func (f IntSliceFlag) String() string { func (f IntSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - newVal := &IntSlice{} - for _, s := range strings.Split(envVal, ",") { - err := newVal.Set(s) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + } } + f.Value = newVal + break } - f.Value = newVal } } @@ -206,10 +220,14 @@ func (f BoolFlag) String() string { func (f BoolFlag) Apply(set *flag.FlagSet) { val := false if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - envValBool, err := strconv.ParseBool(envVal) - if err == nil { - val = envValBool + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + } + break } } } @@ -236,10 +254,14 @@ func (f BoolTFlag) String() string { func (f BoolTFlag) Apply(set *flag.FlagSet) { val := true if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - envValBool, err := strconv.ParseBool(envVal) - if err == nil { - val = envValBool + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + break + } } } } @@ -275,8 +297,12 @@ func (f StringFlag) String() string { func (f StringFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - f.Value = envVal + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + f.Value = envVal + break + } } } @@ -302,10 +328,14 @@ func (f IntFlag) String() string { func (f IntFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - envValInt, err := strconv.ParseUint(envVal, 10, 64) - if err == nil { - f.Value = int(envValInt) + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseUint(envVal, 10, 64) + if err == nil { + f.Value = int(envValInt) + break + } } } } @@ -332,10 +362,14 @@ func (f DurationFlag) String() string { func (f DurationFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - envValDuration, err := time.ParseDuration(envVal) - if err == nil { - f.Value = envValDuration + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValDuration, err := time.ParseDuration(envVal) + if err == nil { + f.Value = envValDuration + break + } } } } @@ -362,10 +396,13 @@ func (f Float64Flag) String() string { func (f Float64Flag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { - if envVal := os.Getenv(f.EnvVar); envVal != "" { - envValFloat, err := strconv.ParseFloat(envVal, 10) - if err == nil { - f.Value = float64(envValFloat) + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err == nil { + f.Value = float64(envValFloat) + } } } } @@ -404,7 +441,7 @@ func prefixedNames(fullName string) (prefixed string) { func withEnvHint(envVar, str string) string { envText := "" if envVar != "" { - envText = fmt.Sprintf(" [$%s]", envVar) + envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $")) } return str + envText } diff --git a/flag_test.go b/flag_test.go index bc5059c..4f0ba55 100644 --- a/flag_test.go +++ b/flag_test.go @@ -54,7 +54,7 @@ func TestStringFlagHelpOutput(t *testing.T) { } func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { - + os.Clearenv() os.Setenv("APP_FOO", "derp") for _, test := range stringFlagTests { flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} @@ -106,7 +106,7 @@ func TestStringSliceFlagHelpOutput(t *testing.T) { } func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { - + os.Clearenv() os.Setenv("APP_QWWX", "11,4") for _, test := range stringSliceFlagTests { flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} @@ -139,7 +139,7 @@ func TestIntFlagHelpOutput(t *testing.T) { } func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { - + os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range intFlagTests { flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"} @@ -172,7 +172,7 @@ func TestDurationFlagHelpOutput(t *testing.T) { } func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { - + os.Clearenv() os.Setenv("APP_BAR", "2h3m6s") for _, test := range durationFlagTests { flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"} @@ -212,7 +212,7 @@ func TestIntSliceFlagHelpOutput(t *testing.T) { } func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { - + os.Clearenv() os.Setenv("APP_SMURF", "42,3") for _, test := range intSliceFlagTests { flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} @@ -245,7 +245,7 @@ func TestFloat64FlagHelpOutput(t *testing.T) { } func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { - + os.Clearenv() os.Setenv("APP_BAZ", "99.4") for _, test := range float64FlagTests { flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} @@ -280,7 +280,7 @@ func TestGenericFlagHelpOutput(t *testing.T) { } func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { - + os.Clearenv() os.Setenv("APP_ZAP", "3") for _, test := range genericFlagTests { flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} @@ -309,6 +309,7 @@ func TestParseMultiString(t *testing.T) { } func TestParseMultiStringFromEnv(t *testing.T) { + os.Clearenv() os.Setenv("APP_COUNT", "20") (&cli.App{ Flags: []cli.Flag{ @@ -325,6 +326,24 @@ func TestParseMultiStringFromEnv(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiStringFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_COUNT", "20") + (&cli.App{ + Flags: []cli.Flag{ + cli.StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, + }, + Action: func(ctx *cli.Context) { + if ctx.String("count") != "20" { + t.Errorf("main name not set") + } + if ctx.String("c") != "20" { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiStringSlice(t *testing.T) { (&cli.App{ Flags: []cli.Flag{ @@ -342,6 +361,7 @@ func TestParseMultiStringSlice(t *testing.T) { } func TestParseMultiStringSliceFromEnv(t *testing.T) { + os.Clearenv() os.Setenv("APP_INTERVALS", "20,30,40") (&cli.App{ @@ -359,6 +379,25 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&cli.App{ + Flags: []cli.Flag{ + cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiInt(t *testing.T) { a := cli.App{ Flags: []cli.Flag{ @@ -377,6 +416,7 @@ func TestParseMultiInt(t *testing.T) { } func TestParseMultiIntFromEnv(t *testing.T) { + os.Clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "10") a := cli.App{ Flags: []cli.Flag{ @@ -394,6 +434,25 @@ func TestParseMultiIntFromEnv(t *testing.T) { a.Run([]string{"run"}) } +func TestParseMultiIntFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "10") + a := cli.App{ + Flags: []cli.Flag{ + cli.IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *cli.Context) { + if ctx.Int("timeout") != 10 { + t.Errorf("main name not set") + } + if ctx.Int("t") != 10 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run"}) +} + func TestParseMultiIntSlice(t *testing.T) { (&cli.App{ Flags: []cli.Flag{ @@ -411,6 +470,7 @@ func TestParseMultiIntSlice(t *testing.T) { } func TestParseMultiIntSliceFromEnv(t *testing.T) { + os.Clearenv() os.Setenv("APP_INTERVALS", "20,30,40") (&cli.App{ @@ -428,6 +488,25 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&cli.App{ + Flags: []cli.Flag{ + cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiFloat64(t *testing.T) { a := cli.App{ Flags: []cli.Flag{ @@ -446,6 +525,7 @@ func TestParseMultiFloat64(t *testing.T) { } func TestParseMultiFloat64FromEnv(t *testing.T) { + os.Clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := cli.App{ Flags: []cli.Flag{ @@ -463,6 +543,25 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { a.Run([]string{"run"}) } +func TestParseMultiFloat64FromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := cli.App{ + Flags: []cli.Flag{ + cli.Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *cli.Context) { + if ctx.Float64("timeout") != 15.5 { + t.Errorf("main name not set") + } + if ctx.Float64("t") != 15.5 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run"}) +} + func TestParseMultiBool(t *testing.T) { a := cli.App{ Flags: []cli.Flag{ @@ -481,6 +580,7 @@ func TestParseMultiBool(t *testing.T) { } func TestParseMultiBoolFromEnv(t *testing.T) { + os.Clearenv() os.Setenv("APP_DEBUG", "1") a := cli.App{ Flags: []cli.Flag{ @@ -498,6 +598,25 @@ func TestParseMultiBoolFromEnv(t *testing.T) { a.Run([]string{"run"}) } +func TestParseMultiBoolFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "1") + a := cli.App{ + Flags: []cli.Flag{ + cli.BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + }, + Action: func(ctx *cli.Context) { + if ctx.Bool("debug") != true { + t.Errorf("main name not set from env") + } + if ctx.Bool("d") != true { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + func TestParseMultiBoolT(t *testing.T) { a := cli.App{ Flags: []cli.Flag{ @@ -516,6 +635,7 @@ func TestParseMultiBoolT(t *testing.T) { } func TestParseMultiBoolTFromEnv(t *testing.T) { + os.Clearenv() os.Setenv("APP_DEBUG", "0") a := cli.App{ Flags: []cli.Flag{ @@ -533,6 +653,25 @@ func TestParseMultiBoolTFromEnv(t *testing.T) { a.Run([]string{"run"}) } +func TestParseMultiBoolTFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "0") + a := cli.App{ + Flags: []cli.Flag{ + cli.BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + }, + Action: func(ctx *cli.Context) { + if ctx.BoolT("debug") != false { + t.Errorf("main name not set from env") + } + if ctx.BoolT("d") != false { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + type Parser [2]string func (p *Parser) Set(value string) error { @@ -569,6 +708,7 @@ func TestParseGeneric(t *testing.T) { } func TestParseGenericFromEnv(t *testing.T) { + os.Clearenv() os.Setenv("APP_SERVE", "20,30") a := cli.App{ Flags: []cli.Flag{ @@ -585,3 +725,19 @@ func TestParseGenericFromEnv(t *testing.T) { } a.Run([]string{"run"}) } + +func TestParseGenericFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_FOO", "99,2000") + a := cli.App{ + Flags: []cli.Flag{ + cli.GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { + t.Errorf("value not set from env") + } + }, + } + a.Run([]string{"run"}) +} From 44efc2952d4de377141c058c334e8a379967ec48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Castan=CC=83e=CC=81?= Date: Tue, 18 Nov 2014 23:44:21 +0100 Subject: [PATCH 04/21] Added Before method to command and app --- app.go | 23 ++++++++++++++---- app_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++- command.go | 6 ++++- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index f4c4af8..a125afc 100644 --- a/app.go +++ b/app.go @@ -31,6 +31,9 @@ type App struct { // An action to execute before any subcommands are run, but after the context is ready // If a non-nil error is returned, no subcommands are run Before func(context *Context) error + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run regardless of Because() result + After func(context *Context) error // The action to execute when no subcommands are specified Action func(context *Context) // Execute this function if the proper command cannot be found @@ -66,7 +69,7 @@ func NewApp() *App { } // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination -func (a *App) Run(arguments []string) error { +func (a *App) Run(arguments []string) (err error) { // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) @@ -85,7 +88,7 @@ func (a *App) Run(arguments []string) error { // parse flags set := flagSet(a.Name, a.Flags) set.SetOutput(ioutil.Discard) - err := set.Parse(arguments[1:]) + err = set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) if nerr != nil { fmt.Println(nerr) @@ -115,6 +118,12 @@ func (a *App) Run(arguments []string) error { return nil } + if a.After != nil { + defer func() { + err = a.After(context) + }() + } + if a.Before != nil { err := a.Before(context) if err != nil { @@ -145,7 +154,7 @@ func (a *App) RunAndExitOnError() { } // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags -func (a *App) RunAsSubcommand(ctx *Context) error { +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 { @@ -162,7 +171,7 @@ func (a *App) RunAsSubcommand(ctx *Context) error { // parse flags set := flagSet(a.Name, a.Flags) set.SetOutput(ioutil.Discard) - err := set.Parse(ctx.Args().Tail()) + err = set.Parse(ctx.Args().Tail()) nerr := normalizeFlags(a.Flags, set) context := NewContext(a, set, ctx.globalSet) @@ -197,6 +206,12 @@ func (a *App) RunAsSubcommand(ctx *Context) error { } } + if a.After != nil { + defer func() { + err = a.After(context) + }() + } + if a.Before != nil { err := a.Before(context) if err != nil { diff --git a/app_test.go b/app_test.go index 81d1174..1e0b638 100644 --- a/app_test.go +++ b/app_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/codegangsta/cli" + "github.com/imdario/cli" ) func ExampleApp() { @@ -331,6 +331,72 @@ func TestApp_BeforeFunc(t *testing.T) { } +func TestApp_AfterFunc(t *testing.T) { + afterRun, subcommandRun := false, false + afterError := fmt.Errorf("fail") + var err error + + app := cli.NewApp() + + app.After = func(c *cli.Context) error { + afterRun = true + s := c.String("opt") + if s == "fail" { + return afterError + } + + return nil + } + + app.Commands = []cli.Command{ + cli.Command{ + Name: "sub", + Action: func(c *cli.Context) { + subcommandRun = true + }, + }, + } + + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "opt"}, + } + + // run with the After() func succeeding + err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if afterRun == false { + t.Errorf("After() not executed when expected") + } + + if subcommandRun == false { + t.Errorf("Subcommand not executed when expected") + } + + // reset + afterRun, subcommandRun = false, false + + // run with the Before() func failing + err = app.Run([]string{"command", "--opt", "fail", "sub"}) + + // should be the same error produced by the Before func + if err != afterError { + t.Errorf("Run error expected, but not received") + } + + if afterRun == false { + t.Errorf("After() not executed when expected") + } + + if subcommandRun == false { + t.Errorf("Subcommand not executed when expected") + } +} + + func TestAppHelpPrinter(t *testing.T) { oldPrinter := cli.HelpPrinter defer func() { diff --git a/command.go b/command.go index 5622b38..f85bc5d 100644 --- a/command.go +++ b/command.go @@ -21,6 +21,9 @@ type Command struct { // An action to execute before any sub-subcommands are run, but after the context is ready // If a non-nil error is returned, no sub-subcommands are run Before func(context *Context) error + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run regardless of Because() result + After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) // List of child commands @@ -36,7 +39,7 @@ type Command struct { // 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 { + if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { return c.startApp(ctx) } @@ -134,6 +137,7 @@ func (c Command) startApp(ctx *Context) error { // set the actions app.Before = c.Before + app.After = c.After if c.Action != nil { app.Action = c.Action } else { From 3c4b583feee724d8be4eee9ccda9f57443218143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Castan=CC=83e=CC=81?= Date: Tue, 18 Nov 2014 23:54:27 +0100 Subject: [PATCH 05/21] Action error shadowing avoided on After --- app.go | 12 ++++++++++-- app_test.go | 1 - 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index a125afc..25f49b0 100644 --- a/app.go +++ b/app.go @@ -120,7 +120,11 @@ func (a *App) Run(arguments []string) (err error) { if a.After != nil { defer func() { - err = a.After(context) + aferr := a.After(context) + // check Action error to avoid shadowing + if err == nil { + err = aferr + } }() } @@ -208,7 +212,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.After != nil { defer func() { - err = a.After(context) + aferr := a.After(context) + // check Action error to avoid shadowing + if err == nil { + err = aferr + } }() } diff --git a/app_test.go b/app_test.go index 1e0b638..20978bd 100644 --- a/app_test.go +++ b/app_test.go @@ -396,7 +396,6 @@ func TestApp_AfterFunc(t *testing.T) { } } - func TestAppHelpPrinter(t *testing.T) { oldPrinter := cli.HelpPrinter defer func() { From ba22f2189a4bcbc0ce827fcca71835bb6878eb1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Casta=C3=B1=C3=A9?= Date: Wed, 19 Nov 2014 00:28:57 +0100 Subject: [PATCH 06/21] Changing back cli import in app_test.go --- app_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app_test.go b/app_test.go index 20978bd..d3cc1c5 100644 --- a/app_test.go +++ b/app_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/imdario/cli" + "github.com/codegangsta/cli" ) func ExampleApp() { From 1dce44d78134e0d59d0648ffd7ff7e685daad555 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 1 Dec 2014 22:46:57 -0500 Subject: [PATCH 07/21] Adding section in README about env var cascade as well as moving a line back up where it belongs --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e0fdace..0e8327b 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,8 @@ app.Flags = []cli.Flag { } ``` +That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. + #### Values from the Environment You can also have the default value set from the environment via `EnvVar`. e.g. @@ -187,7 +189,18 @@ app.Flags = []cli.Flag { } ``` -That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. +The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, +} +``` ### Subcommands From 780f839a02f0e2c93d91fbfc54f839e9596034f4 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 1 Dec 2014 23:20:21 -0500 Subject: [PATCH 08/21] Allow hiding of help flag without hiding help subcommand By utilizing struct zero value --- app.go | 8 ++++++-- app_test.go | 17 +++++++++++++++++ command.go | 2 +- flag.go | 2 ++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index f4c4af8..d90400d 100644 --- a/app.go +++ b/app.go @@ -70,7 +70,9 @@ func (a *App) Run(arguments []string) error { // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) - a.appendFlag(HelpFlag) + if (HelpFlag != BoolFlag{}) { + a.appendFlag(HelpFlag) + } } //append version/help flags @@ -150,7 +152,9 @@ func (a *App) RunAsSubcommand(ctx *Context) error { if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) - a.appendFlag(HelpFlag) + if (HelpFlag != BoolFlag{}) { + a.appendFlag(HelpFlag) + } } } diff --git a/app_test.go b/app_test.go index 81d1174..ede76c7 100644 --- a/app_test.go +++ b/app_test.go @@ -1,6 +1,7 @@ package cli_test import ( + "flag" "fmt" "os" "testing" @@ -331,6 +332,22 @@ func TestApp_BeforeFunc(t *testing.T) { } +func TestAppNoHelpFlag(t *testing.T) { + oldFlag := cli.HelpFlag + defer func() { + cli.HelpFlag = oldFlag + }() + + cli.HelpFlag = cli.BoolFlag{} + + app := cli.NewApp() + err := app.Run([]string{"test", "-h"}) + + if err != flag.ErrHelp { + t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) + } +} + func TestAppHelpPrinter(t *testing.T) { oldPrinter := cli.HelpPrinter defer func() { diff --git a/command.go b/command.go index 5622b38..1171196 100644 --- a/command.go +++ b/command.go @@ -40,7 +40,7 @@ func (c Command) Run(ctx *Context) error { return c.startApp(ctx) } - if !c.HideHelp { + if !c.HideHelp && (HelpFlag != BoolFlag{}) { // append help to flags c.Flags = append( c.Flags, diff --git a/flag.go b/flag.go index b30bca3..3af14b7 100644 --- a/flag.go +++ b/flag.go @@ -21,6 +21,8 @@ var VersionFlag = BoolFlag{ } // This flag prints the help for all commands and subcommands +// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand +// unless HideHelp is set to true) var HelpFlag = BoolFlag{ Name: "help, h", Usage: "show help", From 0d4870d63e0ed75d9e34109af558a46ccb10801a Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 1 Dec 2014 23:50:04 -0500 Subject: [PATCH 09/21] Rename Stdout -> Writer --- app.go | 22 +++++++++++----------- app_test.go | 4 ++-- command.go | 10 +++++----- help.go | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app.go b/app.go index 0125dae..24e0961 100644 --- a/app.go +++ b/app.go @@ -40,8 +40,8 @@ type App struct { Author string // Author e-mail Email string - // Stdout writer to write output to - Stdout io.Writer + // Writer writer to write output to + Writer io.Writer } // Tries to find out when this binary was compiled. @@ -65,7 +65,7 @@ func NewApp() *App { Compiled: compileTime(), Author: "Author", Email: "unknown@email", - Stdout: os.Stdout, + Writer: os.Stdout, } } @@ -77,7 +77,7 @@ func (a *App) Run(arguments []string) error { }() HelpPrinter = func(templ string, data interface{}) { - w := tabwriter.NewWriter(a.Stdout, 0, 8, 1, '\t', 0) + w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0) t := template.Must(template.New("help").Parse(templ)) err := t.Execute(w, data) if err != nil { @@ -105,18 +105,18 @@ func (a *App) Run(arguments []string) error { err := set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) if nerr != nil { - io.WriteString(a.Stdout, fmt.Sprintln(nerr)) + io.WriteString(a.Writer, fmt.Sprintln(nerr)) context := NewContext(a, set, set) ShowAppHelp(context) - io.WriteString(a.Stdout, fmt.Sprintln("")) + io.WriteString(a.Writer, fmt.Sprintln("")) return nerr } context := NewContext(a, set, set) if err != nil { - io.WriteString(a.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) + io.WriteString(a.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) ShowAppHelp(context) - io.WriteString(a.Stdout, fmt.Sprintln("")) + io.WriteString(a.Writer, fmt.Sprintln("")) return err } @@ -176,18 +176,18 @@ func (a *App) RunAsSubcommand(ctx *Context) error { context := NewContext(a, set, set) if nerr != nil { - io.WriteString(a.Stdout, fmt.Sprintln(nerr)) + io.WriteString(a.Writer, fmt.Sprintln(nerr)) if len(a.Commands) > 0 { ShowSubcommandHelp(context) } else { ShowCommandHelp(ctx, context.Args().First()) } - io.WriteString(a.Stdout, fmt.Sprintln("")) + io.WriteString(a.Writer, fmt.Sprintln("")) return nerr } if err != nil { - io.WriteString(a.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) + io.WriteString(a.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) ShowSubcommandHelp(context) return err } diff --git a/app_test.go b/app_test.go index e8937e1..b436bb8 100644 --- a/app_test.go +++ b/app_test.go @@ -265,7 +265,7 @@ func TestApp_ParseSliceFlags(t *testing.T) { func TestApp_DefaultStdout(t *testing.T) { app := cli.NewApp() - if app.Stdout != os.Stdout { + if app.Writer != os.Stdout { t.Error("Default output writer not set.") } } @@ -293,7 +293,7 @@ func TestApp_SetStdout(t *testing.T) { app := cli.NewApp() app.Name = "test" - app.Stdout = mockWriter + app.Writer = mockWriter err := app.Run([]string{"help"}) diff --git a/command.go b/command.go index 3d470c8..58d1f97 100644 --- a/command.go +++ b/command.go @@ -71,18 +71,18 @@ func (c Command) Run(ctx *Context) error { } if err != nil { - io.WriteString(ctx.App.Stdout, fmt.Sprintf("Incorrect Usage.\n\n")) + io.WriteString(ctx.App.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) ShowCommandHelp(ctx, c.Name) - io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) + io.WriteString(ctx.App.Writer, fmt.Sprintln("")) return err } nerr := normalizeFlags(c.Flags, set) if nerr != nil { - io.WriteString(ctx.App.Stdout, fmt.Sprintln(nerr)) - io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) + io.WriteString(ctx.App.Writer, fmt.Sprintln(nerr)) + io.WriteString(ctx.App.Writer, fmt.Sprintln("")) ShowCommandHelp(ctx, c.Name) - io.WriteString(ctx.App.Stdout, fmt.Sprintln("")) + io.WriteString(ctx.App.Writer, fmt.Sprintln("")) return nerr } context := NewContext(ctx.App, set, ctx.globalSet) diff --git a/help.go b/help.go index ae60a13..a90780b 100644 --- a/help.go +++ b/help.go @@ -99,9 +99,9 @@ func ShowAppHelp(c *Context) { // Prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { for _, command := range c.App.Commands { - io.WriteString(c.App.Stdout, fmt.Sprintln(command.Name)) + io.WriteString(c.App.Writer, fmt.Sprintln(command.Name)) if command.ShortName != "" { - io.WriteString(c.App.Stdout, fmt.Sprintln(command.ShortName)) + io.WriteString(c.App.Writer, fmt.Sprintln(command.ShortName)) } } } @@ -118,7 +118,7 @@ func ShowCommandHelp(c *Context, command string) { if c.App.CommandNotFound != nil { c.App.CommandNotFound(c, command) } else { - io.WriteString(c.App.Stdout, fmt.Sprintf("No help topic for '%v'\n", command)) + io.WriteString(c.App.Writer, fmt.Sprintf("No help topic for '%v'\n", command)) } } @@ -129,7 +129,7 @@ func ShowSubcommandHelp(c *Context) { // Prints the version number of the App func ShowVersion(c *Context) { - io.WriteString(c.App.Stdout, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) + io.WriteString(c.App.Writer, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) } // Prints the lists of commands within a given context From e72094e6a406a21eb79abf5c02cee2404ffcbb3b Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 1 Dec 2014 23:57:35 -0500 Subject: [PATCH 10/21] Prefer fmt.Fprint* functions over io.WriteString Less composition needed. --- app.go | 16 ++++++++-------- command.go | 11 +++++------ help.go | 13 +++++-------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/app.go b/app.go index 7dcae60..edf02fb 100644 --- a/app.go +++ b/app.go @@ -112,18 +112,18 @@ func (a *App) Run(arguments []string) error { err := set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) if nerr != nil { - io.WriteString(a.Writer, fmt.Sprintln(nerr)) + fmt.Fprintln(a.Writer, nerr) context := NewContext(a, set, set) ShowAppHelp(context) - io.WriteString(a.Writer, fmt.Sprintln("")) + fmt.Fprintln(a.Writer) return nerr } context := NewContext(a, set, set) if err != nil { - io.WriteString(a.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) + fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") ShowAppHelp(context) - io.WriteString(a.Writer, fmt.Sprintln("")) + fmt.Fprintln(a.Writer) return err } @@ -163,7 +163,7 @@ func (a *App) Run(arguments []string) error { // Another entry point to the cli app, takes care of passing arguments and error handling func (a *App) RunAndExitOnError() { if err := a.Run(os.Args); err != nil { - os.Stderr.WriteString(fmt.Sprintln(err)) + fmt.Fprintln(os.Stderr, err) os.Exit(1) } } @@ -191,18 +191,18 @@ func (a *App) RunAsSubcommand(ctx *Context) error { context := NewContext(a, set, ctx.globalSet) if nerr != nil { - io.WriteString(a.Writer, fmt.Sprintln(nerr)) + fmt.Fprintln(a.Writer, nerr) if len(a.Commands) > 0 { ShowSubcommandHelp(context) } else { ShowCommandHelp(ctx, context.Args().First()) } - io.WriteString(a.Writer, fmt.Sprintln("")) + fmt.Fprintln(a.Writer) return nerr } if err != nil { - io.WriteString(a.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) + fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index befb2a7..1536b15 100644 --- a/command.go +++ b/command.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "io" "io/ioutil" "strings" ) @@ -75,18 +74,18 @@ func (c Command) Run(ctx *Context) error { } if err != nil { - io.WriteString(ctx.App.Writer, fmt.Sprintf("Incorrect Usage.\n\n")) + fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n") ShowCommandHelp(ctx, c.Name) - io.WriteString(ctx.App.Writer, fmt.Sprintln("")) + fmt.Fprintln(ctx.App.Writer) return err } nerr := normalizeFlags(c.Flags, set) if nerr != nil { - io.WriteString(ctx.App.Writer, fmt.Sprintln(nerr)) - io.WriteString(ctx.App.Writer, fmt.Sprintln("")) + fmt.Fprintln(ctx.App.Writer, nerr) + fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - io.WriteString(ctx.App.Writer, fmt.Sprintln("")) + fmt.Fprintln(ctx.App.Writer) return nerr } context := NewContext(ctx.App, set, ctx.globalSet) diff --git a/help.go b/help.go index e089655..c6743db 100644 --- a/help.go +++ b/help.go @@ -1,9 +1,6 @@ package cli -import ( - "fmt" - "io" -) +import "fmt" // The text template for the Default help topic. // cli.go uses text/template to render templates. You can @@ -106,9 +103,9 @@ func ShowAppHelp(c *Context) { // Prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { for _, command := range c.App.Commands { - io.WriteString(c.App.Writer, fmt.Sprintln(command.Name)) + fmt.Fprintln(c.App.Writer, command.Name) if command.ShortName != "" { - io.WriteString(c.App.Writer, fmt.Sprintln(command.ShortName)) + fmt.Fprintln(c.App.Writer, command.ShortName) } } } @@ -125,7 +122,7 @@ func ShowCommandHelp(c *Context, command string) { if c.App.CommandNotFound != nil { c.App.CommandNotFound(c, command) } else { - io.WriteString(c.App.Writer, fmt.Sprintf("No help topic for '%v'\n", command)) + fmt.Fprintf(c.App.Writer, "No help topic for '%v'\n", command) } } @@ -140,7 +137,7 @@ func ShowVersion(c *Context) { } func printVersion(c *Context) { - io.WriteString(c.App.Writer, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) + fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) } // Prints the lists of commands within a given context From 69b84ea8049f1a89469bc9dbbf9609ec9793f04b Mon Sep 17 00:00:00 2001 From: jszwedko Date: Wed, 10 Dec 2014 10:31:53 -0500 Subject: [PATCH 11/21] Renaming fakeWriter to mockWriter for consistency --- app_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app_test.go b/app_test.go index 413a4b2..1413199 100644 --- a/app_test.go +++ b/app_test.go @@ -273,11 +273,11 @@ func TestApp_DefaultStdout(t *testing.T) { } } -type fakeWriter struct { +type mockWriter struct { written []byte } -func (fw *fakeWriter) Write(p []byte) (n int, err error) { +func (fw *mockWriter) Write(p []byte) (n int, err error) { if fw.written == nil { fw.written = p } else { @@ -287,16 +287,16 @@ func (fw *fakeWriter) Write(p []byte) (n int, err error) { return len(p), nil } -func (fw *fakeWriter) GetWritten() (b []byte) { +func (fw *mockWriter) GetWritten() (b []byte) { return fw.written } func TestApp_SetStdout(t *testing.T) { - mockWriter := &fakeWriter{} + w := &mockWriter{} app := cli.NewApp() app.Name = "test" - app.Writer = mockWriter + app.Writer = w err := app.Run([]string{"help"}) @@ -304,7 +304,7 @@ func TestApp_SetStdout(t *testing.T) { t.Fatalf("Run error: %s", err) } - if len(mockWriter.written) == 0 { + if len(w.written) == 0 { t.Error("App did not write output to desired writer.") } } From d05d89549219d66729a07de6451f7d1264f28afc Mon Sep 17 00:00:00 2001 From: Matthew McNew Date: Mon, 8 Dec 2014 11:42:04 -0600 Subject: [PATCH 12/21] stop flag parsing after terminator -- Signed-off-by: David Wadden --- app_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ command.go | 20 ++++++++++++++++---- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/app_test.go b/app_test.go index 1413199..993369d 100644 --- a/app_test.go +++ b/app_test.go @@ -192,6 +192,50 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { expect(t, firstArg, "my-arg") } +func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { + var parsedOption string + var args []string + + app := cli.NewApp() + command := cli.Command{ + Name: "cmd", + Flags: []cli.Flag{ + cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *cli.Context) { + parsedOption = c.String("option") + args = c.Args() + }, + } + app.Commands = []cli.Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) + + expect(t, parsedOption, "my-option") + expect(t, args[0], "my-arg") + expect(t, args[1], "--") + expect(t, args[2], "--notARealFlag") +} + +func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { + var args []string + + app := cli.NewApp() + command := cli.Command{ + Name: "cmd", + Action: func(c *cli.Context) { + args = c.Args() + }, + } + app.Commands = []cli.Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) + + expect(t, args[0], "my-arg") + expect(t, args[1], "--") + expect(t, args[2], "notAFlagAtAll") +} + func TestApp_Float64Flag(t *testing.T) { var meters float64 diff --git a/command.go b/command.go index 1536b15..d2b0532 100644 --- a/command.go +++ b/command.go @@ -56,18 +56,30 @@ func (c Command) Run(ctx *Context) error { set.SetOutput(ioutil.Discard) firstFlagIndex := -1 + terminatorIndex := -1 for index, arg := range ctx.Args() { - if strings.HasPrefix(arg, "-") { - firstFlagIndex = index + if arg == "--" { + terminatorIndex = index break + } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { + firstFlagIndex = index } } var err error if firstFlagIndex > -1 && !c.SkipFlagParsing { args := ctx.Args() - regularArgs := args[1:firstFlagIndex] - flagArgs := args[firstFlagIndex:] + 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 { err = set.Parse(ctx.Args().Tail()) From 22dbe6ffdcc531d86c29c1be5b34880937284368 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Thu, 8 Jan 2015 13:58:34 -0500 Subject: [PATCH 13/21] Fix help text for generic flag to not insinuate that you can specify multiple Feels like it may have been copied from StringSliceFlag or something, but update the output to be more consistent with other single value flags. Also added comments to the String and Apply functions. --- flag.go | 7 ++++++- flag_test.go | 7 +++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/flag.go b/flag.go index ddd6ef8..7426d37 100644 --- a/flag.go +++ b/flag.go @@ -67,10 +67,15 @@ type GenericFlag struct { EnvVar string } +// 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 { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage)) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s '%v'\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage)) } +// 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) { val := f.Value if f.EnvVar != "" { diff --git a/flag_test.go b/flag_test.go index 4f0ba55..3ed07cd 100644 --- a/flag_test.go +++ b/flag_test.go @@ -262,15 +262,14 @@ var genericFlagTests = []struct { value cli.Generic expected string }{ - {"help", &Parser{}, "--help \t`-help option -help option` "}, - {"h", &Parser{}, "-h \t`-h option -h option` "}, - {"test", &Parser{}, "--test \t`-test option -test option` "}, + {"test", &Parser{"abc", "def"}, "--test 'abc,def'\ttest flag"}, + {"t", &Parser{"abc", "def"}, "-t 'abc,def'\ttest flag"}, } func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { - flag := cli.GenericFlag{Name: test.name} + flag := cli.GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} output := flag.String() if output != test.expected { From 059c02782a3a281555d2bdf13c761fa05ddb4081 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Thu, 8 Jan 2015 15:35:23 -0500 Subject: [PATCH 14/21] Use double quotes in output for defaults Windows doesn't recognize 's for wrapping values. "s should work on all systems. --- flag.go | 14 +++++++------- flag_test.go | 34 +++++++++++++++++----------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/flag.go b/flag.go index 9ec9b8c..0a3c880 100644 --- a/flag.go +++ b/flag.go @@ -73,7 +73,7 @@ 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%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage)) } // Apply takes the flagset and calls Set on the generic flag with the value @@ -124,7 +124,7 @@ type StringSliceFlag struct { func (f StringSliceFlag) String() string { firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") pref := prefixFor(firstName) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) } func (f StringSliceFlag) Apply(set *flag.FlagSet) { @@ -183,7 +183,7 @@ type IntSliceFlag struct { func (f IntSliceFlag) String() string { firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") pref := prefixFor(firstName) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) } func (f IntSliceFlag) Apply(set *flag.FlagSet) { @@ -294,7 +294,7 @@ func (f StringFlag) String() string { fmtString = "%s %v\t%v" if len(f.Value) > 0 { - fmtString = "%s '%v'\t%v" + fmtString = "%s \"%v\"\t%v" } else { fmtString = "%s %v\t%v" } @@ -330,7 +330,7 @@ type IntFlag struct { } func (f IntFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) } func (f IntFlag) Apply(set *flag.FlagSet) { @@ -364,7 +364,7 @@ type DurationFlag struct { } func (f DurationFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) } func (f DurationFlag) Apply(set *flag.FlagSet) { @@ -398,7 +398,7 @@ type Float64Flag struct { } func (f Float64Flag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) } func (f Float64Flag) Apply(set *flag.FlagSet) { diff --git a/flag_test.go b/flag_test.go index 3ed07cd..f0f096a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -38,7 +38,7 @@ var stringFlagTests = []struct { {"help", "", "--help \t"}, {"h", "", "-h \t"}, {"h", "", "-h \t"}, - {"test", "Something", "--test 'Something'\t"}, + {"test", "Something", "--test \"Something\"\t"}, } func TestStringFlagHelpOutput(t *testing.T) { @@ -75,22 +75,22 @@ var stringSliceFlagTests = []struct { s := &cli.StringSlice{} s.Set("") return s - }(), "--help '--help option --help option'\t"}, + }(), "--help [--help option --help option]\t"}, {"h", func() *cli.StringSlice { s := &cli.StringSlice{} s.Set("") return s - }(), "-h '-h option -h option'\t"}, + }(), "-h [-h option -h option]\t"}, {"h", func() *cli.StringSlice { s := &cli.StringSlice{} s.Set("") return s - }(), "-h '-h option -h option'\t"}, + }(), "-h [-h option -h option]\t"}, {"test", func() *cli.StringSlice { s := &cli.StringSlice{} s.Set("Something") return s - }(), "--test '--test option --test option'\t"}, + }(), "--test [--test option --test option]\t"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { @@ -122,8 +122,8 @@ var intFlagTests = []struct { name string expected string }{ - {"help", "--help '0'\t"}, - {"h", "-h '0'\t"}, + {"help", "--help \"0\"\t"}, + {"h", "-h \"0\"\t"}, } func TestIntFlagHelpOutput(t *testing.T) { @@ -155,8 +155,8 @@ var durationFlagTests = []struct { name string expected string }{ - {"help", "--help '0'\t"}, - {"h", "-h '0'\t"}, + {"help", "--help \"0\"\t"}, + {"h", "-h \"0\"\t"}, } func TestDurationFlagHelpOutput(t *testing.T) { @@ -189,14 +189,14 @@ var intSliceFlagTests = []struct { value *cli.IntSlice expected string }{ - {"help", &cli.IntSlice{}, "--help '--help option --help option'\t"}, - {"h", &cli.IntSlice{}, "-h '-h option -h option'\t"}, - {"h", &cli.IntSlice{}, "-h '-h option -h option'\t"}, + {"help", &cli.IntSlice{}, "--help [--help option --help option]\t"}, + {"h", &cli.IntSlice{}, "-h [-h option -h option]\t"}, + {"h", &cli.IntSlice{}, "-h [-h option -h option]\t"}, {"test", func() *cli.IntSlice { i := &cli.IntSlice{} i.Set("9") return i - }(), "--test '--test option --test option'\t"}, + }(), "--test [--test option --test option]\t"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { @@ -228,8 +228,8 @@ var float64FlagTests = []struct { name string expected string }{ - {"help", "--help '0'\t"}, - {"h", "-h '0'\t"}, + {"help", "--help \"0\"\t"}, + {"h", "-h \"0\"\t"}, } func TestFloat64FlagHelpOutput(t *testing.T) { @@ -262,8 +262,8 @@ var genericFlagTests = []struct { value cli.Generic expected string }{ - {"test", &Parser{"abc", "def"}, "--test 'abc,def'\ttest flag"}, - {"t", &Parser{"abc", "def"}, "-t 'abc,def'\ttest flag"}, + {"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"}, + {"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"}, } func TestGenericFlagHelpOutput(t *testing.T) { From ef23aa6da2b91ce6a0195047374383c5894eb9e9 Mon Sep 17 00:00:00 2001 From: Sam Zaydel Date: Fri, 9 Jan 2015 05:53:14 -0800 Subject: [PATCH 15/21] strconv.ParseInt should be used instead of strconv.ParseUint when reading Int Flags from envvars. --- flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flag.go b/flag.go index 0a3c880..90859d2 100644 --- a/flag.go +++ b/flag.go @@ -338,7 +338,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { - envValInt, err := strconv.ParseUint(envVal, 10, 64) + envValInt, err := strconv.ParseInt(envVal, 10, 64) if err == nil { f.Value = int(envValInt) break From 44d40054fa6208a3013d7217aca72a2b8b0f5a0b Mon Sep 17 00:00:00 2001 From: jszwedko Date: Fri, 9 Jan 2015 13:10:42 -0500 Subject: [PATCH 16/21] Use 0 as the base when parsing ints To be consistent with what the stdlib flag package does. --- flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flag.go b/flag.go index 90859d2..2511586 100644 --- a/flag.go +++ b/flag.go @@ -338,7 +338,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { - envValInt, err := strconv.ParseInt(envVal, 10, 64) + envValInt, err := strconv.ParseInt(envVal, 0, 64) if err == nil { f.Value = int(envValInt) break From 5159b98cbc0adb739bfc96e3f0ccf853659dbed6 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Fri, 9 Jan 2015 14:46:29 -0500 Subject: [PATCH 17/21] Use parsed context when running command as subcommnd Rather than passing the unparsed context (parent context) in whenever there are no subcommands. --- app.go | 6 +----- app_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/app.go b/app.go index deb8e7f..6422345 100644 --- a/app.go +++ b/app.go @@ -242,11 +242,7 @@ func (a *App) RunAsSubcommand(ctx *Context) error { } // Run default Action - if len(a.Commands) > 0 { - a.Action(context) - } else { - a.Action(ctx) - } + a.Action(context) return nil } diff --git a/app_test.go b/app_test.go index 78f0c10..2cbb0e3 100644 --- a/app_test.go +++ b/app_test.go @@ -193,6 +193,32 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { expect(t, firstArg, "my-arg") } +func TestApp_RunAsSubcommandParseFlags(t *testing.T) { + var context *cli.Context + + a := cli.NewApp() + a.Commands = []cli.Command{ + { + Name: "foo", + Action: func(c *cli.Context) { + context = c + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Before: func(_ *cli.Context) error { return nil }, + }, + } + a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) + + expect(t, context.Args().Get(0), "abcd") + expect(t, context.String("lang"), "spanish") +} + func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string var args []string From 4ab12cd639d80ed43e78a02ef62a8073f597192f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Castan=CC=83e=CC=81?= Date: Sat, 10 Jan 2015 00:44:37 +0100 Subject: [PATCH 18/21] jszwedko's suggestion done --- app.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app.go b/app.go index f400ab9..69aa95b 100644 --- a/app.go +++ b/app.go @@ -78,6 +78,7 @@ func NewApp() *App { // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination func (a *App) Run(arguments []string) error { + var err error if HelpPrinter == nil { defer func() { HelpPrinter = nil @@ -146,11 +147,10 @@ func (a *App) Run(arguments []string) error { if a.After != nil { defer func() { - aferr := a.After(context) - // check Action error to avoid shadowing - if err == nil { - err = aferr - } + // err is always nil here. + // There is a check to see if it is non-nil + // just few lines before. + err = a.After(context) }() } @@ -184,7 +184,8 @@ func (a *App) RunAndExitOnError() { } // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags -func (a *App) RunAsSubcommand(ctx *Context) (err error) { +func (a *App) RunAsSubcommand(ctx *Context) error { + var err error // append help to commands if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { @@ -240,11 +241,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.After != nil { defer func() { - aferr := a.After(context) - // check Action error to avoid shadowing - if err == nil { - err = aferr - } + // err is always nil here. + // There is a check to see if it is non-nil + // just few lines before. + err = a.After(context) }() } From dca177c384c35776b499012316b5363475499cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Castan=CC=83e=CC=81?= Date: Sat, 10 Jan 2015 00:51:06 +0100 Subject: [PATCH 19/21] After handler brought up to speed. As discussed in issue #151, I modified the implementation to be simpler. I checked how to propagate err, being done with a scope trick. From our deferred function we cannot return a new value, so we override the "default" return value (err), declared on functions' declarations. Check this StackOverflow for more info: http://stackoverflow.com/a/19934989 --- app.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 69aa95b..f01ae42 100644 --- a/app.go +++ b/app.go @@ -77,8 +77,7 @@ func NewApp() *App { } // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination -func (a *App) Run(arguments []string) error { - var err error +func (a *App) Run(arguments []string) (err error) { if HelpPrinter == nil { defer func() { HelpPrinter = nil @@ -184,8 +183,7 @@ func (a *App) RunAndExitOnError() { } // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags -func (a *App) RunAsSubcommand(ctx *Context) error { - var err error +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 { From 2a9c617652b1f8a8e5db915dbe85cc0c0b1967ca Mon Sep 17 00:00:00 2001 From: Vishal Sodani Date: Fri, 30 Jan 2015 16:32:58 +0530 Subject: [PATCH 20/21] Fix sentence --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e8327b..c0bb338 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,6 @@ setting the `PROG` variable to the name of your program: ## 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 are have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. +If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. From 50c77ecec0068c9aef9d90ae0fd0fdf410041da3 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Fri, 20 Feb 2015 16:21:27 -0500 Subject: [PATCH 21/21] Fix comments for .After field on Command and App --- app.go | 2 +- command.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index ab49ec0..928983e 100644 --- a/app.go +++ b/app.go @@ -35,7 +35,7 @@ type App struct { // If a non-nil error is returned, no subcommands are run Before func(context *Context) error // An action to execute after any subcommands are run, but after the subcommand has finished - // It is run regardless of Because() result + // It is run even if Action() panics After func(context *Context) error // The action to execute when no subcommands are specified Action func(context *Context) diff --git a/command.go b/command.go index 7c02e47..5747e52 100644 --- a/command.go +++ b/command.go @@ -22,7 +22,7 @@ type Command struct { // If a non-nil error is returned, no sub-subcommands are run Before func(context *Context) error // An action to execute after any subcommands are run, but after the subcommand has finished - // It is run regardless of Because() result + // It is run even if Action() panics After func(context *Context) error // The function to call when this command is invoked Action func(context *Context)