From 71e3acacd2ea8254236025195101171ab0506a17 Mon Sep 17 00:00:00 2001 From: Artem Nezvigin Date: Fri, 13 Jun 2014 20:09:28 -0700 Subject: [PATCH 01/36] Add FlagNames() method to Context It's often useful to list all defined flags prior to launching the program for debugging/logging purposes. This takes away the boilerplate. --- context.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/context.go b/context.go index b2c51bb..5c65c32 100644 --- a/context.go +++ b/context.go @@ -105,6 +105,18 @@ func (c *Context) IsSet(name string) bool { return c.setFlags[name] == true } +// 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] + if name == "help" { + continue + } + names = append(names, name) + } + return +} + type Args []string // Returns the command line arguments associated with the context. From 58216320005c038f3b704c3e56fb8f53eb941ec3 Mon Sep 17 00:00:00 2001 From: Joshua Mervine Date: Tue, 17 Jun 2014 21:37:55 -0700 Subject: [PATCH 02/36] Adding VersionPrinter and tests. --- app_test.go | 22 +++++++++++++++++++++- help.go | 7 +++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app_test.go b/app_test.go index 0b9e154..51a2d4c 100644 --- a/app_test.go +++ b/app_test.go @@ -2,7 +2,7 @@ package cli_test import ( "fmt" - "github.com/codegangsta/cli" + "." "os" "testing" ) @@ -347,6 +347,26 @@ func TestAppHelpPrinter(t *testing.T) { } } +func TestAppVersionPrinter(t *testing.T) { + oldPrinter := cli.VersionPrinter + defer func() { + cli.VersionPrinter = oldPrinter + }() + + var wasCalled = false + cli.VersionPrinter = func(c *cli.Context) { + wasCalled = true + } + + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + cli.ShowVersion(ctx) + + if wasCalled == false { + t.Errorf("Version printer expected to be called, but was not") + } +} + func TestAppCommandNotFound(t *testing.T) { beforeRun, subcommandRun := false, false app := cli.NewApp() diff --git a/help.go b/help.go index 7c04005..45b9db8 100644 --- a/help.go +++ b/help.go @@ -92,6 +92,9 @@ var helpSubcommand = Command{ // Prints help for the App var HelpPrinter = printHelp +// Prints version for the App +var VersionPrinter = printVersion + func ShowAppHelp(c *Context) { HelpPrinter(AppHelpTemplate, c.App) } @@ -129,6 +132,10 @@ func ShowSubcommandHelp(c *Context) { // Prints the version number of the App func ShowVersion(c *Context) { + VersionPrinter(c) +} + +func printVersion(c *Context) { fmt.Printf("%v version %v\n", c.App.Name, c.App.Version) } From 7ca86444e8a8c42f1a87d6fa093d6f9fc5d50230 Mon Sep 17 00:00:00 2001 From: Josh Mervine Date: Tue, 17 Jun 2014 22:28:33 -0700 Subject: [PATCH 03/36] making all pathing for cli local in tests for easier fork testing --- cli_test.go | 2 +- command_test.go | 2 +- context_test.go | 2 +- flag_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli_test.go b/cli_test.go index 30f3c13..ec800f7 100644 --- a/cli_test.go +++ b/cli_test.go @@ -1,7 +1,7 @@ package cli_test import ( - "github.com/codegangsta/cli" + "." "os" ) diff --git a/command_test.go b/command_test.go index 3afd83e..7c94322 100644 --- a/command_test.go +++ b/command_test.go @@ -2,7 +2,7 @@ package cli_test import ( "flag" - "github.com/codegangsta/cli" + "." "testing" ) diff --git a/context_test.go b/context_test.go index 89041b9..25b543c 100644 --- a/context_test.go +++ b/context_test.go @@ -2,7 +2,7 @@ package cli_test import ( "flag" - "github.com/codegangsta/cli" + "." "testing" ) diff --git a/flag_test.go b/flag_test.go index 1c05f01..8a98f99 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1,7 +1,7 @@ package cli_test import ( - "github.com/codegangsta/cli" + "." "fmt" "reflect" From 8f55ca83ff0e27563fbd54fb51e845f97fc8162d Mon Sep 17 00:00:00 2001 From: Josh Mervine Date: Tue, 17 Jun 2014 22:33:30 -0700 Subject: [PATCH 04/36] running go fmt --- app_test.go | 8 ++++---- command_test.go | 24 ++++++++++++------------ context_test.go | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app_test.go b/app_test.go index 51a2d4c..fe5a78e 100644 --- a/app_test.go +++ b/app_test.go @@ -1,8 +1,8 @@ package cli_test import ( - "fmt" "." + "fmt" "os" "testing" ) @@ -348,7 +348,7 @@ func TestAppHelpPrinter(t *testing.T) { } func TestAppVersionPrinter(t *testing.T) { - oldPrinter := cli.VersionPrinter + oldPrinter := cli.VersionPrinter defer func() { cli.VersionPrinter = oldPrinter }() @@ -359,8 +359,8 @@ func TestAppVersionPrinter(t *testing.T) { } app := cli.NewApp() - ctx := cli.NewContext(app, nil, nil) - cli.ShowVersion(ctx) + ctx := cli.NewContext(app, nil, nil) + cli.ShowVersion(ctx) if wasCalled == false { t.Errorf("Version printer expected to be called, but was not") diff --git a/command_test.go b/command_test.go index 7c94322..e601fc1 100644 --- a/command_test.go +++ b/command_test.go @@ -1,8 +1,8 @@ package cli_test import ( - "flag" "." + "flag" "testing" ) @@ -14,12 +14,12 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) { c := cli.NewContext(app, set, set) - command := cli.Command { - Name: "test-cmd", - ShortName: "tc", - Usage: "this is for testing", + command := cli.Command{ + Name: "test-cmd", + ShortName: "tc", + Usage: "this is for testing", Description: "testing", - Action: func(_ *cli.Context) { }, + Action: func(_ *cli.Context) {}, } err := command.Run(c) @@ -34,12 +34,12 @@ func TestCommandIgnoreFlags(t *testing.T) { c := cli.NewContext(app, set, set) - command := cli.Command { - Name: "test-cmd", - ShortName: "tc", - Usage: "this is for testing", - Description: "testing", - Action: func(_ *cli.Context) { }, + command := cli.Command{ + Name: "test-cmd", + ShortName: "tc", + Usage: "this is for testing", + Description: "testing", + Action: func(_ *cli.Context) {}, SkipFlagParsing: true, } err := command.Run(c) diff --git a/context_test.go b/context_test.go index 25b543c..7c6c8b9 100644 --- a/context_test.go +++ b/context_test.go @@ -1,8 +1,8 @@ package cli_test import ( - "flag" "." + "flag" "testing" ) From 742745ce198a1f2f1347cffaf76496cd5182842b Mon Sep 17 00:00:00 2001 From: Yushi Nakai Date: Thu, 19 Jun 2014 00:59:07 +0900 Subject: [PATCH 05/36] add completion file for zsh --- autocomplete/bash_autocomplete | 2 +- autocomplete/zsh_autocomplete | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 autocomplete/zsh_autocomplete diff --git a/autocomplete/bash_autocomplete b/autocomplete/bash_autocomplete index a860e03..9b55dd9 100644 --- a/autocomplete/bash_autocomplete +++ b/autocomplete/bash_autocomplete @@ -5,7 +5,7 @@ _cli_bash_autocomplete() { COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts=$( ${COMP_WORDS[@]:0:COMP_CWORD} --generate-bash-completion ) + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 } diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete new file mode 100644 index 0000000..5430a18 --- /dev/null +++ b/autocomplete/zsh_autocomplete @@ -0,0 +1,5 @@ +autoload -U compinit && compinit +autoload -U bashcompinit && bashcompinit + +script_dir=$(dirname $0) +source ${script_dir}/bash_autocomplete From df5fb460489b2d6075008391a86c52cccd6608b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Lafoucrie=CC=80re?= Date: Sun, 6 Jul 2014 11:04:48 +0200 Subject: [PATCH 06/36] Fix global flags in Subcommands closes #69 --- app.go | 2 +- app_test.go | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 4efba5e..9ba043e 100644 --- a/app.go +++ b/app.go @@ -151,7 +151,7 @@ func (a *App) RunAsSubcommand(ctx *Context) error { set.SetOutput(ioutil.Discard) err := set.Parse(ctx.Args().Tail()) nerr := normalizeFlags(a.Flags, set) - context := NewContext(a, set, set) + context := NewContext(a, set, ctx.globalSet) if nerr != nil { fmt.Println(nerr) diff --git a/app_test.go b/app_test.go index 0b9e154..cf0e3d0 100644 --- a/app_test.go +++ b/app_test.go @@ -2,9 +2,10 @@ package cli_test import ( "fmt" - "github.com/codegangsta/cli" "os" "testing" + + "github.com/codegangsta/cli" ) func ExampleApp() { @@ -369,3 +370,32 @@ func TestAppCommandNotFound(t *testing.T) { expect(t, beforeRun, true) expect(t, subcommandRun, false) } + +func TestGlobalFlagsInSubcommands(t *testing.T) { + subcommandRun := false + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, + } + + app.Commands = []cli.Command{ + cli.Command{ + Name: "foo", + Subcommands: []cli.Command{ + { + Name: "bar", + Action: func(c *cli.Context) { + if c.GlobalBool("debug") { + subcommandRun = true + } + }, + }, + }, + }, + } + + app.Run([]string{"command", "-d", "foo", "bar"}) + + expect(t, subcommandRun, true) +} From 97fd93272f0a480907246cf41b109bb81c3e01ee Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 11 Jul 2014 13:29:56 -0400 Subject: [PATCH 07/36] Starting to hack in some env var configuration goodness --- flag.go | 161 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 126 insertions(+), 35 deletions(-) diff --git a/flag.go b/flag.go index e6f8838..50f192f 100644 --- a/flag.go +++ b/flag.go @@ -3,18 +3,19 @@ package cli import ( "flag" "fmt" + "os" "strconv" "strings" ) // This flag enables bash-completion for all commands and subcommands -var BashCompletionFlag = BoolFlag{"generate-bash-completion", ""} +var BashCompletionFlag = BoolFlag{"generate-bash-completion", "", ""} // This flag prints the version for the application -var VersionFlag = BoolFlag{"version, v", "print the version"} +var VersionFlag = BoolFlag{"version, v", "print the version", ""} // This flag prints the help for all commands and subcommands -var HelpFlag = BoolFlag{"help, h", "show help"} +var HelpFlag = BoolFlag{"help, h", "show help", ""} // Flag is a common interface related to parsing flags in cli. // For more advanced flag parsing techniques, it is recomended that @@ -51,16 +52,24 @@ type Generic interface { // GenericFlag is the flag type for types implementing Generic type GenericFlag struct { - Name string - Value Generic - Usage string + Name string + Value Generic + Usage string + EnvVar string } func (f GenericFlag) String() string { - return 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` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage)) } func (f GenericFlag) Apply(set *flag.FlagSet) { + val := f.Value + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + val.Set(envVal) + } + } + eachName(f.Name, func(name string) { set.Var(f.Value, name, f.Usage) }) @@ -86,18 +95,29 @@ func (f *StringSlice) Value() []string { } type StringSliceFlag struct { - Name string - Value *StringSlice - Usage string + Name string + Value *StringSlice + Usage string + EnvVar string } func (f StringSliceFlag) String() string { firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") pref := prefixFor(firstName) - return 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) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + newVal.Set(s) + } + f.Value = newVal + } + } + eachName(f.Name, func(name string) { set.Var(f.Value, name, f.Usage) }) @@ -129,18 +149,32 @@ func (f *IntSlice) Value() []int { } type IntSliceFlag struct { - Name string - Value *IntSlice - Usage string + Name string + Value *IntSlice + Usage string + EnvVar string } func (f IntSliceFlag) String() string { firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") pref := prefixFor(firstName) - return 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) { + 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()) + } + } + f.Value = newVal + } + } + eachName(f.Name, func(name string) { set.Var(f.Value, name, f.Usage) }) @@ -151,17 +185,28 @@ func (f IntSliceFlag) getName() string { } type BoolFlag struct { - Name string - Usage string + Name string + Usage string + EnvVar string } func (f BoolFlag) String() string { - return fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) } 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 + } + } + } + eachName(f.Name, func(name string) { - set.Bool(name, false, f.Usage) + set.Bool(name, val, f.Usage) }) } @@ -170,17 +215,28 @@ func (f BoolFlag) getName() string { } type BoolTFlag struct { - Name string - Usage string + Name string + Usage string + EnvVar string } func (f BoolTFlag) String() string { - return fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) } 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 + } + } + } + eachName(f.Name, func(name string) { - set.Bool(name, true, f.Usage) + set.Bool(name, val, f.Usage) }) } @@ -189,9 +245,10 @@ func (f BoolTFlag) getName() string { } type StringFlag struct { - Name string - Value string - Usage string + Name string + Value string + Usage string + EnvVar string } func (f StringFlag) String() string { @@ -204,10 +261,16 @@ func (f StringFlag) String() string { fmtString = "%s %v\t%v" } - return fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) } func (f StringFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + f.Value = envVal + } + } + eachName(f.Name, func(name string) { set.String(name, f.Value, f.Usage) }) @@ -218,16 +281,26 @@ func (f StringFlag) getName() string { } type IntFlag struct { - Name string - Value int - Usage string + Name string + Value int + Usage string + EnvVar string } func (f IntFlag) String() string { - return 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) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValInt, err := strconv.ParseUint(envVal, 10, 64) + if err == nil { + f.Value = int(envValInt) + } + } + } + eachName(f.Name, func(name string) { set.Int(name, f.Value, f.Usage) }) @@ -238,16 +311,26 @@ func (f IntFlag) getName() string { } type Float64Flag struct { - Name string - Value float64 - Usage string + Name string + Value float64 + Usage string + EnvVar string } func (f Float64Flag) String() string { - return 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) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err == nil { + f.Value = float64(envValFloat) + } + } + } + eachName(f.Name, func(name string) { set.Float64(name, f.Value, f.Usage) }) @@ -278,3 +361,11 @@ func prefixedNames(fullName string) (prefixed string) { } return } + +func withEnvHint(envVar, str string) string { + envText := "" + if envVar != "" { + envText = fmt.Sprintf(" [$%s]", envVar) + } + return str + envText +} From fc16c67be3bb3ed54d832f03870e9d2460f6a14a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 11 Jul 2014 18:13:10 -0400 Subject: [PATCH 08/36] Updating structs to use labels, adding tests for env stuff --- README.md | 27 +++- app_test.go | 6 +- cli_test.go | 18 ++- flag.go | 14 ++- flag_test.go | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 396 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4621310..2453c1a 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,11 @@ Setting and querying flags is simple. ``` go ... app.Flags = []cli.Flag { - cli.StringFlag{"lang", "english", "language for the greeting"}, + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, } app.Action = func(c *cli.Context) { name := "someone" @@ -159,7 +163,26 @@ You can set alternate (or short) names for flags by providing a comma-delimited ``` go app.Flags = []cli.Flag { - cli.StringFlag{"lang, l", "english", "language for the greeting"}, + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, +} +``` + +#### Values from the Environment + +You can also have the default value set from the environment via EnvVar. e.g. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, } ``` diff --git a/app_test.go b/app_test.go index cf0e3d0..26c674a 100644 --- a/app_test.go +++ b/app_test.go @@ -43,7 +43,11 @@ func ExampleAppSubcommand() { Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []cli.Flag{ - cli.StringFlag{"name", "Bob", "Name of the person to greet"}, + cli.StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, }, Action: func(c *cli.Context) { fmt.Println("Hello,", c.String("name")) diff --git a/cli_test.go b/cli_test.go index 30f3c13..71dd5a4 100644 --- a/cli_test.go +++ b/cli_test.go @@ -47,7 +47,11 @@ func ExampleSubcommand() { Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []cli.Flag{ - cli.StringFlag{"name", "Bob", "Name of the person to greet"}, + cli.StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, }, Action: func(c *cli.Context) { println("Hello, ", c.String("name")) @@ -57,7 +61,11 @@ func ExampleSubcommand() { ShortName: "sp", Usage: "sends a greeting in spanish", Flags: []cli.Flag{ - cli.StringFlag{"surname", "Jones", "Surname of the person to greet"}, + cli.StringFlag{ + Name: "surname", + Value: "Jones", + Usage: "Surname of the person to greet", + }, }, Action: func(c *cli.Context) { println("Hola, ", c.String("surname")) @@ -67,7 +75,11 @@ func ExampleSubcommand() { ShortName: "fr", Usage: "sends a greeting in french", Flags: []cli.Flag{ - cli.StringFlag{"nickname", "Stevie", "Nickname of the person to greet"}, + cli.StringFlag{ + Name: "nickname", + Value: "Stevie", + Usage: "Nickname of the person to greet", + }, }, Action: func(c *cli.Context) { println("Bonjour, ", c.String("nickname")) diff --git a/flag.go b/flag.go index 50f192f..60353e2 100644 --- a/flag.go +++ b/flag.go @@ -9,13 +9,21 @@ import ( ) // This flag enables bash-completion for all commands and subcommands -var BashCompletionFlag = BoolFlag{"generate-bash-completion", "", ""} +var BashCompletionFlag = BoolFlag{ + Name: "generate-bash-completion", +} // This flag prints the version for the application -var VersionFlag = BoolFlag{"version, v", "print the version", ""} +var VersionFlag = BoolFlag{ + Name: "version, v", + Usage: "print the version", +} // This flag prints the help for all commands and subcommands -var HelpFlag = BoolFlag{"help, h", "show help", ""} +var HelpFlag = BoolFlag{ + Name: "help, h", + Usage: "show help", +} // Flag is a common interface related to parsing flags in cli. // For more advanced flag parsing techniques, it is recomended that diff --git a/flag_test.go b/flag_test.go index 1c05f01..76dad72 100644 --- a/flag_test.go +++ b/flag_test.go @@ -4,6 +4,7 @@ import ( "github.com/codegangsta/cli" "fmt" + "os" "reflect" "strings" "testing" @@ -52,6 +53,55 @@ func TestStringFlagHelpOutput(t *testing.T) { } } +func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_FOO", "derp") + for _, test := range stringFlagTests { + flag := cli.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) + } + } +} + +var stringSliceFlagTests = []struct { + name string + value *cli.StringSlice + expected string +}{ + {"help", &cli.StringSlice{""}, "--help '--help option --help option'\t"}, + {"h", &cli.StringSlice{""}, "-h '-h option -h option'\t"}, + {"h", &cli.StringSlice{""}, "-h '-h option -h option'\t"}, + {"test", &cli.StringSlice{"Something"}, "--test '--test option --test option'\t"}, +} + +func TestStringSliceFlagHelpOutput(t *testing.T) { + + for _, test := range stringSliceFlagTests { + flag := cli.StringSliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_QWWX", "11,4") + for _, test := range stringSliceFlagTests { + flag := cli.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) + } + } +} + var intFlagTests = []struct { name string expected string @@ -72,6 +122,55 @@ func TestIntFlagHelpOutput(t *testing.T) { } } +func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_BAR", "2") + for _, test := range intFlagTests { + flag := cli.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) + } + } +} + +var intSliceFlagTests = []struct { + name string + 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"}, + {"test", &cli.IntSlice{9}, "--test '--test option --test option'\t"}, +} + +func TestIntSliceFlagHelpOutput(t *testing.T) { + + for _, test := range intSliceFlagTests { + flag := cli.IntSliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_SMURF", "42,3") + for _, test := range intSliceFlagTests { + flag := cli.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) + } + } +} + var float64FlagTests = []struct { name string expected string @@ -92,6 +191,54 @@ func TestFloat64FlagHelpOutput(t *testing.T) { } } +func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_BAZ", "99.4") + for _, test := range float64FlagTests { + flag := cli.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) + } + } +} + +var genericFlagTests = []struct { + name string + 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` "}, +} + +func TestGenericFlagHelpOutput(t *testing.T) { + + for _, test := range genericFlagTests { + flag := cli.GenericFlag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_ZAP", "3") + for _, test := range genericFlagTests { + flag := cli.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) + } + } +} + func TestParseMultiString(t *testing.T) { (&cli.App{ Flags: []cli.Flag{ @@ -108,6 +255,23 @@ func TestParseMultiString(t *testing.T) { }).Run([]string{"run", "-s", "10"}) } +func TestParseMultiStringFromEnv(t *testing.T) { + os.Setenv("APP_COUNT", "20") + (&cli.App{ + Flags: []cli.Flag{ + cli.StringFlag{Name: "count, c", EnvVar: "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{ @@ -124,6 +288,24 @@ func TestParseMultiStringSlice(t *testing.T) { }).Run([]string{"run", "-s", "10", "-s", "20"}) } +func TestParseMultiStringSliceFromEnv(t *testing.T) { + os.Setenv("APP_INTERVALS", "20,30,40") + + (&cli.App{ + Flags: []cli.Flag{ + cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "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{ @@ -141,6 +323,93 @@ func TestParseMultiInt(t *testing.T) { a.Run([]string{"run", "-s", "10"}) } +func TestParseMultiIntFromEnv(t *testing.T) { + os.Setenv("APP_TIMEOUT_SECONDS", "10") + a := cli.App{ + Flags: []cli.Flag{ + cli.IntFlag{Name: "timeout, t", EnvVar: "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{ + cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiIntSliceFromEnv(t *testing.T) { + os.Setenv("APP_INTERVALS", "20,30,40") + + (&cli.App{ + Flags: []cli.Flag{ + cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "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{ + cli.Float64Flag{Name: "serve, s"}, + }, + Action: func(ctx *cli.Context) { + if ctx.Float64("serve") != 10.2 { + t.Errorf("main name not set") + } + if ctx.Float64("s") != 10.2 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "-s", "10.2"}) +} + +func TestParseMultiFloat64FromEnv(t *testing.T) { + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := cli.App{ + Flags: []cli.Flag{ + cli.Float64Flag{Name: "timeout, t", EnvVar: "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{ @@ -158,6 +427,59 @@ func TestParseMultiBool(t *testing.T) { a.Run([]string{"run", "--serve"}) } +func TestParseMultiBoolFromEnv(t *testing.T) { + os.Setenv("APP_DEBUG", "1") + a := cli.App{ + Flags: []cli.Flag{ + cli.BoolFlag{Name: "debug, d", EnvVar: "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{ + cli.BoolTFlag{Name: "serve, s"}, + }, + Action: func(ctx *cli.Context) { + if ctx.BoolT("serve") != true { + t.Errorf("main name not set") + } + if ctx.BoolT("s") != true { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "--serve"}) +} + +func TestParseMultiBoolTFromEnv(t *testing.T) { + os.Setenv("APP_DEBUG", "0") + a := cli.App{ + Flags: []cli.Flag{ + cli.BoolTFlag{Name: "debug, d", EnvVar: "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 { @@ -192,3 +514,21 @@ func TestParseGeneric(t *testing.T) { } a.Run([]string{"run", "-s", "10,20"}) } + +func TestParseGenericFromEnv(t *testing.T) { + os.Setenv("APP_SERVE", "20,30") + a := cli.App{ + Flags: []cli.Flag{ + cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} From bcfb32b8b0ea078afdd72bed4de03ccd59bea40d Mon Sep 17 00:00:00 2001 From: jszwedko Date: Fri, 11 Jul 2014 18:16:19 -0400 Subject: [PATCH 09/36] Updating tests to pass `go vet` Mostly lack of struct field names in literals and one sprintf format specifier mismatch. --- app_test.go | 6 +++--- cli_test.go | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app_test.go b/app_test.go index cf0e3d0..9414c1b 100644 --- a/app_test.go +++ b/app_test.go @@ -43,7 +43,7 @@ func ExampleAppSubcommand() { Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []cli.Flag{ - cli.StringFlag{"name", "Bob", "Name of the person to greet"}, + cli.StringFlag{Name: "name", Value: "Bob", Usage: "Name of the person to greet"}, }, Action: func(c *cli.Context) { fmt.Println("Hello,", c.String("name")) @@ -255,11 +255,11 @@ func TestApp_ParseSliceFlags(t *testing.T) { var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"} if !IntsEquals(parsedIntSlice, expectedIntSlice) { - t.Errorf("%s does not match %s", parsedIntSlice, expectedIntSlice) + t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice) } if !StrsEquals(parsedStringSlice, expectedStringSlice) { - t.Errorf("%s does not match %s", parsedStringSlice, expectedStringSlice) + t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice) } } diff --git a/cli_test.go b/cli_test.go index 30f3c13..4d7bd84 100644 --- a/cli_test.go +++ b/cli_test.go @@ -1,8 +1,9 @@ package cli_test import ( - "github.com/codegangsta/cli" "os" + + "github.com/codegangsta/cli" ) func Example() { @@ -47,7 +48,7 @@ func ExampleSubcommand() { Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []cli.Flag{ - cli.StringFlag{"name", "Bob", "Name of the person to greet"}, + cli.StringFlag{Name: "name", Value: "Bob", Usage: "Name of the person to greet"}, }, Action: func(c *cli.Context) { println("Hello, ", c.String("name")) @@ -57,7 +58,7 @@ func ExampleSubcommand() { ShortName: "sp", Usage: "sends a greeting in spanish", Flags: []cli.Flag{ - cli.StringFlag{"surname", "Jones", "Surname of the person to greet"}, + cli.StringFlag{Name: "surname", Value: "Jones", Usage: "Surname of the person to greet"}, }, Action: func(c *cli.Context) { println("Hola, ", c.String("surname")) @@ -67,7 +68,7 @@ func ExampleSubcommand() { ShortName: "fr", Usage: "sends a greeting in french", Flags: []cli.Flag{ - cli.StringFlag{"nickname", "Stevie", "Nickname of the person to greet"}, + cli.StringFlag{Name: "nickname", Value: "Stevie", Usage: "Nickname of the person to greet"}, }, Action: func(c *cli.Context) { println("Bonjour, ", c.String("nickname")) From c99454b374ae684d01fb654b41d82c2c2584a38d Mon Sep 17 00:00:00 2001 From: jszwedko Date: Fri, 11 Jul 2014 18:17:30 -0400 Subject: [PATCH 10/36] Adding struct field names to examples in README Idiomatic Go --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4621310..59806f4 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Setting and querying flags is simple. ``` go ... app.Flags = []cli.Flag { - cli.StringFlag{"lang", "english", "language for the greeting"}, + cli.StringFlag{Name: "lang", Value: "english", Usage: "language for the greeting"}, } app.Action = func(c *cli.Context) { name := "someone" @@ -159,7 +159,7 @@ You can set alternate (or short) names for flags by providing a comma-delimited ``` go app.Flags = []cli.Flag { - cli.StringFlag{"lang, l", "english", "language for the greeting"}, + cli.StringFlag{Name: "lang, l", Value: "english", Usage: "language for the greeting"}, } ``` From 48869e13a4e5d5695de22e4232d516b1d973aca9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 11 Jul 2014 18:30:16 -0400 Subject: [PATCH 11/36] Addressing `go vet` offenses --- flag_test.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/flag_test.go b/flag_test.go index 76dad72..4103236 100644 --- a/flag_test.go +++ b/flag_test.go @@ -71,10 +71,26 @@ var stringSliceFlagTests = []struct { value *cli.StringSlice expected string }{ - {"help", &cli.StringSlice{""}, "--help '--help option --help option'\t"}, - {"h", &cli.StringSlice{""}, "-h '-h option -h option'\t"}, - {"h", &cli.StringSlice{""}, "-h '-h option -h option'\t"}, - {"test", &cli.StringSlice{"Something"}, "--test '--test option --test option'\t"}, + {"help", func() *cli.StringSlice { + s := &cli.StringSlice{} + s.Set("") + return s + }(), "--help '--help option --help option'\t"}, + {"h", func() *cli.StringSlice { + s := &cli.StringSlice{} + s.Set("") + return s + }(), "-h '-h option -h option'\t"}, + {"h", func() *cli.StringSlice { + s := &cli.StringSlice{} + s.Set("") + return s + }(), "-h '-h option -h option'\t"}, + {"test", func() *cli.StringSlice { + s := &cli.StringSlice{} + s.Set("Something") + return s + }(), "--test '--test option --test option'\t"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { @@ -143,7 +159,11 @@ var intSliceFlagTests = []struct { {"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", &cli.IntSlice{9}, "--test '--test option --test option'\t"}, + {"test", func() *cli.IntSlice { + i := &cli.IntSlice{} + i.Set("9") + return i + }(), "--test '--test option --test option'\t"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { From 4a645835f0602f2d510484e5baae8595bbb442c3 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 13 Jul 2014 14:16:30 +0100 Subject: [PATCH 12/36] Add HideHelp flag in App and Command --- app.go | 10 ++++++---- command.go | 15 ++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index 9ba043e..a4e3ff7 100644 --- a/app.go +++ b/app.go @@ -22,6 +22,8 @@ type App struct { Flags []Flag // Boolean to enable bash completion commands EnableBashCompletion bool + // Boolean to hide built-in help command + HideHelp bool // An action to execute when the bash-completion flag is set BashComplete func(context *Context) // An action to execute before any subcommands are run, but after the context is ready @@ -66,8 +68,9 @@ 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 { // append help to commands - if a.Command(helpCommand.Name) == nil { + if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) + a.appendFlag(HelpFlag) } //append version/help flags @@ -75,7 +78,6 @@ func (a *App) Run(arguments []string) error { a.appendFlag(BashCompletionFlag) } a.appendFlag(VersionFlag) - a.appendFlag(HelpFlag) // parse flags set := flagSet(a.Name, a.Flags) @@ -135,8 +137,9 @@ func (a *App) Run(arguments []string) error { func (a *App) RunAsSubcommand(ctx *Context) error { // append help to commands if len(a.Commands) > 0 { - if a.Command(helpCommand.Name) == nil { + if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) + a.appendFlag(HelpFlag) } } @@ -144,7 +147,6 @@ func (a *App) RunAsSubcommand(ctx *Context) error { if a.EnableBashCompletion { a.appendFlag(BashCompletionFlag) } - a.appendFlag(HelpFlag) // parse flags set := flagSet(a.Name, a.Flags) diff --git a/command.go b/command.go index 9d8fff4..dcc8de5 100644 --- a/command.go +++ b/command.go @@ -29,6 +29,8 @@ type Command struct { Flags []Flag // Treat all flags as normal arguments if true SkipFlagParsing bool + // Boolean to hide built-in help command + HideHelp bool } // Invokes the command given the context, parses ctx.Args() to generate command-specific flags @@ -38,11 +40,13 @@ func (c Command) Run(ctx *Context) error { return c.startApp(ctx) } - // append help to flags - c.Flags = append( - c.Flags, - HelpFlag, - ) + if !c.HideHelp { + // append help to flags + c.Flags = append( + c.Flags, + HelpFlag, + ) + } if ctx.App.EnableBashCompletion { c.Flags = append(c.Flags, BashCompletionFlag) @@ -117,6 +121,7 @@ func (c Command) startApp(ctx *Context) error { // set the flags and commands app.Commands = c.Subcommands app.Flags = c.Flags + app.HideHelp = c.HideHelp // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion From d6d4e6448b03fcbff51263649b9b5a012acac650 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 13 Jul 2014 14:27:44 +0100 Subject: [PATCH 13/36] Hide "Options" sections in help, if no flags are defined --- help.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/help.go b/help.go index 7c04005..845e805 100644 --- a/help.go +++ b/help.go @@ -14,17 +14,17 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} [global options] command [command options] [arguments...] + {{.Name}} {{ if .Flags }}[global options] {{ end }}command{{ if .Flags }} [command options]{{ end }} [arguments...] VERSION: {{.Version}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}} + {{end}}{{ if .Flags }} GLOBAL OPTIONS: {{range .Flags}}{{.}} - {{end}} + {{end}}{{ end }} ` // The text template for the command help topic. @@ -34,14 +34,14 @@ var CommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - command {{.Name}} [command options] [arguments...] + command {{.Name}}{{ if .Flags }} [command options]{{ end }} [arguments...] DESCRIPTION: - {{.Description}} + {{.Description}}{{ if .Flags }} OPTIONS: {{range .Flags}}{{.}} - {{end}} + {{end}}{{ end }} ` // The text template for the subcommand help topic. @@ -51,14 +51,14 @@ var SubcommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} [global options] command [command options] [arguments...] + {{.Name}} [global options] command{{ if .Flags }} [command options]{{ end }} [arguments...] COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}} + {{end}}{{ if .Flags }} OPTIONS: {{range .Flags}}{{.}} - {{end}} + {{end}}{{ end }} ` var helpCommand = Command{ From bc02933ea4b153fe92a4517a2891b084fa479141 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 13 Jul 2014 14:29:30 +0100 Subject: [PATCH 14/36] Fix Subcommand help text --- help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.go b/help.go index 845e805..ccca036 100644 --- a/help.go +++ b/help.go @@ -51,7 +51,7 @@ var SubcommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} [global options] command{{ if .Flags }} [command options]{{ end }} [arguments...] + {{.Name}} command{{ if .Flags }} [command options]{{ end }} [arguments...] COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} From 9e036e08f164beaa5062c6ff959ceecd88709459 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 13 Jul 2014 18:52:30 +0100 Subject: [PATCH 15/36] Fix tests --- app_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app_test.go b/app_test.go index 9414c1b..a915624 100644 --- a/app_test.go +++ b/app_test.go @@ -84,12 +84,10 @@ func ExampleAppHelp() { // describeit - use it to see a description // // USAGE: - // command describeit [command options] [arguments...] + // command describeit [arguments...] // // DESCRIPTION: // This is how we describe describeit the function - // - // OPTIONS: } func ExampleAppBashComplete() { From 6f8cfa703a6b5eabeef4b957d14cfc200a16c908 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 13 Jul 2014 15:18:45 +0100 Subject: [PATCH 16/36] Add Args.Swap command for swapping arguments The usecase: my-cli user set Being able to swap with set argument, we can have nested subcommands while preserving all variable arguments along the way. --- context.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/context.go b/context.go index b2c51bb..1e023ce 100644 --- a/context.go +++ b/context.go @@ -140,6 +140,15 @@ func (a Args) Present() bool { return len(a) != 0 } +// Swaps arguments at the given indexes +func (a Args) Swap(from, to int) error { + if from >= len(a) || to >= len(a) { + return errors.New("index out of range") + } + a[from], a[to] = a[to], a[from] + return nil +} + func lookupInt(name string, set *flag.FlagSet) int { f := set.Lookup(name) if f != nil { From 449f261ee9faccc374c97ed04bb58def80ac34ed Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sun, 13 Jul 2014 17:26:20 +0100 Subject: [PATCH 17/36] Add App.RunAndExitOnError shortcut --- app.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app.go b/app.go index 9ba043e..2f4bd34 100644 --- a/app.go +++ b/app.go @@ -131,6 +131,14 @@ func (a *App) Run(arguments []string) error { return nil } +// 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)) + os.Exit(1) + } +} + // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) error { // append help to commands From 9e5f47524bd6569c08a45b2f07657e19cdcd2b87 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 14 Jul 2014 13:48:52 -0400 Subject: [PATCH 18/36] Run `go vet` as part of Travis build --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2379c61..baf46ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,6 @@ language: go go: 1.1 + +script: +- go vet ./... +- go test -v ./... From 1880853f15b1f0534c99b141b10eaa48613e8ce5 Mon Sep 17 00:00:00 2001 From: Kyle Allan Date: Thu, 24 Jul 2014 23:48:10 -0700 Subject: [PATCH 19/36] copy the CommandNotFound when running as a subcommand --- app.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.go b/app.go index e193b82..4ae0c5e 100644 --- a/app.go +++ b/app.go @@ -151,6 +151,9 @@ func (a *App) RunAsSubcommand(ctx *Context) error { } } + // add the App's CommandNotFound + a.CommandNotFound = ctx.App.CommandNotFound + // append flags if a.EnableBashCompletion { a.appendFlag(BashCompletionFlag) From 156cd267e5ff2ed95305ffec13926c80afb388f7 Mon Sep 17 00:00:00 2001 From: Kyle Allan Date: Fri, 25 Jul 2014 10:06:04 -0700 Subject: [PATCH 20/36] move to CommandNotFound copy to startApp --- app.go | 3 --- command.go | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 4ae0c5e..e193b82 100644 --- a/app.go +++ b/app.go @@ -151,9 +151,6 @@ func (a *App) RunAsSubcommand(ctx *Context) error { } } - // add the App's CommandNotFound - a.CommandNotFound = ctx.App.CommandNotFound - // append flags if a.EnableBashCompletion { a.appendFlag(BashCompletionFlag) diff --git a/command.go b/command.go index dcc8de5..5622b38 100644 --- a/command.go +++ b/command.go @@ -118,6 +118,9 @@ func (c Command) startApp(ctx *Context) error { app.Usage = c.Usage } + // set CommandNotFound + app.CommandNotFound = ctx.App.CommandNotFound + // set the flags and commands app.Commands = c.Subcommands app.Flags = c.Flags From e38bac82251a8f0bef1ffa40900f38c63294e7b1 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sat, 26 Jul 2014 21:47:36 +0100 Subject: [PATCH 21/36] Print author --- app.go | 2 -- help.go | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index e193b82..66e541c 100644 --- a/app.go +++ b/app.go @@ -60,8 +60,6 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), - Author: "Author", - Email: "unknown@email", } } diff --git a/help.go b/help.go index ccca036..7486030 100644 --- a/help.go +++ b/help.go @@ -17,7 +17,11 @@ USAGE: {{.Name}} {{ if .Flags }}[global options] {{ end }}command{{ if .Flags }} [command options]{{ end }} [arguments...] VERSION: - {{.Version}} + {{.Version}}{{if or .Author .Email}} + +AUTHOR:{{if .Author}} + {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} + {{.Email}}{{end}}{{end}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} From fcc1613bb54edd49d722d99b553b470638bb8e71 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sat, 26 Jul 2014 21:53:05 +0100 Subject: [PATCH 22/36] Do not show empty description section --- help.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/help.go b/help.go index ccca036..85af1d9 100644 --- a/help.go +++ b/help.go @@ -34,10 +34,10 @@ var CommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - command {{.Name}}{{ if .Flags }} [command options]{{ end }} [arguments...] + command {{.Name}}{{ if .Flags }} [command options]{{ end }} [arguments...]{{if .Description}} DESCRIPTION: - {{.Description}}{{ if .Flags }} + {{.Description}}{{end}}{{ if .Flags }} OPTIONS: {{range .Flags}}{{.}} From 527fbe0671e86bcd611e743499cb1c26dae8ad58 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Sat, 26 Jul 2014 21:54:50 +0100 Subject: [PATCH 23/36] Unify template formatting --- help.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/help.go b/help.go index 85af1d9..89c45e7 100644 --- a/help.go +++ b/help.go @@ -14,17 +14,17 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} {{ if .Flags }}[global options] {{ end }}command{{ if .Flags }} [command options]{{ end }} [arguments...] + {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] VERSION: {{.Version}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}}{{ if .Flags }} + {{end}}{{if .Flags}} GLOBAL OPTIONS: {{range .Flags}}{{.}} - {{end}}{{ end }} + {{end}}{{end}} ` // The text template for the command help topic. @@ -34,10 +34,10 @@ var CommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - command {{.Name}}{{ if .Flags }} [command options]{{ end }} [arguments...]{{if .Description}} + command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{ if .Flags }} + {{.Description}}{{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} @@ -51,14 +51,14 @@ var SubcommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} command{{ if .Flags }} [command options]{{ end }} [arguments...] + {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...] COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}}{{ if .Flags }} + {{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} - {{end}}{{ end }} + {{end}}{{end}} ` var helpCommand = Command{ From 8b5fb401314fd92dc5c82284639e0adba6a8ec4d Mon Sep 17 00:00:00 2001 From: Jeremy Saenz Date: Sat, 2 Aug 2014 12:45:44 -0700 Subject: [PATCH 24/36] Added contributor guidelines --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 2453c1a..0bd9105 100644 --- a/README.md +++ b/README.md @@ -276,5 +276,12 @@ setting the PROG variable to the name of your program: `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` +## 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 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 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 me up over email and we will get it figured out. + ## About cli.go is written by none other than the [Code Gangsta](http://codegangsta.io) From 0e069d0611d7c97d95637ae5662bb095e0b46677 Mon Sep 17 00:00:00 2001 From: Jeremy Saenz Date: Sat, 2 Aug 2014 12:46:57 -0700 Subject: [PATCH 25/36] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0bd9105..b30917a 100644 --- a/README.md +++ b/README.md @@ -279,9 +279,9 @@ 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 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 merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. +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 feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit me up over email and we will get it figured out. +If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. ## About cli.go is written by none other than the [Code Gangsta](http://codegangsta.io) From 98af61a59f4f7f16f27a08cf427e8c373264300f Mon Sep 17 00:00:00 2001 From: Ghislain Gt Date: Sat, 2 Aug 2014 22:32:32 +0100 Subject: [PATCH 26/36] Add time.Duration flag type --- context.go | 23 +++++++++++++++++++++++ context_test.go | 11 ++++++++++- flag.go | 31 +++++++++++++++++++++++++++++++ flag_test.go | 37 +++++++++++++++++++++++++++++++++++-- 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index def858c..8b44148 100644 --- a/context.go +++ b/context.go @@ -5,6 +5,7 @@ import ( "flag" "strconv" "strings" + "time" ) // Context is a type that is passed through to @@ -29,6 +30,11 @@ func (c *Context) Int(name string) int { return lookupInt(name, c.flagSet) } +// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists +func (c *Context) Duration(name string) time.Duration { + return lookupDuration(name, c.flagSet) +} + // Looks up the value of a local float64 flag, returns 0 if no float64 flag exists func (c *Context) Float64(name string) float64 { return lookupFloat64(name, c.flagSet) @@ -69,6 +75,11 @@ func (c *Context) GlobalInt(name string) int { return lookupInt(name, c.globalSet) } +// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists +func (c *Context) GlobalDuration(name string) time.Duration { + return lookupDuration(name, c.globalSet) +} + // Looks up the value of a global bool flag, returns false if no bool flag exists func (c *Context) GlobalBool(name string) bool { return lookupBool(name, c.globalSet) @@ -174,6 +185,18 @@ func lookupInt(name string, set *flag.FlagSet) int { return 0 } +func lookupDuration(name string, set *flag.FlagSet) time.Duration { + f := set.Lookup(name) + if f != nil { + val, err := time.ParseDuration(f.Value.String()) + if err == nil { + return val + } + } + + return 0 +} + func lookupFloat64(name string, set *flag.FlagSet) float64 { f := set.Lookup(name) if f != nil { diff --git a/context_test.go b/context_test.go index 89041b9..b2d2412 100644 --- a/context_test.go +++ b/context_test.go @@ -2,8 +2,10 @@ package cli_test import ( "flag" - "github.com/codegangsta/cli" "testing" + "time" + + "github.com/codegangsta/cli" ) func TestNewContext(t *testing.T) { @@ -26,6 +28,13 @@ func TestContext_Int(t *testing.T) { expect(t, c.Int("myflag"), 12) } +func TestContext_Duration(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Duration("myflag", time.Duration(12*time.Second), "doc") + c := cli.NewContext(nil, set, set) + expect(t, c.Duration("myflag"), time.Duration(12*time.Second)) +} + func TestContext_String(t *testing.T) { set := flag.NewFlagSet("test", 0) set.String("myflag", "hello world", "doc") diff --git a/flag.go b/flag.go index 60353e2..b30bca3 100644 --- a/flag.go +++ b/flag.go @@ -6,6 +6,7 @@ import ( "os" "strconv" "strings" + "time" ) // This flag enables bash-completion for all commands and subcommands @@ -318,6 +319,36 @@ func (f IntFlag) getName() string { return f.Name } +type DurationFlag struct { + Name string + Value time.Duration + Usage string + EnvVar string +} + +func (f DurationFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +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 + } + } + } + + eachName(f.Name, func(name string) { + set.Duration(name, f.Value, f.Usage) + }) +} + +func (f DurationFlag) getName() string { + return f.Name +} + type Float64Flag struct { Name string Value float64 diff --git a/flag_test.go b/flag_test.go index 4103236..bc5059c 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1,13 +1,13 @@ package cli_test import ( - "github.com/codegangsta/cli" - "fmt" "os" "reflect" "strings" "testing" + + "github.com/codegangsta/cli" ) var boolFlagTests = []struct { @@ -151,6 +151,39 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { } } +var durationFlagTests = []struct { + name string + expected string +}{ + {"help", "--help '0'\t"}, + {"h", "-h '0'\t"}, +} + +func TestDurationFlagHelpOutput(t *testing.T) { + + for _, test := range durationFlagTests { + flag := cli.DurationFlag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_BAR", "2h3m6s") + for _, test := range durationFlagTests { + flag := cli.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) + } + } +} + var intSliceFlagTests = []struct { name string value *cli.IntSlice From 089def51d86d5f09b1153d01976cd976bbf8822d Mon Sep 17 00:00:00 2001 From: Josh Mervine Date: Mon, 4 Aug 2014 11:30:13 -0700 Subject: [PATCH 27/36] Tweaking tests, removing local import. --- app_test.go | 1 - command_test.go | 3 ++- context_test.go | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app_test.go b/app_test.go index 32807bf..81d1174 100644 --- a/app_test.go +++ b/app_test.go @@ -1,7 +1,6 @@ package cli_test import ( - "." "fmt" "os" "testing" diff --git a/command_test.go b/command_test.go index e601fc1..c0f556a 100644 --- a/command_test.go +++ b/command_test.go @@ -1,9 +1,10 @@ package cli_test import ( - "." "flag" "testing" + + "github.com/codegangsta/cli" ) func TestCommandDoNotIgnoreFlags(t *testing.T) { diff --git a/context_test.go b/context_test.go index 70bf2ad..b2d2412 100644 --- a/context_test.go +++ b/context_test.go @@ -1,7 +1,6 @@ package cli_test import ( - "." "flag" "testing" "time" From 023450545efc538ebdd55c50b3b48996d7949faf Mon Sep 17 00:00:00 2001 From: Tony Date: Sun, 24 Aug 2014 12:28:10 -0400 Subject: [PATCH 28/36] Minor README tweaks --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b30917a..5c83df6 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,17 @@ http://godoc.org/github.com/codegangsta/cli ## 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! +**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: +To install `cli.go`, simply run: ``` $ go get github.com/codegangsta/cli ``` -Make sure your PATH includes to the `$GOPATH/bin` directory so your commands can be easily used: +Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: ``` export PATH=$PATH:$GOPATH/bin ``` @@ -122,7 +122,7 @@ GLOBAL OPTIONS ``` ### Arguments -You can lookup arguments by calling the `Args` function on cli.Context. +You can lookup arguments by calling the `Args` function on `cli.Context`. ``` go ... @@ -159,7 +159,7 @@ app.Action = func(c *cli.Context) { #### Alternate Names -You can set alternate (or short) names for flags by providing a comma-delimited list for the Name. e.g. +You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. ``` go app.Flags = []cli.Flag { @@ -173,7 +173,7 @@ app.Flags = []cli.Flag { #### Values from the Environment -You can also have the default value set from the environment via EnvVar. e.g. +You can also have the default value set from the environment via `EnvVar`. e.g. ``` go app.Flags = []cli.Flag { @@ -237,8 +237,8 @@ app.Commands = []cli.Command{ ### Bash Completion -You can enable completion commands by setting the EnableBashCompletion -flag on the App object. By default, this setting will only auto-complete to +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 @@ -270,8 +270,8 @@ app.Commands = []cli.Command{ #### To Enable -Source the autocomplete/bash_autocomplete file in your .bashrc file while -setting the PROG variable to the name of your program: +Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while +setting the `PROG` variable to the name of your program: `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` From 9610ab172974bc745dc53e89ae1a443f916f0ea5 Mon Sep 17 00:00:00 2001 From: Yushi Nakai Date: Mon, 15 Sep 2014 21:03:35 +0000 Subject: [PATCH 29/36] Fix example code --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c83df6..fe4652c 100644 --- a/README.md +++ b/README.md @@ -260,7 +260,7 @@ app.Commands = []cli.Command{ return } for _, t := range tasks { - println(t) + fmt.Println(t) } }, } From 6db418e461936303d1082600f0efe23cd57f8c2c Mon Sep 17 00:00:00 2001 From: Artem Nezvigin Date: Thu, 2 Oct 2014 15:16:29 -0700 Subject: [PATCH 30/36] Add Context.GlobalFlagNames() --- context.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/context.go b/context.go index 8b44148..e4b0da6 100644 --- a/context.go +++ b/context.go @@ -128,6 +128,18 @@ func (c *Context) FlagNames() (names []string) { return } +// 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] + if name == "help" || name == "version" { + continue + } + names = append(names, name) + } + return +} + type Args []string // Returns the command line arguments associated with the context. From b490b5e35d7e246da681819c9e6bf80758222b97 Mon Sep 17 00:00:00 2001 From: Jesse Howarth Date: Tue, 11 Nov 2014 12:48:07 -0800 Subject: [PATCH 31/36] Add a function to determine if a global flag is set. --- context.go | 24 ++++++++++++++++++------ context_test.go | 23 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/context.go b/context.go index 8b44148..d55eae1 100644 --- a/context.go +++ b/context.go @@ -13,11 +13,12 @@ import ( // can be used to retrieve context-specific Args and // parsed command-line options. type Context struct { - App *App - Command Command - flagSet *flag.FlagSet - globalSet *flag.FlagSet - setFlags map[string]bool + App *App + Command Command + flagSet *flag.FlagSet + globalSet *flag.FlagSet + setFlags map[string]bool + globalSetFlags map[string]bool } // Creates a new context. For use in when invoking an App or Command action. @@ -105,7 +106,7 @@ func (c *Context) GlobalGeneric(name string) interface{} { return lookupGeneric(name, c.globalSet) } -// Determines if the flag was actually set exists +// Determines if the flag was actually set func (c *Context) IsSet(name string) bool { if c.setFlags == nil { c.setFlags = make(map[string]bool) @@ -116,6 +117,17 @@ func (c *Context) IsSet(name string) bool { return c.setFlags[name] == true } +// Determines if the global flag was actually set +func (c *Context) GlobalIsSet(name string) bool { + if c.globalSetFlags == nil { + c.globalSetFlags = make(map[string]bool) + c.globalSet.Visit(func(f *flag.Flag) { + c.globalSetFlags[f.Name] = true + }) + } + return c.globalSetFlags[name] == true +} + // Returns a slice of flag names used in this context. func (c *Context) FlagNames() (names []string) { for _, flag := range c.Command.Flags { diff --git a/context_test.go b/context_test.go index b2d2412..198c10b 100644 --- a/context_test.go +++ b/context_test.go @@ -69,9 +69,30 @@ func TestContext_IsSet(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") set.String("otherflag", "hello world", "doc") - c := cli.NewContext(nil, set, set) + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("myflagGlobal", true, "doc") + c := cli.NewContext(nil, set, globalSet) set.Parse([]string{"--myflag", "bat", "baz"}) + globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) expect(t, c.IsSet("myflag"), true) expect(t, c.IsSet("otherflag"), false) expect(t, c.IsSet("bogusflag"), false) + expect(t, c.IsSet("myflagGlobal"), false) +} + +func TestContext_GlobalIsSet(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + set.String("otherflag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("myflagGlobal", true, "doc") + globalSet.Bool("myflagGlobalUnset", true, "doc") + c := cli.NewContext(nil, set, globalSet) + set.Parse([]string{"--myflag", "bat", "baz"}) + globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) + expect(t, c.GlobalIsSet("myflag"), false) + expect(t, c.GlobalIsSet("otherflag"), false) + expect(t, c.GlobalIsSet("bogusflag"), false) + expect(t, c.GlobalIsSet("myflagGlobal"), true) + expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) } From a6bafbe9a52ad22c26a3a5c3091724613f6c54d4 Mon Sep 17 00:00:00 2001 From: Jesse Howarth Date: Tue, 11 Nov 2014 12:50:40 -0800 Subject: [PATCH 32/36] Add additional test --- context_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/context_test.go b/context_test.go index 198c10b..7c9a443 100644 --- a/context_test.go +++ b/context_test.go @@ -95,4 +95,5 @@ func TestContext_GlobalIsSet(t *testing.T) { expect(t, c.GlobalIsSet("bogusflag"), false) expect(t, c.GlobalIsSet("myflagGlobal"), true) expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) + expect(t, c.GlobalIsSet("bogusGlobal"), false) } From 179c7a5f7c4bc8821964dbdf587b317b34092acd Mon Sep 17 00:00:00 2001 From: Peter Bourgon Date: Wed, 12 Nov 2014 12:38:58 +0100 Subject: [PATCH 33/36] Make -v, --version optional --- app.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 66e541c..f4c4af8 100644 --- a/app.go +++ b/app.go @@ -24,6 +24,8 @@ type App struct { EnableBashCompletion bool // Boolean to hide built-in help command HideHelp bool + // Boolean to hide built-in version flag + HideVersion bool // An action to execute when the bash-completion flag is set BashComplete func(context *Context) // An action to execute before any subcommands are run, but after the context is ready @@ -75,7 +77,10 @@ func (a *App) Run(arguments []string) error { if a.EnableBashCompletion { a.appendFlag(BashCompletionFlag) } - a.appendFlag(VersionFlag) + + if !a.HideVersion { + a.appendFlag(VersionFlag) + } // parse flags set := flagSet(a.Name, a.Flags) From 993877b447ae2b97a14787f2a25ce4d419cecb9f Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Thu, 13 Nov 2014 06:27:03 -0800 Subject: [PATCH 34/36] Clarify example in README. Fixes #149 --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe4652c..cae6f30 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,9 @@ Running this already gives you a ton of functionality, plus support for things l Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness! +Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it: + ``` go -/* greet.go */ package main import ( @@ -84,7 +85,7 @@ func main() { app.Action = func(c *cli.Context) { println("Hello friend!") } - + app.Run(os.Args) } ``` From 59ef9567ae831e6528679ca98a07d37d0630322d Mon Sep 17 00:00:00 2001 From: pivaldi Date: Wed, 19 Nov 2014 11:05:01 +0100 Subject: [PATCH 35/36] Fix bash completion for deep level subcommands --- help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.go b/help.go index 5020cb6..5667d3a 100644 --- a/help.go +++ b/help.go @@ -206,7 +206,7 @@ func checkSubcommandHelp(c *Context) bool { } func checkCompletions(c *Context) bool { - if c.GlobalBool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { + if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { ShowCompletions(c) return true } From 9908e96513e5a94de37004098a3974a567f18111 Mon Sep 17 00:00:00 2001 From: Jeremy Saenz Date: Sun, 23 Nov 2014 13:46:56 -0800 Subject: [PATCH 36/36] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index cae6f30..e0fdace 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,3 @@ Feel free to put up a pull request to fix a bug or maybe add a feature. I will g 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 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. - -## About -cli.go is written by none other than the [Code Gangsta](http://codegangsta.io)