From 683e911d61b0be794eec4200056f0451b3976622 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sun, 31 Aug 2014 11:40:31 -0700 Subject: [PATCH 001/381] Return error from app and command actions. See #66. This introduces a significant breaking change, and I don't expect it to be merged off-hand. I do think that it's worth discussion, since it seems like a very idiomatic choice in terms of how errors would be handled. A similar backwards-incompatible change was introduced in e6e64114, allowing the app's Run method to return an error. --- app.go | 5 ++--- app_test.go | 36 ++++++++++++++++++++++++------------ cli_test.go | 18 ++++++++++++------ command.go | 5 ++--- command_test.go | 4 ++-- flag_test.go | 48 ++++++++++++++++++++++++++++++++---------------- help.go | 6 ++++-- 7 files changed, 78 insertions(+), 44 deletions(-) diff --git a/app.go b/app.go index 66e541c..145611b 100644 --- a/app.go +++ b/app.go @@ -30,7 +30,7 @@ type App struct { // If a non-nil error is returned, no subcommands are run Before func(context *Context) error // The action to execute when no subcommands are specified - Action func(context *Context) + Action func(context *Context) error // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) // Compilation date @@ -127,8 +127,7 @@ func (a *App) Run(arguments []string) error { } // Run default Action - a.Action(context) - return nil + return a.Action(context) } // Another entry point to the cli app, takes care of passing arguments and error handling diff --git a/app_test.go b/app_test.go index 81d1174..cefa5fd 100644 --- a/app_test.go +++ b/app_test.go @@ -17,8 +17,9 @@ func ExampleApp() { app.Flags = []cli.Flag{ cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) error { fmt.Printf("Hello %v\n", c.String("name")) + return nil } app.Run(os.Args) // Output: @@ -49,8 +50,9 @@ func ExampleAppSubcommand() { Usage: "Name of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Println("Hello,", c.String("name")) + return nil }, }, }, @@ -77,8 +79,9 @@ func ExampleAppHelp() { ShortName: "d", Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Printf("i like to describe things") + return nil }, }, } @@ -107,15 +110,17 @@ func ExampleAppBashComplete() { ShortName: "d", Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Printf("i like to describe things") + return nil }, }, { Name: "next", Usage: "next example", Description: "more stuff to see when generating bash completion", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Printf("the next example") + return nil }, }, } @@ -133,8 +138,9 @@ func TestApp_Run(t *testing.T) { s := "" app := cli.NewApp() - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) error { s = s + c.Args().First() + return nil } err := app.Run([]string{"command", "foo"}) @@ -179,9 +185,10 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { Flags: []cli.Flag{ cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { parsedOption = c.String("option") firstArg = c.Args().First() + return nil }, } app.Commands = []cli.Command{command} @@ -199,8 +206,9 @@ func TestApp_Float64Flag(t *testing.T) { app.Flags = []cli.Flag{ cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, } - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) error { meters = c.Float64("height") + return nil } app.Run([]string{"", "--height", "1.93"}) @@ -219,11 +227,12 @@ func TestApp_ParseSliceFlags(t *testing.T) { cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"}, cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"}, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") parsedOption = c.String("option") firstArg = c.Args().First() + return nil }, } app.Commands = []cli.Command{command} @@ -285,8 +294,9 @@ func TestApp_BeforeFunc(t *testing.T) { app.Commands = []cli.Command{ cli.Command{ Name: "sub", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { subcommandRun = true + return nil }, }, } @@ -381,8 +391,9 @@ func TestAppCommandNotFound(t *testing.T) { app.Commands = []cli.Command{ cli.Command{ Name: "bar", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { subcommandRun = true + return nil }, }, } @@ -407,10 +418,11 @@ func TestGlobalFlagsInSubcommands(t *testing.T) { Subcommands: []cli.Command{ { Name: "bar", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { if c.GlobalBool("debug") { subcommandRun = true } + return nil }, }, }, diff --git a/cli_test.go b/cli_test.go index 879a793..e330810 100644 --- a/cli_test.go +++ b/cli_test.go @@ -15,16 +15,18 @@ func Example() { Name: "add", ShortName: "a", Usage: "add a task to the list", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { println("added task: ", c.Args().First()) + return nil }, }, { Name: "complete", ShortName: "c", Usage: "complete a task on the list", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { println("completed task: ", c.Args().First()) + return nil }, }, } @@ -54,8 +56,9 @@ func ExampleSubcommand() { Usage: "Name of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { println("Hello, ", c.String("name")) + return nil }, }, { Name: "spanish", @@ -68,8 +71,9 @@ func ExampleSubcommand() { Usage: "Surname of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { println("Hola, ", c.String("surname")) + return nil }, }, { Name: "french", @@ -82,16 +86,18 @@ func ExampleSubcommand() { Usage: "Nickname of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { println("Bonjour, ", c.String("nickname")) + return nil }, }, }, }, { Name: "bye", Usage: "says goodbye", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { println("bye") + return nil }, }, } diff --git a/command.go b/command.go index 5622b38..362db59 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 // The function to call when this command is invoked - Action func(context *Context) + Action func(context *Context) error // List of child commands Subcommands []Command // List of flags to parse @@ -98,8 +98,7 @@ func (c Command) Run(ctx *Context) error { return nil } context.Command = c - c.Action(context) - return nil + return c.Action(context) } // Returns true if Command.Name or Command.ShortName matches given name diff --git a/command_test.go b/command_test.go index c0f556a..06c2203 100644 --- a/command_test.go +++ b/command_test.go @@ -20,7 +20,7 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) { ShortName: "tc", Usage: "this is for testing", Description: "testing", - Action: func(_ *cli.Context) {}, + Action: func(_ *cli.Context) error { return nil }, } err := command.Run(c) @@ -40,7 +40,7 @@ func TestCommandIgnoreFlags(t *testing.T) { ShortName: "tc", Usage: "this is for testing", Description: "testing", - Action: func(_ *cli.Context) {}, + Action: func(_ *cli.Context) error { return nil }, SkipFlagParsing: true, } err := command.Run(c) diff --git a/flag_test.go b/flag_test.go index bc5059c..9aa1cff 100644 --- a/flag_test.go +++ b/flag_test.go @@ -297,13 +297,14 @@ func TestParseMultiString(t *testing.T) { Flags: []cli.Flag{ cli.StringFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if ctx.String("serve") != "10" { t.Errorf("main name not set") } if ctx.String("s") != "10" { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run", "-s", "10"}) } @@ -314,13 +315,14 @@ func TestParseMultiStringFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if ctx.String("count") != "20" { t.Errorf("main name not set") } if ctx.String("c") != "20" { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run"}) } @@ -330,13 +332,14 @@ func TestParseMultiStringSlice(t *testing.T) { Flags: []cli.Flag{ cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -348,13 +351,14 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { 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") } + return nil }, }).Run([]string{"run"}) } @@ -364,13 +368,14 @@ func TestParseMultiInt(t *testing.T) { Flags: []cli.Flag{ cli.IntFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if ctx.Int("serve") != 10 { t.Errorf("main name not set") } if ctx.Int("s") != 10 { t.Errorf("short name not set") } + return nil }, } a.Run([]string{"run", "-s", "10"}) @@ -382,13 +387,14 @@ func TestParseMultiIntFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } if ctx.Int("t") != 10 { t.Errorf("short name not set") } + return nil }, } a.Run([]string{"run"}) @@ -399,13 +405,14 @@ func TestParseMultiIntSlice(t *testing.T) { Flags: []cli.Flag{ cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { 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") } + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -417,13 +424,14 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { 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") } + return nil }, }).Run([]string{"run"}) } @@ -433,13 +441,14 @@ func TestParseMultiFloat64(t *testing.T) { Flags: []cli.Flag{ cli.Float64Flag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if ctx.Float64("serve") != 10.2 { t.Errorf("main name not set") } if ctx.Float64("s") != 10.2 { t.Errorf("short name not set") } + return nil }, } a.Run([]string{"run", "-s", "10.2"}) @@ -451,13 +460,14 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { Flags: []cli.Flag{ cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } if ctx.Float64("t") != 15.5 { t.Errorf("short name not set") } + return nil }, } a.Run([]string{"run"}) @@ -468,13 +478,14 @@ func TestParseMultiBool(t *testing.T) { Flags: []cli.Flag{ cli.BoolFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if ctx.Bool("serve") != true { t.Errorf("main name not set") } if ctx.Bool("s") != true { t.Errorf("short name not set") } + return nil }, } a.Run([]string{"run", "--serve"}) @@ -486,13 +497,14 @@ func TestParseMultiBoolFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { 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") } + return nil }, } a.Run([]string{"run"}) @@ -503,13 +515,14 @@ func TestParseMultiBoolT(t *testing.T) { Flags: []cli.Flag{ cli.BoolTFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if ctx.BoolT("serve") != true { t.Errorf("main name not set") } if ctx.BoolT("s") != true { t.Errorf("short name not set") } + return nil }, } a.Run([]string{"run", "--serve"}) @@ -521,13 +534,14 @@ func TestParseMultiBoolTFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { 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") } + return nil }, } a.Run([]string{"run"}) @@ -556,13 +570,14 @@ func TestParseGeneric(t *testing.T) { Flags: []cli.Flag{ cli.GenericFlag{Name: "serve, s", Value: &Parser{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) { t.Errorf("short name not set") } + return nil }, } a.Run([]string{"run", "-s", "10,20"}) @@ -574,13 +589,14 @@ func TestParseGenericFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { 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") } + return nil }, } a.Run([]string{"run"}) diff --git a/help.go b/help.go index 5020cb6..4997dde 100644 --- a/help.go +++ b/help.go @@ -69,13 +69,14 @@ var helpCommand = Command{ Name: "help", ShortName: "h", Usage: "Shows a list of commands or help for one command", - Action: func(c *Context) { + Action: func(c *Context) error { args := c.Args() if args.Present() { ShowCommandHelp(c, args.First()) } else { ShowAppHelp(c) } + return nil }, } @@ -83,13 +84,14 @@ var helpSubcommand = Command{ Name: "help", ShortName: "h", Usage: "Shows a list of commands or help for one command", - Action: func(c *Context) { + Action: func(c *Context) error { args := c.Args() if args.Present() { ShowCommandHelp(c, args.First()) } else { ShowSubcommandHelp(c) } + return nil }, } From 3d7183307aaa25628f657a0f6dac4d6dcf331359 Mon Sep 17 00:00:00 2001 From: Harrison Date: Sat, 31 Jan 2015 10:04:52 +1100 Subject: [PATCH 002/381] app, help: add support for multiple authors --- app.go | 25 +++++++++++++++++++------ app_test.go | 4 +++- help.go | 6 ++---- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app.go b/app.go index 6422345..8a6c88e 100644 --- a/app.go +++ b/app.go @@ -40,10 +40,8 @@ type App struct { CommandNotFound func(context *Context, command string) // Compilation date Compiled time.Time - // Author - Author string - // Author e-mail - Email string + // Authors + Authors []Author // Writer writer to write output to Writer io.Writer } @@ -67,8 +65,7 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), - Author: "Author", - Email: "unknown@email", + Authors: []Author{{"Jim", "jim@corporate.com"}, {"Hank", "hank@indiepalace.com"}}, Writer: os.Stdout, } } @@ -273,3 +270,19 @@ func (a *App) appendFlag(flag Flag) { a.Flags = append(a.Flags, flag) } } + +// Author represents someone who has contributed to a cli project. +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + +// String makes Author comply to the Stringer interface, to allow an easy print in the templating process +func (a Author) String() string { + e := "" + if a.Email != "" { + e = " <" + a.Email + "> " + } + + return fmt.Sprintf("%v %v", a.Name, e) +} diff --git a/app_test.go b/app_test.go index 2cbb0e3..be9b0d4 100644 --- a/app_test.go +++ b/app_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/codegangsta/cli" + "github.com/paked/cli" ) func ExampleApp() { @@ -21,6 +21,8 @@ func ExampleApp() { app.Action = func(c *cli.Context) { fmt.Printf("Hello %v\n", c.String("name")) } + app.Authors = []cli.Author{{"Oliver Allen", "oliver@toyshop.com"}} + app.Run(os.Args) // Output: // Hello Jeremy diff --git a/help.go b/help.go index bfb2788..cc4548c 100644 --- a/help.go +++ b/help.go @@ -12,11 +12,9 @@ USAGE: {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] VERSION: - {{.Version}}{{if or .Author .Email}} + {{.Version}} -AUTHOR:{{if .Author}} - {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} - {{.Email}}{{end}}{{end}} +AUTHOR(S): {{range .Authors}} {{ . }}{{end}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} From 05ecd63a95fe0acc44a3d6c4f88fd45356d6c782 Mon Sep 17 00:00:00 2001 From: Harrison Date: Sat, 31 Jan 2015 10:05:45 +1100 Subject: [PATCH 003/381] app_test: prepare for PR --- app_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app_test.go b/app_test.go index be9b0d4..28dfc45 100644 --- a/app_test.go +++ b/app_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/paked/cli" + "github.com/codegangsta/cli" ) func ExampleApp() { From 5308b4cd0fd522bf1cc2ff61a5ac738b9e8bcf26 Mon Sep 17 00:00:00 2001 From: Peter Smit Date: Fri, 6 Feb 2015 10:46:32 +0200 Subject: [PATCH 004/381] Allow commands to be hidden from help and autocomplete --- command.go | 2 ++ help.go | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index ffd3ef8..6117a85 100644 --- a/command.go +++ b/command.go @@ -31,6 +31,8 @@ type Command struct { SkipFlagParsing bool // Boolean to hide built-in help command HideHelp bool + // Boolean to hide this command from help or completion + Hidden bool } // Invokes the command given the context, parses ctx.Args() to generate command-specific flags diff --git a/help.go b/help.go index bfb2788..0ea1b11 100644 --- a/help.go +++ b/help.go @@ -19,7 +19,7 @@ AUTHOR:{{if .Author}} {{.Email}}{{end}}{{end}} COMMANDS: - {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{range .Commands}}{{if not .Hidden}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} {{end}}{{if .Flags}} GLOBAL OPTIONS: {{range .Flags}}{{.}} @@ -53,7 +53,7 @@ USAGE: {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...] COMMANDS: - {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{range .Commands}}{{if not .Hidden}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} {{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} @@ -103,6 +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 { + if command.Hidden { + continue + } fmt.Fprintln(c.App.Writer, command.Name) if command.ShortName != "" { fmt.Fprintln(c.App.Writer, command.ShortName) From c6592bb4878bed542931f20d07f941bc065a4b75 Mon Sep 17 00:00:00 2001 From: Harrison Date: Sat, 21 Feb 2015 10:44:00 +1100 Subject: [PATCH 005/381] app, help: add backwards compatibility for Authors --- app.go | 14 ++++++++++++-- app_test.go | 3 ++- help.go | 3 ++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 8a6c88e..2898356 100644 --- a/app.go +++ b/app.go @@ -40,8 +40,12 @@ type App struct { CommandNotFound func(context *Context, command string) // Compilation date Compiled time.Time - // Authors + // List of all authors who contributed Authors []Author + // Name of Author (Note: Use App.Authors, this is deprecated) + Author string + // Email of Author (Note: Use App.Authors, this is deprecated) + Email string // Writer writer to write output to Writer io.Writer } @@ -65,6 +69,8 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), + Author: "Dr. James", + Email: "who@gmail.com", Authors: []Author{{"Jim", "jim@corporate.com"}, {"Hank", "hank@indiepalace.com"}}, Writer: os.Stdout, } @@ -72,6 +78,10 @@ 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 { + if a.Author != "" && a.Author != "" { + a.Authors = append(a.Authors, Author{a.Author, a.Email}) + } + if HelpPrinter == nil { defer func() { HelpPrinter = nil @@ -281,7 +291,7 @@ type Author struct { func (a Author) String() string { e := "" if a.Email != "" { - e = " <" + a.Email + "> " + e = "<" + a.Email + "> " } return fmt.Sprintf("%v %v", a.Name, e) diff --git a/app_test.go b/app_test.go index 28dfc45..be95845 100644 --- a/app_test.go +++ b/app_test.go @@ -21,8 +21,9 @@ func ExampleApp() { app.Action = func(c *cli.Context) { fmt.Printf("Hello %v\n", c.String("name")) } + app.Author = "Harrison" + app.Email = "harrison@lolwut.com" app.Authors = []cli.Author{{"Oliver Allen", "oliver@toyshop.com"}} - app.Run(os.Args) // Output: // Hello Jeremy diff --git a/help.go b/help.go index cc4548c..27d413a 100644 --- a/help.go +++ b/help.go @@ -14,7 +14,8 @@ USAGE: VERSION: {{.Version}} -AUTHOR(S): {{range .Authors}} {{ . }}{{end}} +AUTHOR(S): + {{range .Authors}}{{ . }} {{end}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} From 4be878bffc8eb18b30b770da0f88075a252814e1 Mon Sep 17 00:00:00 2001 From: Nicolas Dufour Date: Mon, 23 Feb 2015 08:01:17 -0500 Subject: [PATCH 006/381] Fixing the issue with a command with subcommands not showing help message. - the command name is "" and HasName was returning true for an empty ShortName. - the Show method wasn't aware that command name was just "" and returned the first subcommand. --- command.go | 2 +- help.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 5747e52..07c919a 100644 --- a/command.go +++ b/command.go @@ -119,7 +119,7 @@ func (c Command) Run(ctx *Context) error { // Returns true if Command.Name or Command.ShortName matches given name func (c Command) HasName(name string) bool { - return c.Name == name || c.ShortName == name + return c.Name == name || (c.ShortName != "" && c.ShortName == name) } func (c Command) startApp(ctx *Context) error { diff --git a/help.go b/help.go index bfb2788..316b58d 100644 --- a/help.go +++ b/help.go @@ -112,6 +112,12 @@ func DefaultAppComplete(c *Context) { // Prints help for the given command func ShowCommandHelp(c *Context, command string) { + // show the subcommand help for a command with subcommands + if command == "" { + HelpPrinter(SubcommandHelpTemplate, c.App) + return + } + for _, c := range c.App.Commands { if c.HasName(command) { HelpPrinter(CommandHelpTemplate, c) From bf65971a6a2f115d0f1089fa794121a150705a07 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 9 Mar 2015 21:24:57 -0700 Subject: [PATCH 007/381] Add `Command.Aliases` and deprecate `Command.ShortName` `Aliases` will be more flexible while still allowing "ShortName" behaviour via `Aliases`. --- README.md | 12 ++++++------ app.go | 7 ++++++- app_test.go | 51 +++++++++++++++++-------------------------------- cli_test.go | 28 +++++++++++++-------------- command.go | 21 ++++++++++++++++++-- command_test.go | 4 ++-- help.go | 21 ++++++++++---------- 7 files changed, 74 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index c0bb338..3a183b1 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Subcommands can be defined for a more git-like command line app. app.Commands = []cli.Command{ { Name: "add", - ShortName: "a", + Names: []string{"a"}, Usage: "add a task to the list", Action: func(c *cli.Context) { println("added task: ", c.Args().First()) @@ -218,7 +218,7 @@ app.Commands = []cli.Command{ }, { Name: "complete", - ShortName: "c", + Names: []string{"c"}, Usage: "complete a task on the list", Action: func(c *cli.Context) { println("completed task: ", c.Args().First()) @@ -226,7 +226,7 @@ app.Commands = []cli.Command{ }, { Name: "template", - ShortName: "r", + Names: []string{"r"}, Usage: "options for task templates", Subcommands: []cli.Command{ { @@ -244,7 +244,7 @@ app.Commands = []cli.Command{ }, }, }, - }, + }, } ... ``` @@ -262,8 +262,8 @@ app := cli.NewApp() app.EnableBashCompletion = true app.Commands = []cli.Command{ { - Name: "complete", - ShortName: "c", + Name: "complete", + Names: []string{"c"}, Usage: "complete a task on the list", Action: func(c *cli.Context) { println("completed task: ", c.Args().First()) diff --git a/app.go b/app.go index 3e7d5a6..dde0061 100644 --- a/app.go +++ b/app.go @@ -5,6 +5,7 @@ import ( "io" "io/ioutil" "os" + "strings" "text/tabwriter" "text/template" "time" @@ -91,8 +92,12 @@ func (a *App) Run(arguments []string) (err error) { }() HelpPrinter = func(templ string, data interface{}) { + funcMap := template.FuncMap{ + "join": strings.Join, + } + w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0) - t := template.Must(template.New("help").Parse(templ)) + t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) err := t.Execute(w, data) if err != nil { panic(err) diff --git a/app_test.go b/app_test.go index 6143d36..4308669 100644 --- a/app_test.go +++ b/app_test.go @@ -29,41 +29,24 @@ func ExampleApp() { // Hello Jeremy } -func ExampleAppSubcommand() { +func ExampleAppCommands() { // set args for examples sake - os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} + os.Args = []string{"greet", "--name", "Jeremy"} + app := cli.NewApp() - app.Name = "say" - app.Commands = []cli.Command{ - { - Name: "hello", - ShortName: "hi", - Usage: "use it to see a description", - Description: "This is how we describe hello the function", - Subcommands: []cli.Command{ - { - Name: "english", - ShortName: "en", - Usage: "sends a greeting in english", - Description: "greets someone in english", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "name", - Value: "Bob", - Usage: "Name of the person to greet", - }, - }, - Action: func(c *cli.Context) { - fmt.Println("Hello,", c.String("name")) - }, - }, - }, - }, + app.Name = "greet" + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - + app.Action = func(c *cli.Context) { + fmt.Printf("Hello %v\n", c.String("name")) + } + app.Author = "Harrison" + app.Email = "harrison@lolwut.com" + app.Authors = []cli.Author{{"Oliver Allen", "oliver@toyshop.com"}} app.Run(os.Args) // Output: - // Hello, Jeremy + // Hello Jeremy } func ExampleAppHelp() { @@ -78,7 +61,7 @@ func ExampleAppHelp() { app.Commands = []cli.Command{ { Name: "describeit", - ShortName: "d", + Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", Action: func(c *cli.Context) { @@ -108,7 +91,7 @@ func ExampleAppBashComplete() { app.Commands = []cli.Command{ { Name: "describeit", - ShortName: "d", + Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", Action: func(c *cli.Context) { @@ -162,8 +145,8 @@ var commandAppTests = []struct { func TestApp_Command(t *testing.T) { app := cli.NewApp() - fooCommand := cli.Command{Name: "foobar", ShortName: "f"} - batCommand := cli.Command{Name: "batbaz", ShortName: "b"} + fooCommand := cli.Command{Name: "foobar", Aliases: []string{"f"}} + batCommand := cli.Command{Name: "batbaz", Aliases: []string{"b"}} app.Commands = []cli.Command{ fooCommand, batCommand, diff --git a/cli_test.go b/cli_test.go index 879a793..8a8df97 100644 --- a/cli_test.go +++ b/cli_test.go @@ -12,17 +12,17 @@ func Example() { app.Usage = "task list on the command line" app.Commands = []cli.Command{ { - Name: "add", - ShortName: "a", - Usage: "add a task to the list", + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", Action: func(c *cli.Context) { println("added task: ", c.Args().First()) }, }, { - Name: "complete", - ShortName: "c", - Usage: "complete a task on the list", + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", Action: func(c *cli.Context) { println("completed task: ", c.Args().First()) }, @@ -38,13 +38,13 @@ func ExampleSubcommand() { app.Commands = []cli.Command{ { Name: "hello", - ShortName: "hi", + Aliases: []string{"hi"}, Usage: "use it to see a description", Description: "This is how we describe hello the function", Subcommands: []cli.Command{ { Name: "english", - ShortName: "en", + Aliases: []string{"en"}, Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []cli.Flag{ @@ -58,9 +58,9 @@ func ExampleSubcommand() { println("Hello, ", c.String("name")) }, }, { - Name: "spanish", - ShortName: "sp", - Usage: "sends a greeting in spanish", + Name: "spanish", + Aliases: []string{"sp"}, + Usage: "sends a greeting in spanish", Flags: []cli.Flag{ cli.StringFlag{ Name: "surname", @@ -72,9 +72,9 @@ func ExampleSubcommand() { println("Hola, ", c.String("surname")) }, }, { - Name: "french", - ShortName: "fr", - Usage: "sends a greeting in french", + Name: "french", + Aliases: []string{"fr"}, + Usage: "sends a greeting in french", Flags: []cli.Flag{ cli.StringFlag{ Name: "nickname", diff --git a/command.go b/command.go index 07c919a..b61691c 100644 --- a/command.go +++ b/command.go @@ -10,8 +10,10 @@ import ( type Command struct { // The name of the command Name string - // short name of the command. Typically one character + // short name of the command. Typically one character (deprecated, use `Aliases`) ShortName string + // A list of aliases for the command + Aliases []string // A short description of the usage of this command Usage string // A longer explanation of how the command works @@ -117,9 +119,24 @@ func (c Command) Run(ctx *Context) error { return nil } +func (c Command) Names() []string { + names := []string{c.Name} + + if c.ShortName != "" { + names = append(names, c.ShortName) + } + + return append(names, c.Aliases...) +} + // Returns true if Command.Name or Command.ShortName matches given name func (c Command) HasName(name string) bool { - return c.Name == name || (c.ShortName != "" && c.ShortName == name) + for _, n := range c.Names() { + if n == name { + return true + } + } + return false } func (c Command) startApp(ctx *Context) error { diff --git a/command_test.go b/command_test.go index c0f556a..4125b0c 100644 --- a/command_test.go +++ b/command_test.go @@ -17,7 +17,7 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) { command := cli.Command{ Name: "test-cmd", - ShortName: "tc", + Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", Action: func(_ *cli.Context) {}, @@ -37,7 +37,7 @@ func TestCommandIgnoreFlags(t *testing.T) { command := cli.Command{ Name: "test-cmd", - ShortName: "tc", + Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", Action: func(_ *cli.Context) {}, diff --git a/help.go b/help.go index 8d17655..17465d7 100644 --- a/help.go +++ b/help.go @@ -18,7 +18,7 @@ AUTHOR(S): {{range .Authors}}{{ . }} {{end}} COMMANDS: - {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} {{end}}{{if .Flags}} GLOBAL OPTIONS: {{range .Flags}}{{.}} @@ -52,7 +52,7 @@ USAGE: {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...] COMMANDS: - {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} {{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} @@ -60,9 +60,9 @@ OPTIONS: ` var helpCommand = Command{ - Name: "help", - ShortName: "h", - Usage: "Shows a list of commands or help for one command", + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", Action: func(c *Context) { args := c.Args() if args.Present() { @@ -74,9 +74,9 @@ var helpCommand = Command{ } var helpSubcommand = Command{ - Name: "help", - ShortName: "h", - Usage: "Shows a list of commands or help for one command", + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", Action: func(c *Context) { args := c.Args() if args.Present() { @@ -102,9 +102,8 @@ 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.Fprintln(c.App.Writer, command.Name) - if command.ShortName != "" { - fmt.Fprintln(c.App.Writer, command.ShortName) + for _, name := range command.Names() { + fmt.Fprintln(c.App.Writer, name) } } } From 5f95a9e88b7bb61d461c453041f546d0f195692b Mon Sep 17 00:00:00 2001 From: jszwedko Date: Tue, 10 Mar 2015 07:59:59 -0700 Subject: [PATCH 008/381] Fix support for deprecated author fields Should add an author if either name or email is specified. --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 3e7d5a6..cd0d058 100644 --- a/app.go +++ b/app.go @@ -81,7 +81,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) (err error) { - if a.Author != "" && a.Author != "" { + if a.Author != "" || a.Email != "" { a.Authors = append(a.Authors, Author{a.Author, a.Email}) } From 7beac44ab1173acf1f10bf7a4310b5a15b3df383 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Tue, 10 Mar 2015 08:03:05 -0700 Subject: [PATCH 009/381] Don't default authors --- app.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/app.go b/app.go index cd0d058..8701320 100644 --- a/app.go +++ b/app.go @@ -72,9 +72,6 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), - Author: "Dr. James", - Email: "who@gmail.com", - Authors: []Author{{"Jim", "jim@corporate.com"}, {"Hank", "hank@indiepalace.com"}}, Writer: os.Stdout, } } From b95607c608f1bd870702373791c944032e5338b0 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Tue, 10 Mar 2015 08:12:44 -0700 Subject: [PATCH 010/381] Use named struct fields --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 8701320..3d4ed12 100644 --- a/app.go +++ b/app.go @@ -79,7 +79,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) (err error) { if a.Author != "" || a.Email != "" { - a.Authors = append(a.Authors, Author{a.Author, a.Email}) + a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } if HelpPrinter == nil { From bcec9b08c7e5564f7512ad7e7b03778fe1923116 Mon Sep 17 00:00:00 2001 From: Ravi Gadde Date: Thu, 26 Feb 2015 13:51:01 -0800 Subject: [PATCH 011/381] Added an API and test case for number of flags set --- context.go | 5 +++++ context_test.go | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/context.go b/context.go index c9f645b..37221bd 100644 --- a/context.go +++ b/context.go @@ -106,6 +106,11 @@ func (c *Context) GlobalGeneric(name string) interface{} { return lookupGeneric(name, c.globalSet) } +// Returns the number of flags set +func (c *Context) NumFlags() int { + return c.flagSet.NFlag() +} + // Determines if the flag was actually set func (c *Context) IsSet(name string) bool { if c.setFlags == nil { diff --git a/context_test.go b/context_test.go index 7c9a443..d4a1877 100644 --- a/context_test.go +++ b/context_test.go @@ -97,3 +97,15 @@ func TestContext_GlobalIsSet(t *testing.T) { expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) expect(t, c.GlobalIsSet("bogusGlobal"), false) } + +func TestContext_NumFlags(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") + c := cli.NewContext(nil, set, globalSet) + set.Parse([]string{"--myflag", "--otherflag=foo"}) + globalSet.Parse([]string{"--myflagGlobal"}) + expect(t, c.NumFlags(), 2) +} From 84630daaf4166b395884f51f32ff523c48d679eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=8F=E7=8E=89?= Date: Sat, 14 Mar 2015 01:12:37 +0800 Subject: [PATCH 012/381] Update help.go change template `AUTHOR(s)` tab ident to whitespace. --- help.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/help.go b/help.go index 8d17655..ee71155 100644 --- a/help.go +++ b/help.go @@ -15,8 +15,8 @@ VERSION: {{.Version}} AUTHOR(S): - {{range .Authors}}{{ . }} {{end}} - + {{range .Authors}}{{ . }} + {{end}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{end}}{{if .Flags}} From 474646abed5582b4f4265ffadb1ed1625fa6f114 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 16 Mar 2015 18:04:57 -0700 Subject: [PATCH 013/381] Didn't mean to remove ExampleAppSubcommand Also specify struct field initializers --- app_test.go | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/app_test.go b/app_test.go index 4308669..2e5cdfb 100644 --- a/app_test.go +++ b/app_test.go @@ -23,12 +23,49 @@ func ExampleApp() { } app.Author = "Harrison" app.Email = "harrison@lolwut.com" - app.Authors = []cli.Author{{"Oliver Allen", "oliver@toyshop.com"}} + app.Authors = []cli.Author{cli.Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} app.Run(os.Args) // Output: // Hello Jeremy } +func ExampleAppSubcommand() { + // set args for examples sake + os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} + app := cli.NewApp() + app.Name = "say" + app.Commands = []cli.Command{ + { + Name: "hello", + Aliases: []string{"hi"}, + Usage: "use it to see a description", + Description: "This is how we describe hello the function", + Subcommands: []cli.Command{ + { + Name: "english", + Aliases: []string{"en"}, + Usage: "sends a greeting in english", + Description: "greets someone in english", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, + }, + Action: func(c *cli.Context) { + fmt.Println("Hello,", c.String("name")) + }, + }, + }, + }, + } + + app.Run(os.Args) + // Output: + // Hello, Jeremy +} + func ExampleAppCommands() { // set args for examples sake os.Args = []string{"greet", "--name", "Jeremy"} @@ -43,7 +80,6 @@ func ExampleAppCommands() { } app.Author = "Harrison" app.Email = "harrison@lolwut.com" - app.Authors = []cli.Author{{"Oliver Allen", "oliver@toyshop.com"}} app.Run(os.Args) // Output: // Hello Jeremy From daad50e530f8799aa8ef8e670130ed53fa83dca1 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 16 Mar 2015 18:48:06 -0700 Subject: [PATCH 014/381] Turns out I didn't mean to add ExampleAppCommands at all --- app_test.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/app_test.go b/app_test.go index 2e5cdfb..4a40b89 100644 --- a/app_test.go +++ b/app_test.go @@ -66,25 +66,6 @@ func ExampleAppSubcommand() { // Hello, Jeremy } -func ExampleAppCommands() { - // set args for examples sake - os.Args = []string{"greet", "--name", "Jeremy"} - - app := cli.NewApp() - app.Name = "greet" - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, - } - app.Action = func(c *cli.Context) { - fmt.Printf("Hello %v\n", c.String("name")) - } - app.Author = "Harrison" - app.Email = "harrison@lolwut.com" - app.Run(os.Args) - // Output: - // Hello Jeremy -} - func ExampleAppHelp() { // set args for examples sake os.Args = []string{"greet", "h", "describeit"} From 6c6d93d4515ee574c1d8438ac61e05aaa9079e28 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Mon, 23 Mar 2015 07:55:18 -0700 Subject: [PATCH 015/381] Names -> Aliases in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3a183b1..4b3ddb0 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Subcommands can be defined for a more git-like command line app. app.Commands = []cli.Command{ { Name: "add", - Names: []string{"a"}, + Aliases: []string{"a"}, Usage: "add a task to the list", Action: func(c *cli.Context) { println("added task: ", c.Args().First()) @@ -218,7 +218,7 @@ app.Commands = []cli.Command{ }, { Name: "complete", - Names: []string{"c"}, + Aliases: []string{"c"}, Usage: "complete a task on the list", Action: func(c *cli.Context) { println("completed task: ", c.Args().First()) @@ -226,7 +226,7 @@ app.Commands = []cli.Command{ }, { Name: "template", - Names: []string{"r"}, + Aliases: []string{"r"}, Usage: "options for task templates", Subcommands: []cli.Command{ { @@ -263,7 +263,7 @@ app.EnableBashCompletion = true app.Commands = []cli.Command{ { Name: "complete", - Names: []string{"c"}, + Aliases: []string{"c"}, Usage: "complete a task on the list", Action: func(c *cli.Context) { println("completed task: ", c.Args().First()) From e8af095ac4f76fd8f72ce1b999d3c292b99162b8 Mon Sep 17 00:00:00 2001 From: My-khael Pierce Date: Thu, 9 Apr 2015 04:42:53 -0400 Subject: [PATCH 016/381] changes to readme.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b3ddb0..a588ca6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Command line apps are usually so tiny that there is absolutely no reason why you **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). +Make sure you have a working Go environment (go 1.1 or above is *required*). [See the install instructions](http://golang.org/doc/install.html). To install `cli.go`, simply run: ``` From a66d4d2bec835f98ee3b06bc79ca7a423acff18a Mon Sep 17 00:00:00 2001 From: My-khael Pierce Date: Sun, 12 Apr 2015 14:24:35 -0400 Subject: [PATCH 017/381] changes to readme.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a588ca6..a7bec84 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Command line apps are usually so tiny that there is absolutely no reason why you **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 or above is *required*). [See the install instructions](http://golang.org/doc/install.html). +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: ``` From e8425474216bce134df89a7833678c7bbcfd1df0 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Sun, 3 May 2015 17:43:52 -0700 Subject: [PATCH 018/381] Readd printHelp function back But update signature to take a writer. This is a backwards incompatible change for those overriding the HelpPrinter, but the hope is that this feature is largely unused and the usage is easily updated. --- app.go | 23 ----------------------- app_test.go | 3 ++- help.go | 44 ++++++++++++++++++++++++++++++++------------ 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/app.go b/app.go index cd29005..891416d 100644 --- a/app.go +++ b/app.go @@ -5,9 +5,6 @@ import ( "io" "io/ioutil" "os" - "strings" - "text/tabwriter" - "text/template" "time" ) @@ -83,26 +80,6 @@ func (a *App) Run(arguments []string) (err error) { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } - if HelpPrinter == nil { - defer func() { - HelpPrinter = nil - }() - - HelpPrinter = func(templ string, data interface{}) { - funcMap := template.FuncMap{ - "join": strings.Join, - } - - w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0) - t := template.Must(template.New("help").Funcs(funcMap).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.HideHelp { a.Commands = append(a.Commands, helpCommand) diff --git a/app_test.go b/app_test.go index 4a40b89..3bede53 100644 --- a/app_test.go +++ b/app_test.go @@ -3,6 +3,7 @@ package cli_test import ( "flag" "fmt" + "io" "os" "testing" @@ -537,7 +538,7 @@ func TestAppHelpPrinter(t *testing.T) { }() var wasCalled = false - cli.HelpPrinter = func(template string, data interface{}) { + cli.HelpPrinter = func(w io.Writer, template string, data interface{}) { wasCalled = true } diff --git a/help.go b/help.go index 7c4f81b..a45e642 100644 --- a/help.go +++ b/help.go @@ -1,6 +1,12 @@ package cli -import "fmt" +import ( + "fmt" + "io" + "strings" + "text/tabwriter" + "text/template" +) // The text template for the Default help topic. // cli.go uses text/template to render templates. You can @@ -87,16 +93,16 @@ var helpSubcommand = Command{ }, } -// Prints help for the App -type helpPrinter func(templ string, data interface{}) +// Prints help for the App or Command +type helpPrinter func(w io.Writer, templ string, data interface{}) -var HelpPrinter helpPrinter = nil +var HelpPrinter helpPrinter = printHelp // Prints version for the App var VersionPrinter = printVersion func ShowAppHelp(c *Context) { - HelpPrinter(AppHelpTemplate, c.App) + HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) } // Prints the list of subcommands as the default app completion method @@ -109,24 +115,24 @@ func DefaultAppComplete(c *Context) { } // Prints help for the given command -func ShowCommandHelp(c *Context, command string) { +func ShowCommandHelp(ctx *Context, command string) { // show the subcommand help for a command with subcommands if command == "" { - HelpPrinter(SubcommandHelpTemplate, c.App) + HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) return } - for _, c := range c.App.Commands { + for _, c := range ctx.App.Commands { if c.HasName(command) { - HelpPrinter(CommandHelpTemplate, c) + HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) return } } - if c.App.CommandNotFound != nil { - c.App.CommandNotFound(c, command) + if ctx.App.CommandNotFound != nil { + ctx.App.CommandNotFound(ctx, command) } else { - fmt.Fprintf(c.App.Writer, "No help topic for '%v'\n", command) + fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command) } } @@ -160,6 +166,20 @@ func ShowCommandCompletions(ctx *Context, command string) { } } +func printHelp(out io.Writer, templ string, data interface{}) { + funcMap := template.FuncMap{ + "join": strings.Join, + } + + w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) + t := template.Must(template.New("help").Funcs(funcMap).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 7c041f58117023547434ef4bf28ed6c592fdf4e8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 23 Apr 2015 14:55:05 -0700 Subject: [PATCH 019/381] do not print 'AUTHOR(S):' is there is no author Signed-off-by: Victor Vieux --- help.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/help.go b/help.go index a45e642..1117945 100644 --- a/help.go +++ b/help.go @@ -18,11 +18,11 @@ USAGE: {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] VERSION: - {{.Version}} + {{.Version}}{{if len .Authors}} AUTHOR(S): - {{range .Authors}}{{ . }} - {{end}} + {{range .Authors}}{{ . }}{{end}}{{end}} + COMMANDS: {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} {{end}}{{if .Flags}} From 96ad9297841968796e1bdf988b0af87854372d65 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Sun, 3 May 2015 18:02:03 -0700 Subject: [PATCH 020/381] Add test for inclusion of 'AUTHORS' in App help --- help_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 help_test.go diff --git a/help_test.go b/help_test.go new file mode 100644 index 0000000..bf0b272 --- /dev/null +++ b/help_test.go @@ -0,0 +1,22 @@ +package cli_test + +import ( + "bytes" + "testing" + + "github.com/codegangsta/cli" +) + +func TestShowAppHelp(t *testing.T) { + output := new(bytes.Buffer) + app := cli.NewApp() + app.Writer = output + + c := cli.NewContext(app, nil, nil) + + cli.ShowAppHelp(c) + + if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 { + t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):") + } +} From f952f5ac6f80d952248256b1061334bca89036c9 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Sun, 3 May 2015 18:04:45 -0700 Subject: [PATCH 021/381] Rename TestShowAppHelp to be more verbose Follow golang convention --- help_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help_test.go b/help_test.go index bf0b272..b3c1fda 100644 --- a/help_test.go +++ b/help_test.go @@ -7,7 +7,7 @@ import ( "github.com/codegangsta/cli" ) -func TestShowAppHelp(t *testing.T) { +func Test_ShowAppHelp_NoAuthor(t *testing.T) { output := new(bytes.Buffer) app := cli.NewApp() app.Writer = output From b8104e5da7b9fa3103aab4ff31545cace1946eb1 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Sun, 3 May 2015 18:37:51 -0700 Subject: [PATCH 022/381] Set writer when running command as app Also add test from https://github.com/codegangsta/cli/pull/202 with slight modifications. --- app_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ command.go | 1 + 2 files changed, 57 insertions(+) diff --git a/app_test.go b/app_test.go index 3bede53..ae8bb0f 100644 --- a/app_test.go +++ b/app_test.go @@ -1,10 +1,12 @@ package cli_test import ( + "bytes" "flag" "fmt" "io" "os" + "strings" "testing" "github.com/codegangsta/cli" @@ -621,3 +623,57 @@ func TestGlobalFlagsInSubcommands(t *testing.T) { expect(t, subcommandRun, true) } + +func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { + var subcommandHelpTopics = [][]string{ + {"command", "foo", "--help"}, + {"command", "foo", "-h"}, + {"command", "foo", "help"}, + } + + for _, flagSet := range subcommandHelpTopics { + t.Logf("==> checking with flags %v", flagSet) + + app := cli.NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + + subCmdBar := cli.Command{ + Name: "bar", + Usage: "does bar things", + } + subCmdBaz := cli.Command{ + Name: "baz", + Usage: "does baz things", + } + cmd := cli.Command{ + Name: "foo", + Description: "descriptive wall of text about how it does foo things", + Subcommands: []cli.Command{subCmdBar, subCmdBaz}, + } + + app.Commands = []cli.Command{cmd} + err := app.Run(flagSet) + + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if strings.Contains(output, "No help topic for") { + t.Errorf("expect a help topic, got none: \n%q", output) + } + + for _, shouldContain := range []string{ + cmd.Name, cmd.Description, + subCmdBar.Name, subCmdBar.Usage, + subCmdBaz.Name, subCmdBaz.Usage, + } { + if !strings.Contains(output, shouldContain) { + t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output) + } + } + } +} diff --git a/command.go b/command.go index b61691c..132a1e3 100644 --- a/command.go +++ b/command.go @@ -157,6 +157,7 @@ func (c Command) startApp(ctx *Context) error { app.Commands = c.Subcommands app.Flags = c.Flags app.HideHelp = c.HideHelp + app.Writer = ctx.App.Writer // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion From a889873af50a499d060097216dcdbcc26ed09e7c Mon Sep 17 00:00:00 2001 From: jszwedko Date: Sun, 3 May 2015 18:42:21 -0700 Subject: [PATCH 023/381] Set additional information on command's 'app' For completeness --- command.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/command.go b/command.go index 132a1e3..d0bbd0c 100644 --- a/command.go +++ b/command.go @@ -157,6 +157,12 @@ func (c Command) startApp(ctx *Context) error { app.Commands = c.Subcommands app.Flags = c.Flags app.HideHelp = c.HideHelp + + app.Version = ctx.App.Version + app.HideVersion = ctx.App.HideVersion + app.Compiled = ctx.App.Compiled + app.Author = ctx.App.Author + app.Email = ctx.App.Email app.Writer = ctx.App.Writer // bash completion From 942282e931e8286aa802a30b01fa7e16befb50f3 Mon Sep 17 00:00:00 2001 From: jszwedko Date: Sun, 10 May 2015 11:43:26 -0700 Subject: [PATCH 024/381] Add instructions for distributing bash completion [ci skip] --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index a7bec84..cd980fd 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,14 @@ setting the `PROG` variable to the name of your program: `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` +#### To Distribute + +Copy and modify `autocomplete/bash_autocomplete` to use your program name +rather than `$PROG` and have the user copy the file into +`/etc/bash_completion.d/` (or automatically install it there if you are +distributing a package). Alternatively you can just document that users should +source the generic `autocomplete/bash_autocomplete` with `$PROG` set to your +program name in their bash configuration. ## 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. From 35ac968c9e1d4b82f46dbf9b42399f3049a5d8e5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 12 May 2015 17:08:05 -0700 Subject: [PATCH 025/381] upate string slice usage Signed-off-by: Victor Vieux --- flag.go | 2 +- flag_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flag.go b/flag.go index 2511586..bac044b 100644 --- a/flag.go +++ b/flag.go @@ -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+" ...", f.Usage)) } func (f StringSliceFlag) Apply(set *flag.FlagSet) { diff --git a/flag_test.go b/flag_test.go index f0f096a..4c7fd5d 100644 --- a/flag_test.go +++ b/flag_test.go @@ -75,22 +75,22 @@ var stringSliceFlagTests = []struct { s := &cli.StringSlice{} s.Set("") return s - }(), "--help [--help option --help option]\t"}, + }(), "--help [--help ...]\t"}, {"h", func() *cli.StringSlice { s := &cli.StringSlice{} s.Set("") return s - }(), "-h [-h option -h option]\t"}, + }(), "-h [-h ...]\t"}, {"h", func() *cli.StringSlice { s := &cli.StringSlice{} s.Set("") return s - }(), "-h [-h option -h option]\t"}, + }(), "-h [-h ...]\t"}, {"test", func() *cli.StringSlice { s := &cli.StringSlice{} s.Set("Something") return s - }(), "--test [--test option --test option]\t"}, + }(), "--test [--test ...]\t"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { From 65d50017d4f34772b8d767fb7478b9416b5d30c5 Mon Sep 17 00:00:00 2001 From: Fabian Ruff Date: Mon, 18 May 2015 17:39:48 +0200 Subject: [PATCH 026/381] search context hierachy for global flags --- app.go | 6 ++--- app_test.go | 10 ++++++++- command.go | 2 +- command_test.go | 4 ++-- context.go | 60 +++++++++++++++++++++++++++++++++++++------------ context_test.go | 24 +++++++++++--------- 6 files changed, 75 insertions(+), 31 deletions(-) diff --git a/app.go b/app.go index 891416d..5e551b8 100644 --- a/app.go +++ b/app.go @@ -104,12 +104,12 @@ func (a *App) Run(arguments []string) (err error) { nerr := normalizeFlags(a.Flags, set) if nerr != nil { fmt.Fprintln(a.Writer, nerr) - context := NewContext(a, set, set) + context := NewContext(a, set, nil) ShowAppHelp(context) fmt.Fprintln(a.Writer) return nerr } - context := NewContext(a, set, set) + context := NewContext(a, set, nil) if err != nil { fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") @@ -190,7 +190,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { set.SetOutput(ioutil.Discard) err = set.Parse(ctx.Args().Tail()) nerr := normalizeFlags(a.Flags, set) - context := NewContext(a, set, ctx.globalSet) + context := NewContext(a, set, ctx) if nerr != nil { fmt.Fprintln(a.Writer, nerr) diff --git a/app_test.go b/app_test.go index ae8bb0f..4a0aa3c 100644 --- a/app_test.go +++ b/app_test.go @@ -597,6 +597,7 @@ func TestAppCommandNotFound(t *testing.T) { func TestGlobalFlagsInSubcommands(t *testing.T) { subcommandRun := false + parentFlag := false app := cli.NewApp() app.Flags = []cli.Flag{ @@ -606,6 +607,9 @@ func TestGlobalFlagsInSubcommands(t *testing.T) { app.Commands = []cli.Command{ cli.Command{ Name: "foo", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "parent, p", Usage: "Parent flag"}, + }, Subcommands: []cli.Command{ { Name: "bar", @@ -613,15 +617,19 @@ func TestGlobalFlagsInSubcommands(t *testing.T) { if c.GlobalBool("debug") { subcommandRun = true } + if c.GlobalBool("parent") { + parentFlag = true + } }, }, }, }, } - app.Run([]string{"command", "-d", "foo", "bar"}) + app.Run([]string{"command", "-d", "foo", "-p", "bar"}) expect(t, subcommandRun, true) + expect(t, parentFlag, true) } func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { diff --git a/command.go b/command.go index d0bbd0c..b721c0a 100644 --- a/command.go +++ b/command.go @@ -105,7 +105,7 @@ func (c Command) Run(ctx *Context) error { fmt.Fprintln(ctx.App.Writer) return nerr } - context := NewContext(ctx.App, set, ctx.globalSet) + context := NewContext(ctx.App, set, ctx) if checkCommandCompletions(context, c.Name) { return nil diff --git a/command_test.go b/command_test.go index 4125b0c..db81db2 100644 --- a/command_test.go +++ b/command_test.go @@ -13,7 +13,7 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) { test := []string{"blah", "blah", "-break"} set.Parse(test) - c := cli.NewContext(app, set, set) + c := cli.NewContext(app, set, nil) command := cli.Command{ Name: "test-cmd", @@ -33,7 +33,7 @@ func TestCommandIgnoreFlags(t *testing.T) { test := []string{"blah", "blah"} set.Parse(test) - c := cli.NewContext(app, set, set) + c := cli.NewContext(app, set, nil) command := cli.Command{ Name: "test-cmd", diff --git a/context.go b/context.go index 37221bd..5b67129 100644 --- a/context.go +++ b/context.go @@ -16,14 +16,14 @@ type Context struct { App *App Command Command flagSet *flag.FlagSet - globalSet *flag.FlagSet setFlags map[string]bool globalSetFlags map[string]bool + parentContext *Context } // Creates a new context. For use in when invoking an App or Command action. -func NewContext(app *App, set *flag.FlagSet, globalSet *flag.FlagSet) *Context { - return &Context{App: app, flagSet: set, globalSet: globalSet} +func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { + return &Context{App: app, flagSet: set, parentContext: parentCtx} } // Looks up the value of a local int flag, returns 0 if no int flag exists @@ -73,37 +73,58 @@ func (c *Context) Generic(name string) interface{} { // Looks up the value of a global int flag, returns 0 if no int flag exists func (c *Context) GlobalInt(name string) int { - return lookupInt(name, c.globalSet) + if fs := lookupParentFlagSet(name, c); fs != nil { + return lookupInt(name, fs) + } + return 0 } // 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) + if fs := lookupParentFlagSet(name, c); fs != nil { + return lookupDuration(name, fs) + } + return 0 } // 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) + if fs := lookupParentFlagSet(name, c); fs != nil { + return lookupBool(name, fs) + } + return false } // Looks up the value of a global string flag, returns "" if no string flag exists func (c *Context) GlobalString(name string) string { - return lookupString(name, c.globalSet) + if fs := lookupParentFlagSet(name, c); fs != nil { + return lookupString(name, fs) + } + return "" } // Looks up the value of a global string slice flag, returns nil if no string slice flag exists func (c *Context) GlobalStringSlice(name string) []string { - return lookupStringSlice(name, c.globalSet) + if fs := lookupParentFlagSet(name, c); fs != nil { + return lookupStringSlice(name, fs) + } + return nil } // Looks up the value of a global int slice flag, returns nil if no int slice flag exists func (c *Context) GlobalIntSlice(name string) []int { - return lookupIntSlice(name, c.globalSet) + if fs := lookupParentFlagSet(name, c); fs != nil { + return lookupIntSlice(name, fs) + } + return nil } // Looks up the value of a global generic flag, returns nil if no generic flag exists func (c *Context) GlobalGeneric(name string) interface{} { - return lookupGeneric(name, c.globalSet) + if fs := lookupParentFlagSet(name, c); fs != nil { + return lookupGeneric(name, fs) + } + return nil } // Returns the number of flags set @@ -126,11 +147,13 @@ func (c *Context) IsSet(name string) bool { 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 - }) + for ctx := c.parentContext; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { + ctx.flagSet.Visit(func(f *flag.Flag) { + c.globalSetFlags[f.Name] = true + }) + } } - return c.globalSetFlags[name] == true + return c.globalSetFlags[name] } // Returns a slice of flag names used in this context. @@ -201,6 +224,15 @@ func (a Args) Swap(from, to int) error { return nil } +func lookupParentFlagSet(name string, ctx *Context) *flag.FlagSet { + for ctx := ctx.parentContext; ctx != nil; ctx = ctx.parentContext { + if f := ctx.flagSet.Lookup(name); f != nil { + return ctx.flagSet + } + } + return nil +} + func lookupInt(name string, set *flag.FlagSet) int { f := set.Lookup(name) if f != nil { diff --git a/context_test.go b/context_test.go index d4a1877..6c27d06 100644 --- a/context_test.go +++ b/context_test.go @@ -13,8 +13,9 @@ func TestNewContext(t *testing.T) { set.Int("myflag", 12, "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Int("myflag", 42, "doc") + globalCtx := cli.NewContext(nil, globalSet, nil) command := cli.Command{Name: "mycommand"} - c := cli.NewContext(nil, set, globalSet) + c := cli.NewContext(nil, set, globalCtx) c.Command = command expect(t, c.Int("myflag"), 12) expect(t, c.GlobalInt("myflag"), 42) @@ -24,42 +25,42 @@ func TestNewContext(t *testing.T) { func TestContext_Int(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") - c := cli.NewContext(nil, set, set) + c := cli.NewContext(nil, set, nil) 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) + c := cli.NewContext(nil, set, nil) 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") - c := cli.NewContext(nil, set, set) + c := cli.NewContext(nil, set, nil) expect(t, c.String("myflag"), "hello world") } func TestContext_Bool(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") - c := cli.NewContext(nil, set, set) + c := cli.NewContext(nil, set, nil) expect(t, c.Bool("myflag"), false) } func TestContext_BoolT(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", true, "doc") - c := cli.NewContext(nil, set, set) + c := cli.NewContext(nil, set, nil) expect(t, c.BoolT("myflag"), true) } func TestContext_Args(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") - c := cli.NewContext(nil, set, set) + c := cli.NewContext(nil, set, nil) set.Parse([]string{"--myflag", "bat", "baz"}) expect(t, len(c.Args()), 2) expect(t, c.Bool("myflag"), true) @@ -71,7 +72,8 @@ func TestContext_IsSet(t *testing.T) { set.String("otherflag", "hello world", "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Bool("myflagGlobal", true, "doc") - c := cli.NewContext(nil, set, globalSet) + globalCtx := cli.NewContext(nil, globalSet, nil) + c := cli.NewContext(nil, set, globalCtx) set.Parse([]string{"--myflag", "bat", "baz"}) globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) expect(t, c.IsSet("myflag"), true) @@ -87,7 +89,8 @@ func TestContext_GlobalIsSet(t *testing.T) { globalSet := flag.NewFlagSet("test", 0) globalSet.Bool("myflagGlobal", true, "doc") globalSet.Bool("myflagGlobalUnset", true, "doc") - c := cli.NewContext(nil, set, globalSet) + globalCtx := cli.NewContext(nil, globalSet, nil) + c := cli.NewContext(nil, set, globalCtx) set.Parse([]string{"--myflag", "bat", "baz"}) globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) expect(t, c.GlobalIsSet("myflag"), false) @@ -104,7 +107,8 @@ func TestContext_NumFlags(t *testing.T) { set.String("otherflag", "hello world", "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Bool("myflagGlobal", true, "doc") - c := cli.NewContext(nil, set, globalSet) + globalCtx := cli.NewContext(nil, globalSet, nil) + c := cli.NewContext(nil, set, globalCtx) set.Parse([]string{"--myflag", "--otherflag=foo"}) globalSet.Parse([]string{"--myflagGlobal"}) expect(t, c.NumFlags(), 2) From f47f7b7e8568e846e9614acd5738092c3acf7058 Mon Sep 17 00:00:00 2001 From: Sergey Romanov Date: Mon, 1 Jun 2015 01:50:23 +0500 Subject: [PATCH 027/381] Fix panic if Valus in Int/StringSliceFlasg is missing --- app_test.go | 32 ++++++++++++++++++++++++++++++++ flag.go | 6 ++++++ 2 files changed, 38 insertions(+) diff --git a/app_test.go b/app_test.go index ae8bb0f..991dda4 100644 --- a/app_test.go +++ b/app_test.go @@ -342,6 +342,38 @@ func TestApp_ParseSliceFlags(t *testing.T) { } } +func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { + var parsedIntSlice []int + var parsedStringSlice []string + + app := cli.NewApp() + command := cli.Command{ + Name: "cmd", + Flags: []cli.Flag{ + cli.IntSliceFlag{Name: "a", Usage: "set numbers"}, + cli.StringSliceFlag{Name: "str", Usage: "set strings"}, + }, + Action: func(c *cli.Context) { + parsedIntSlice = c.IntSlice("a") + parsedStringSlice = c.StringSlice("str") + }, + } + app.Commands = []cli.Command{command} + + app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) + + var expectedIntSlice = []int{2} + var expectedStringSlice = []string{"A"} + + if parsedIntSlice[0] != expectedIntSlice[0] { + t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0]) + } + + if parsedStringSlice[0] != expectedStringSlice[0] { + t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0]) + } +} + func TestApp_DefaultStdout(t *testing.T) { app := cli.NewApp() diff --git a/flag.go b/flag.go index 2511586..3f34aff 100644 --- a/flag.go +++ b/flag.go @@ -144,6 +144,9 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { } eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &StringSlice{} + } set.Var(f.Value, name, f.Usage) }) } @@ -206,6 +209,9 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { } eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &IntSlice{} + } set.Var(f.Value, name, f.Usage) }) } From 7ed7a51f8621d7ff2a031039d39c8b21cfa5b9cf Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 1 Jun 2015 21:11:20 -0700 Subject: [PATCH 028/381] Aggregate errors from Before/After Previously `After` would overwrite any error from `Before`. --- app.go | 24 ++++++++++++++++-------- app_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ cli.go | 21 +++++++++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/app.go b/app.go index 891416d..0b658cb 100644 --- a/app.go +++ b/app.go @@ -132,10 +132,14 @@ func (a *App) Run(arguments []string) (err error) { if a.After != nil { defer func() { - // err is always nil here. - // There is a check to see if it is non-nil - // just few lines before. - err = a.After(context) + afterErr := a.After(context) + if afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } }() } @@ -225,10 +229,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.After != nil { defer func() { - // err is always nil here. - // There is a check to see if it is non-nil - // just few lines before. - err = a.After(context) + afterErr := a.After(context) + if afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } }() } diff --git a/app_test.go b/app_test.go index 991dda4..83bb4dd 100644 --- a/app_test.go +++ b/app_test.go @@ -709,3 +709,45 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } } } + +func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { + app := cli.NewApp() + app.Action = func(c *cli.Context) {} + app.Before = func(c *cli.Context) error { return fmt.Errorf("before error") } + app.After = func(c *cli.Context) error { return fmt.Errorf("after error") } + + err := app.Run([]string{"foo"}) + if err == nil { + t.Fatalf("expected to recieve error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} + +func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { + app := cli.NewApp() + app.Commands = []cli.Command{ + cli.Command{ + Name: "bar", + Before: func(c *cli.Context) error { return fmt.Errorf("before error") }, + After: func(c *cli.Context) error { return fmt.Errorf("after error") }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + if err == nil { + t.Fatalf("expected to recieve error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} diff --git a/cli.go b/cli.go index b742545..31dc912 100644 --- a/cli.go +++ b/cli.go @@ -17,3 +17,24 @@ // app.Run(os.Args) // } package cli + +import ( + "strings" +) + +type MultiError struct { + Errors []error +} + +func NewMultiError(err ...error) MultiError { + return MultiError{Errors: err} +} + +func (m MultiError) Error() string { + errs := make([]string, len(m.Errors)) + for i, err := range m.Errors { + errs[i] = err.Error() + } + + return strings.Join(errs, "\n") +} From 2272dad83ef28967ad46bf27470b56be95840ada Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Tue, 2 Jun 2015 20:16:44 -0700 Subject: [PATCH 029/381] Version and help check should look for local flags too Now that Global looks up the chain of contexts, the top level should access the flags without the prefix (i.e. Bool rather than GlobalBool). --- app_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++ help.go | 4 ++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/app_test.go b/app_test.go index fb8111d..3d5678b 100644 --- a/app_test.go +++ b/app_test.go @@ -717,3 +717,64 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } } } + +func TestApp_Run_Help(t *testing.T) { + var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}} + + for _, args := range helpArguments { + buf := new(bytes.Buffer) + + t.Logf("==> checking with arguments %v", args) + + app := cli.NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Writer = buf + app.Action = func(c *cli.Context) { + buf.WriteString("boom I say!") + } + + err := app.Run(args) + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "boom - make an explosive entrance") { + t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output) + } + } +} + +func TestApp_Run_Version(t *testing.T) { + var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}} + + for _, args := range versionArguments { + buf := new(bytes.Buffer) + + t.Logf("==> checking with arguments %v", args) + + app := cli.NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Version = "0.1.0" + app.Writer = buf + app.Action = func(c *cli.Context) { + buf.WriteString("boom I say!") + } + + err := app.Run(args) + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "0.1.0") { + t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output) + } + } +} diff --git a/help.go b/help.go index 1117945..9b7b9b9 100644 --- a/help.go +++ b/help.go @@ -181,7 +181,7 @@ func printHelp(out io.Writer, templ string, data interface{}) { } func checkVersion(c *Context) bool { - if c.GlobalBool("version") { + if c.GlobalBool("version") || c.GlobalBool("v") || c.Bool("version") || c.Bool("v") { ShowVersion(c) return true } @@ -190,7 +190,7 @@ func checkVersion(c *Context) bool { } func checkHelp(c *Context) bool { - if c.GlobalBool("h") || c.GlobalBool("help") { + if c.GlobalBool("h") || c.GlobalBool("help") || c.Bool("h") || c.Bool("help") { ShowAppHelp(c) return true } From 005b120d20811867d5439444b0dac3321147af92 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Tue, 2 Jun 2015 20:51:09 -0700 Subject: [PATCH 030/381] Add godoc comments to flag structs --- README.md | 2 ++ flag.go | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd980fd..85b9cda 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,8 @@ app.Action = func(c *cli.Context) { ... ``` +See full list of flags at http://godoc.org/github.com/codegangsta/cli + #### Alternate Names You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. diff --git a/flag.go b/flag.go index 3f34aff..531b091 100644 --- a/flag.go +++ b/flag.go @@ -99,21 +99,27 @@ func (f GenericFlag) getName() string { return f.Name } +// StringSlice is an opaque type for []string to satisfy flag.Value type StringSlice []string +// Set appends the string value to the list of values func (f *StringSlice) Set(value string) error { *f = append(*f, value) return nil } +// String returns a readable representation of this value (for usage defaults) func (f *StringSlice) String() string { return fmt.Sprintf("%s", *f) } +// Value returns the slice of strings set by this flag func (f *StringSlice) Value() []string { return *f } +// StringSlice is a string flag that can be specified multiple times on the +// command-line type StringSliceFlag struct { Name string Value *StringSlice @@ -121,12 +127,14 @@ type StringSliceFlag struct { EnvVar string } +// String returns the usage 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)) } +// Apply populates the flag given the flag set and environment func (f StringSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -155,10 +163,11 @@ func (f StringSliceFlag) getName() string { return f.Name } +// StringSlice is an opaque type for []int to satisfy flag.Value type IntSlice []int +// Set parses the value into an integer and appends it to the list of values func (f *IntSlice) Set(value string) error { - tmp, err := strconv.Atoi(value) if err != nil { return err @@ -168,14 +177,18 @@ func (f *IntSlice) Set(value string) error { return nil } +// String returns a readable representation of this value (for usage defaults) func (f *IntSlice) String() string { return fmt.Sprintf("%d", *f) } +// Value returns the slice of ints set by this flag func (f *IntSlice) Value() []int { return *f } +// IntSliceFlag is an int flag that can be specified multiple times on the +// command-line type IntSliceFlag struct { Name string Value *IntSlice @@ -183,12 +196,14 @@ type IntSliceFlag struct { EnvVar string } +// String returns the usage 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)) } +// Apply populates the flag given the flag set and environment func (f IntSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -220,16 +235,19 @@ func (f IntSliceFlag) getName() string { return f.Name } +// BoolFlag is a switch that defaults to false type BoolFlag struct { Name string Usage string EnvVar string } +// String returns a readable representation of this value (for usage defaults) func (f BoolFlag) String() string { return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) } +// Apply populates the flag given the flag set and environment func (f BoolFlag) Apply(set *flag.FlagSet) { val := false if f.EnvVar != "" { @@ -254,16 +272,20 @@ func (f BoolFlag) getName() string { return f.Name } +// BoolTFlag this represents a boolean flag that is true by default, but can +// still be set to false by --some-flag=false type BoolTFlag struct { Name string Usage string EnvVar string } +// String returns a readable representation of this value (for usage defaults) func (f BoolTFlag) String() string { return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) } +// Apply populates the flag given the flag set and environment func (f BoolTFlag) Apply(set *flag.FlagSet) { val := true if f.EnvVar != "" { @@ -288,6 +310,7 @@ func (f BoolTFlag) getName() string { return f.Name } +// StringFlag represents a flag that takes as string value type StringFlag struct { Name string Value string @@ -295,6 +318,7 @@ type StringFlag struct { EnvVar string } +// String returns the usage func (f StringFlag) String() string { var fmtString string fmtString = "%s %v\t%v" @@ -308,6 +332,7 @@ func (f StringFlag) String() string { return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) } +// Apply populates the flag given the flag set and environment func (f StringFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -328,6 +353,8 @@ func (f StringFlag) getName() string { return f.Name } +// IntFlag is a flag that takes an integer +// Errors if the value provided cannot be parsed type IntFlag struct { Name string Value int @@ -335,10 +362,12 @@ type IntFlag struct { EnvVar string } +// String returns the usage func (f IntFlag) String() string { return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) } +// Apply populates the flag given the flag set and environment func (f IntFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -362,6 +391,8 @@ func (f IntFlag) getName() string { return f.Name } +// DurationFlag is a flag that takes a duration specified in Go's duration +// format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { Name string Value time.Duration @@ -369,10 +400,12 @@ type DurationFlag struct { EnvVar string } +// String returns a readable representation of this value (for usage defaults) func (f DurationFlag) String() string { return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) } +// Apply populates the flag given the flag set and environment func (f DurationFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -396,6 +429,8 @@ func (f DurationFlag) getName() string { return f.Name } +// Float64Flag is a flag that takes an float value +// Errors if the value provided cannot be parsed type Float64Flag struct { Name string Value float64 @@ -403,10 +438,12 @@ type Float64Flag struct { EnvVar string } +// String returns the usage func (f Float64Flag) String() string { return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) } +// Apply populates the flag given the flag set and environment func (f Float64Flag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { From a3b93076fffa81ce505c15d47e3f847d37a97382 Mon Sep 17 00:00:00 2001 From: bryanl Date: Mon, 8 Jun 2015 16:21:26 -0400 Subject: [PATCH 031/381] Allow context value to be set after parse This change allows a context value to be set after parsing. The use case is updating default settings in a Before func. An example usage: ``` f, err := os.Open(configPath) if err == nil { config, err := docli.NewConfig(f) if err != nil { panic(err) } c.Set("token", config.APIKey) } ``` --- context.go | 5 +++++ context_test.go | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/context.go b/context.go index 5b67129..3c8f468 100644 --- a/context.go +++ b/context.go @@ -132,6 +132,11 @@ func (c *Context) NumFlags() int { return c.flagSet.NFlag() } +// Set sets a context flag to a value. +func (c *Context) Set(name, value string) error { + return c.flagSet.Set(name, value) +} + // Determines if the flag was actually set func (c *Context) IsSet(name string) bool { if c.setFlags == nil { diff --git a/context_test.go b/context_test.go index 6c27d06..c3f2631 100644 --- a/context_test.go +++ b/context_test.go @@ -113,3 +113,12 @@ func TestContext_NumFlags(t *testing.T) { globalSet.Parse([]string{"--myflagGlobal"}) expect(t, c.NumFlags(), 2) } + +func TestContext_Set(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("int", 5, "an int") + c := cli.NewContext(nil, set, nil) + + c.Set("int", "1") + expect(t, c.Int("int"), 1) +} From 2726643347a6dfbed620bc0e87857b26a2178b53 Mon Sep 17 00:00:00 2001 From: Peter Olds Date: Tue, 9 Jun 2015 16:35:50 -0600 Subject: [PATCH 032/381] Add the ability to add a copyright Signed-off-by: Peter Olds --- app.go | 2 ++ help.go | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 891416d..a1cf6e1 100644 --- a/app.go +++ b/app.go @@ -43,6 +43,8 @@ type App struct { Compiled time.Time // List of all authors who contributed Authors []Author + // Copyright of the binary if any + Copyright string // Name of Author (Note: Use App.Authors, this is deprecated) Author string // Email of Author (Note: Use App.Authors, this is deprecated) diff --git a/help.go b/help.go index 1117945..906fe7c 100644 --- a/help.go +++ b/help.go @@ -28,7 +28,10 @@ COMMANDS: {{end}}{{if .Flags}} GLOBAL OPTIONS: {{range .Flags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}}{{end}} + ` // The text template for the command help topic. From e04e926f1066d9c6c53fd8d5cfce713ef8ce91cf Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 11 Jun 2015 01:27:57 -0400 Subject: [PATCH 033/381] Update Travis config to run against more versions on faster CI --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index baf46ab..34d39c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ language: go -go: 1.1 +sudo: false + +go: +- 1.0.3 +- 1.1.2 +- 1.2.2 +- 1.3.3 +- 1.4.2 script: - go vet ./... From c7be972e815f660c504d87c3297f25df900caad6 Mon Sep 17 00:00:00 2001 From: Mawueli Kofi Adzoe Date: Thu, 11 Jun 2015 10:24:06 +0000 Subject: [PATCH 034/381] Fix tiny typo. --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 8f3bb30..f627420 100644 --- a/app.go +++ b/app.go @@ -9,7 +9,7 @@ import ( ) // App is the main structure of a cli application. It is recomended that -// and app be created with the cli.NewApp() function +// an app be created with the cli.NewApp() function type App struct { // The name of the program. Defaults to os.Args[0] Name string From 4a11a6ba052ebb4c831741a506ad7eaa8e20e3b2 Mon Sep 17 00:00:00 2001 From: Peter Olds Date: Tue, 16 Jun 2015 15:23:29 -0600 Subject: [PATCH 035/381] Remove whitespace #238 Signed-off-by: Peter Olds --- help.go | 1 - 1 file changed, 1 deletion(-) diff --git a/help.go b/help.go index 906fe7c..8e362e8 100644 --- a/help.go +++ b/help.go @@ -31,7 +31,6 @@ GLOBAL OPTIONS: {{end}}{{end}}{{if .Copyright }} COPYRIGHT: {{.Copyright}}{{end}} - ` // The text template for the command help topic. From 15e6b2fcc755130371d60193cc6d04b50ef9d546 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 19 Jun 2015 17:06:04 -0400 Subject: [PATCH 036/381] Add func to get context parent Signed-off-by: Brian Goff --- context.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/context.go b/context.go index 5b67129..c75607e 100644 --- a/context.go +++ b/context.go @@ -180,6 +180,11 @@ func (c *Context) GlobalFlagNames() (names []string) { return } +// Returns the parent context, if any +func (c *Context) Parent() *Context { + return c.parentContext +} + type Args []string // Returns the command line arguments associated with the context. From ee2cde7a77216d0c9eb00a39c7836ed4f5ec662f Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Wed, 24 Jun 2015 22:46:33 -0700 Subject: [PATCH 037/381] Print blank lines in help and error outputs more consistently. --- app.go | 10 +++++----- command.go | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app.go b/app.go index ac6700d..e7caec9 100644 --- a/app.go +++ b/app.go @@ -108,15 +108,14 @@ func (a *App) Run(arguments []string) (err error) { fmt.Fprintln(a.Writer, nerr) context := NewContext(a, set, nil) ShowAppHelp(context) - fmt.Fprintln(a.Writer) return nerr } context := NewContext(a, set, nil) if err != nil { - fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") - ShowAppHelp(context) + fmt.Fprintln(a.Writer, "Incorrect Usage.") fmt.Fprintln(a.Writer) + ShowAppHelp(context) return err } @@ -200,17 +199,18 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if nerr != nil { fmt.Fprintln(a.Writer, nerr) + fmt.Fprintln(a.Writer) if len(a.Commands) > 0 { ShowSubcommandHelp(context) } else { ShowCommandHelp(ctx, context.Args().First()) } - fmt.Fprintln(a.Writer) return nerr } if err != nil { - fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") + fmt.Fprintln(a.Writer, "Incorrect Usage.") + fmt.Fprintln(a.Writer) ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index b721c0a..212f128 100644 --- a/command.go +++ b/command.go @@ -91,9 +91,9 @@ func (c Command) Run(ctx *Context) error { } if err != nil { - fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n") - ShowCommandHelp(ctx, c.Name) + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) return err } @@ -102,7 +102,6 @@ func (c Command) Run(ctx *Context) error { fmt.Fprintln(ctx.App.Writer, nerr) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - fmt.Fprintln(ctx.App.Writer) return nerr } context := NewContext(ctx.App, set, ctx) From 595c055010bb8321f1a054c155ddc19ce8ae5910 Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Wed, 24 Jun 2015 23:07:32 -0700 Subject: [PATCH 038/381] If Version is an empty string, suppress version output in usage help. --- help.go | 4 ++-- help_test.go | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/help.go b/help.go index ef362f6..c80636a 100644 --- a/help.go +++ b/help.go @@ -15,10 +15,10 @@ 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...]{{if len .Version}} VERSION: - {{.Version}}{{if len .Authors}} + {{.Version}}{{end}}{{if len .Authors}} AUTHOR(S): {{range .Authors}}{{ . }}{{end}}{{end}} diff --git a/help_test.go b/help_test.go index b3c1fda..bff931f 100644 --- a/help_test.go +++ b/help_test.go @@ -20,3 +20,17 @@ func Test_ShowAppHelp_NoAuthor(t *testing.T) { t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):") } } + +func Test_ShowAppHelp_NoVersion(t *testing.T) { + output := new(bytes.Buffer) + app := cli.NewApp() + app.Writer = output + + c := cli.NewContext(app, nil, nil) + + cli.ShowAppHelp(c) + + if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { + t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") + } +} From dbde3303cf0ee27c8c2ec99089b19f257d1056bf Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Wed, 24 Jun 2015 23:11:59 -0700 Subject: [PATCH 039/381] Test updated --- help_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/help_test.go b/help_test.go index bff931f..c85f957 100644 --- a/help_test.go +++ b/help_test.go @@ -26,6 +26,8 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) { app := cli.NewApp() app.Writer = output + app.Version = "" + c := cli.NewContext(app, nil, nil) cli.ShowAppHelp(c) From 4d3820c145448b83214c1d951b82791cbc93a023 Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Thu, 25 Jun 2015 01:30:54 -0700 Subject: [PATCH 040/381] If there are no commands, don't show Commands section. Also fixed Copyright section formatting. --- help.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/help.go b/help.go index c80636a..7148c19 100644 --- a/help.go +++ b/help.go @@ -21,14 +21,16 @@ VERSION: {{.Version}}{{end}}{{if len .Authors}} AUTHOR(S): - {{range .Authors}}{{ . }}{{end}}{{end}} + {{range .Authors}}{{ . }}{{end}}{{end}}{{if .Commands}} COMMANDS: {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} - {{end}}{{if .Flags}} + {{end}}{{end}}{{if .Flags}} + GLOBAL OPTIONS: {{range .Flags}}{{.}} {{end}}{{end}}{{if .Copyright }} + COPYRIGHT: {{.Copyright}}{{end}} ` From 8cae4991af6e8b3312601375a1709e56ea7fb018 Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Thu, 25 Jun 2015 01:53:32 -0700 Subject: [PATCH 041/381] Fixing more formatting --- help.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/help.go b/help.go index 7148c19..e2cf82e 100644 --- a/help.go +++ b/help.go @@ -15,24 +15,23 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]{{if len .Version}} - + {{.Name}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} [arguments...] + {{if .Version}} VERSION: - {{.Version}}{{end}}{{if len .Authors}} - + {{.Version}} + {{end}}{{if len .Authors}} AUTHOR(S): - {{range .Authors}}{{ . }}{{end}}{{end}}{{if .Commands}} - + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} COMMANDS: {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} {{end}}{{end}}{{if .Flags}} - GLOBAL OPTIONS: {{range .Flags}}{{.}} {{end}}{{end}}{{if .Copyright }} - COPYRIGHT: - {{.Copyright}}{{end}} + {{.Copyright}} + {{end}} ` // The text template for the command help topic. From a2d4ae59392f39d8f02757647068405180572673 Mon Sep 17 00:00:00 2001 From: Fabian Ruff Date: Mon, 29 Jun 2015 23:20:27 +0200 Subject: [PATCH 042/381] Fix global flags processing on top level This fixes a regression introduced by #227. When looking up global flags by walking up the parent context's we need to consider the special case when we are starting at the very top and there is no parent context to start the traversal. Fixes #252 --- app_test.go | 17 +++++++++++++++++ context.go | 27 +++++++++++++++++---------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/app_test.go b/app_test.go index 46ec33f..b224d63 100644 --- a/app_test.go +++ b/app_test.go @@ -627,6 +627,23 @@ func TestAppCommandNotFound(t *testing.T) { expect(t, subcommandRun, false) } +func TestGlobalFlag(t *testing.T) { + var globalFlag string + var globalFlagSet bool + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "global, g", Usage: "global"}, + } + app.Action = func(c *cli.Context) { + globalFlag = c.GlobalString("global") + globalFlagSet = c.GlobalIsSet("global") + } + app.Run([]string{"command", "-g", "foo"}) + expect(t, globalFlag, "foo") + expect(t, globalFlagSet, true) + +} + func TestGlobalFlagsInSubcommands(t *testing.T) { subcommandRun := false parentFlag := false diff --git a/context.go b/context.go index c75607e..f541f41 100644 --- a/context.go +++ b/context.go @@ -73,7 +73,7 @@ func (c *Context) Generic(name string) interface{} { // Looks up the value of a global int flag, returns 0 if no int flag exists func (c *Context) GlobalInt(name string) int { - if fs := lookupParentFlagSet(name, c); fs != nil { + if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupInt(name, fs) } return 0 @@ -81,7 +81,7 @@ func (c *Context) GlobalInt(name string) int { // 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 { - if fs := lookupParentFlagSet(name, c); fs != nil { + if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupDuration(name, fs) } return 0 @@ -89,7 +89,7 @@ func (c *Context) GlobalDuration(name string) time.Duration { // Looks up the value of a global bool flag, returns false if no bool flag exists func (c *Context) GlobalBool(name string) bool { - if fs := lookupParentFlagSet(name, c); fs != nil { + if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupBool(name, fs) } return false @@ -97,7 +97,7 @@ func (c *Context) GlobalBool(name string) bool { // Looks up the value of a global string flag, returns "" if no string flag exists func (c *Context) GlobalString(name string) string { - if fs := lookupParentFlagSet(name, c); fs != nil { + if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupString(name, fs) } return "" @@ -105,7 +105,7 @@ func (c *Context) GlobalString(name string) string { // Looks up the value of a global string slice flag, returns nil if no string slice flag exists func (c *Context) GlobalStringSlice(name string) []string { - if fs := lookupParentFlagSet(name, c); fs != nil { + if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupStringSlice(name, fs) } return nil @@ -113,7 +113,7 @@ func (c *Context) GlobalStringSlice(name string) []string { // Looks up the value of a global int slice flag, returns nil if no int slice flag exists func (c *Context) GlobalIntSlice(name string) []int { - if fs := lookupParentFlagSet(name, c); fs != nil { + if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupIntSlice(name, fs) } return nil @@ -121,7 +121,7 @@ func (c *Context) GlobalIntSlice(name string) []int { // Looks up the value of a global generic flag, returns nil if no generic flag exists func (c *Context) GlobalGeneric(name string) interface{} { - if fs := lookupParentFlagSet(name, c); fs != nil { + if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupGeneric(name, fs) } return nil @@ -147,7 +147,11 @@ func (c *Context) IsSet(name string) bool { func (c *Context) GlobalIsSet(name string) bool { if c.globalSetFlags == nil { c.globalSetFlags = make(map[string]bool) - for ctx := c.parentContext; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { + ctx := c + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { ctx.flagSet.Visit(func(f *flag.Flag) { c.globalSetFlags[f.Name] = true }) @@ -229,8 +233,11 @@ func (a Args) Swap(from, to int) error { return nil } -func lookupParentFlagSet(name string, ctx *Context) *flag.FlagSet { - for ctx := ctx.parentContext; ctx != nil; ctx = ctx.parentContext { +func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + for ; ctx != nil; ctx = ctx.parentContext { if f := ctx.flagSet.Lookup(name); f != nil { return ctx.flagSet } From 758ad1e83684a8fd03447c32bbbea763f5e400c5 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Sat, 20 Jun 2015 19:59:54 -0400 Subject: [PATCH 043/381] Sets a subcommand's parent cmd This allows the help output to show the correct/full command path to the subcommand. Signed-off-by: Brian Goff --- app_test.go | 30 ++++++++++++++++++++++++++++++ command.go | 19 ++++++++++++++++++- help.go | 6 +++--- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/app_test.go b/app_test.go index b224d63..2d52e88 100644 --- a/app_test.go +++ b/app_test.go @@ -735,6 +735,36 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } } +func TestApp_Run_SubcommandFullPath(t *testing.T) { + app := cli.NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + + subCmd := cli.Command{ + Name: "bar", + Usage: "does bar things", + } + cmd := cli.Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []cli.Command{subCmd}, + } + app.Commands = []cli.Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "foo bar - does bar things") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "command foo bar [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + func TestApp_Run_Help(t *testing.T) { var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}} diff --git a/command.go b/command.go index 212f128..54617af 100644 --- a/command.go +++ b/command.go @@ -36,11 +36,21 @@ type Command struct { SkipFlagParsing bool // Boolean to hide built-in help command HideHelp bool + + commandNamePath []string +} + +// Returns the full name of the command. +// For subcommands this ensures that parent commands are part of the command path +func (c Command) FullName() string { + if c.commandNamePath == nil { + return c.Name + } + return strings.Join(c.commandNamePath, " ") } // Invokes the command given the context, parses ctx.Args() to generate command-specific flags func (c Command) Run(ctx *Context) error { - if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { return c.startApp(ctx) } @@ -179,5 +189,12 @@ func (c Command) startApp(ctx *Context) error { app.Action = helpSubcommand.Action } + var newCmds []Command + for _, cc := range app.Commands { + cc.commandNamePath = []string{c.Name, cc.Name} + newCmds = append(newCmds, cc) + } + app.Commands = newCmds + return app.RunAsSubcommand(ctx) } diff --git a/help.go b/help.go index e2cf82e..66ef2fb 100644 --- a/help.go +++ b/help.go @@ -20,7 +20,7 @@ USAGE: VERSION: {{.Version}} {{end}}{{if len .Authors}} -AUTHOR(S): +AUTHOR(S): {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} COMMANDS: @@ -38,10 +38,10 @@ COPYRIGHT: // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} + {{.FullName}} - {{.Usage}} USAGE: - command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}} + command {{.FullName}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}} DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} From 8ea1232ede49c2634f6068cd52c0a03aeeaac82a Mon Sep 17 00:00:00 2001 From: Edward Sheffler III Date: Mon, 20 Jul 2015 12:18:25 -0700 Subject: [PATCH 044/381] Improve vendoring options by removing self-referential imports in tests. --- app_test.go | 282 ++++++++++++++++++++++++------------------------ cli_test.go | 38 ++++--- command_test.go | 20 ++-- context_test.go | 34 +++--- flag_test.go | 258 ++++++++++++++++++++++---------------------- help_test.go | 16 ++- helpers_test.go | 2 +- 7 files changed, 319 insertions(+), 331 deletions(-) diff --git a/app_test.go b/app_test.go index 2d52e88..4c6787a 100644 --- a/app_test.go +++ b/app_test.go @@ -1,4 +1,4 @@ -package cli_test +package cli import ( "bytes" @@ -8,25 +8,23 @@ import ( "os" "strings" "testing" - - "github.com/codegangsta/cli" ) func ExampleApp() { // set args for examples sake os.Args = []string{"greet", "--name", "Jeremy"} - app := cli.NewApp() + app := NewApp() app.Name = "greet" - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + app.Flags = []Flag{ + StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Action = func(c *cli.Context) { + app.Action = func(c *Context) { fmt.Printf("Hello %v\n", c.String("name")) } app.Author = "Harrison" app.Email = "harrison@lolwut.com" - app.Authors = []cli.Author{cli.Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Authors = []Author{Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} app.Run(os.Args) // Output: // Hello Jeremy @@ -35,28 +33,28 @@ func ExampleApp() { func ExampleAppSubcommand() { // set args for examples sake os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} - app := cli.NewApp() + app := NewApp() app.Name = "say" - app.Commands = []cli.Command{ + app.Commands = []Command{ { Name: "hello", Aliases: []string{"hi"}, Usage: "use it to see a description", Description: "This is how we describe hello the function", - Subcommands: []cli.Command{ + Subcommands: []Command{ { Name: "english", Aliases: []string{"en"}, Usage: "sends a greeting in english", Description: "greets someone in english", - Flags: []cli.Flag{ - cli.StringFlag{ + Flags: []Flag{ + StringFlag{ Name: "name", Value: "Bob", Usage: "Name of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *Context) { fmt.Println("Hello,", c.String("name")) }, }, @@ -73,18 +71,18 @@ func ExampleAppHelp() { // set args for examples sake os.Args = []string{"greet", "h", "describeit"} - app := cli.NewApp() + app := NewApp() app.Name = "greet" - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + app.Flags = []Flag{ + StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Commands = []cli.Command{ + app.Commands = []Command{ { Name: "describeit", Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *cli.Context) { + Action: func(c *Context) { fmt.Printf("i like to describe things") }, }, @@ -105,23 +103,23 @@ func ExampleAppBashComplete() { // set args for examples sake os.Args = []string{"greet", "--generate-bash-completion"} - app := cli.NewApp() + app := NewApp() app.Name = "greet" app.EnableBashCompletion = true - app.Commands = []cli.Command{ + app.Commands = []Command{ { Name: "describeit", Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *cli.Context) { + Action: func(c *Context) { fmt.Printf("i like to describe things") }, }, { Name: "next", Usage: "next example", Description: "more stuff to see when generating bash completion", - Action: func(c *cli.Context) { + Action: func(c *Context) { fmt.Printf("the next example") }, }, @@ -139,8 +137,8 @@ func ExampleAppBashComplete() { func TestApp_Run(t *testing.T) { s := "" - app := cli.NewApp() - app.Action = func(c *cli.Context) { + app := NewApp() + app.Action = func(c *Context) { s = s + c.Args().First() } @@ -164,10 +162,10 @@ var commandAppTests = []struct { } func TestApp_Command(t *testing.T) { - app := cli.NewApp() - fooCommand := cli.Command{Name: "foobar", Aliases: []string{"f"}} - batCommand := cli.Command{Name: "batbaz", Aliases: []string{"b"}} - app.Commands = []cli.Command{ + app := NewApp() + fooCommand := Command{Name: "foobar", Aliases: []string{"f"}} + batCommand := Command{Name: "batbaz", Aliases: []string{"b"}} + app.Commands = []Command{ fooCommand, batCommand, } @@ -180,18 +178,18 @@ func TestApp_Command(t *testing.T) { func TestApp_CommandWithArgBeforeFlags(t *testing.T) { var parsedOption, firstArg string - app := cli.NewApp() - command := cli.Command{ + app := NewApp() + command := Command{ Name: "cmd", - Flags: []cli.Flag{ - cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, + Flags: []Flag{ + StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *cli.Context) { + Action: func(c *Context) { parsedOption = c.String("option") firstArg = c.Args().First() }, } - app.Commands = []cli.Command{command} + app.Commands = []Command{command} app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) @@ -200,23 +198,23 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { } func TestApp_RunAsSubcommandParseFlags(t *testing.T) { - var context *cli.Context + var context *Context - a := cli.NewApp() - a.Commands = []cli.Command{ + a := NewApp() + a.Commands = []Command{ { Name: "foo", - Action: func(c *cli.Context) { + Action: func(c *Context) { context = c }, - Flags: []cli.Flag{ - cli.StringFlag{ + Flags: []Flag{ + StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", }, }, - Before: func(_ *cli.Context) error { return nil }, + Before: func(_ *Context) error { return nil }, }, } a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) @@ -229,18 +227,18 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string var args []string - app := cli.NewApp() - command := cli.Command{ + app := NewApp() + command := Command{ Name: "cmd", - Flags: []cli.Flag{ - cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, + Flags: []Flag{ + StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *cli.Context) { + Action: func(c *Context) { parsedOption = c.String("option") args = c.Args() }, } - app.Commands = []cli.Command{command} + app.Commands = []Command{command} app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) @@ -253,14 +251,14 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { var args []string - app := cli.NewApp() - command := cli.Command{ + app := NewApp() + command := Command{ Name: "cmd", - Action: func(c *cli.Context) { + Action: func(c *Context) { args = c.Args() }, } - app.Commands = []cli.Command{command} + app.Commands = []Command{command} app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) @@ -272,11 +270,11 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { func TestApp_Float64Flag(t *testing.T) { var meters float64 - app := cli.NewApp() - app.Flags = []cli.Flag{ - cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, + app := NewApp() + app.Flags = []Flag{ + Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, } - app.Action = func(c *cli.Context) { + app.Action = func(c *Context) { meters = c.Float64("height") } @@ -289,21 +287,21 @@ func TestApp_ParseSliceFlags(t *testing.T) { var parsedIntSlice []int var parsedStringSlice []string - app := cli.NewApp() - command := cli.Command{ + app := NewApp() + command := Command{ Name: "cmd", - Flags: []cli.Flag{ - cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"}, - cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"}, + Flags: []Flag{ + IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"}, + StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"}, }, - Action: func(c *cli.Context) { + Action: func(c *Context) { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") parsedOption = c.String("option") firstArg = c.Args().First() }, } - app.Commands = []cli.Command{command} + app.Commands = []Command{command} app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) @@ -346,19 +344,19 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { var parsedIntSlice []int var parsedStringSlice []string - app := cli.NewApp() - command := cli.Command{ + app := NewApp() + command := Command{ Name: "cmd", - Flags: []cli.Flag{ - cli.IntSliceFlag{Name: "a", Usage: "set numbers"}, - cli.StringSliceFlag{Name: "str", Usage: "set strings"}, + Flags: []Flag{ + IntSliceFlag{Name: "a", Usage: "set numbers"}, + StringSliceFlag{Name: "str", Usage: "set strings"}, }, - Action: func(c *cli.Context) { + Action: func(c *Context) { parsedIntSlice = c.IntSlice("a") parsedStringSlice = c.StringSlice("str") }, } - app.Commands = []cli.Command{command} + app.Commands = []Command{command} app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) @@ -375,7 +373,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { } func TestApp_DefaultStdout(t *testing.T) { - app := cli.NewApp() + app := NewApp() if app.Writer != os.Stdout { t.Error("Default output writer not set.") @@ -403,7 +401,7 @@ func (fw *mockWriter) GetWritten() (b []byte) { func TestApp_SetStdout(t *testing.T) { w := &mockWriter{} - app := cli.NewApp() + app := NewApp() app.Name = "test" app.Writer = w @@ -423,9 +421,9 @@ func TestApp_BeforeFunc(t *testing.T) { beforeError := fmt.Errorf("fail") var err error - app := cli.NewApp() + app := NewApp() - app.Before = func(c *cli.Context) error { + app.Before = func(c *Context) error { beforeRun = true s := c.String("opt") if s == "fail" { @@ -435,17 +433,17 @@ func TestApp_BeforeFunc(t *testing.T) { return nil } - app.Commands = []cli.Command{ - cli.Command{ + app.Commands = []Command{ + Command{ Name: "sub", - Action: func(c *cli.Context) { + Action: func(c *Context) { subcommandRun = true }, }, } - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "opt"}, + app.Flags = []Flag{ + StringFlag{Name: "opt"}, } // run with the Before() func succeeding @@ -489,9 +487,9 @@ func TestApp_AfterFunc(t *testing.T) { afterError := fmt.Errorf("fail") var err error - app := cli.NewApp() + app := NewApp() - app.After = func(c *cli.Context) error { + app.After = func(c *Context) error { afterRun = true s := c.String("opt") if s == "fail" { @@ -501,17 +499,17 @@ func TestApp_AfterFunc(t *testing.T) { return nil } - app.Commands = []cli.Command{ - cli.Command{ + app.Commands = []Command{ + Command{ Name: "sub", - Action: func(c *cli.Context) { + Action: func(c *Context) { subcommandRun = true }, }, } - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "opt"}, + app.Flags = []Flag{ + StringFlag{Name: "opt"}, } // run with the After() func succeeding @@ -550,14 +548,14 @@ func TestApp_AfterFunc(t *testing.T) { } func TestAppNoHelpFlag(t *testing.T) { - oldFlag := cli.HelpFlag + oldFlag := HelpFlag defer func() { - cli.HelpFlag = oldFlag + HelpFlag = oldFlag }() - cli.HelpFlag = cli.BoolFlag{} + HelpFlag = BoolFlag{} - app := cli.NewApp() + app := NewApp() err := app.Run([]string{"test", "-h"}) if err != flag.ErrHelp { @@ -566,17 +564,17 @@ func TestAppNoHelpFlag(t *testing.T) { } func TestAppHelpPrinter(t *testing.T) { - oldPrinter := cli.HelpPrinter + oldPrinter := HelpPrinter defer func() { - cli.HelpPrinter = oldPrinter + HelpPrinter = oldPrinter }() var wasCalled = false - cli.HelpPrinter = func(w io.Writer, template string, data interface{}) { + HelpPrinter = func(w io.Writer, template string, data interface{}) { wasCalled = true } - app := cli.NewApp() + app := NewApp() app.Run([]string{"-h"}) if wasCalled == false { @@ -585,19 +583,19 @@ func TestAppHelpPrinter(t *testing.T) { } func TestAppVersionPrinter(t *testing.T) { - oldPrinter := cli.VersionPrinter + oldPrinter := VersionPrinter defer func() { - cli.VersionPrinter = oldPrinter + VersionPrinter = oldPrinter }() var wasCalled = false - cli.VersionPrinter = func(c *cli.Context) { + VersionPrinter = func(c *Context) { wasCalled = true } - app := cli.NewApp() - ctx := cli.NewContext(app, nil, nil) - cli.ShowVersion(ctx) + app := NewApp() + ctx := NewContext(app, nil, nil) + ShowVersion(ctx) if wasCalled == false { t.Errorf("Version printer expected to be called, but was not") @@ -606,16 +604,16 @@ func TestAppVersionPrinter(t *testing.T) { func TestAppCommandNotFound(t *testing.T) { beforeRun, subcommandRun := false, false - app := cli.NewApp() + app := NewApp() - app.CommandNotFound = func(c *cli.Context, command string) { + app.CommandNotFound = func(c *Context, command string) { beforeRun = true } - app.Commands = []cli.Command{ - cli.Command{ + app.Commands = []Command{ + Command{ Name: "bar", - Action: func(c *cli.Context) { + Action: func(c *Context) { subcommandRun = true }, }, @@ -630,11 +628,11 @@ func TestAppCommandNotFound(t *testing.T) { func TestGlobalFlag(t *testing.T) { var globalFlag string var globalFlagSet bool - app := cli.NewApp() - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "global, g", Usage: "global"}, + app := NewApp() + app.Flags = []Flag{ + StringFlag{Name: "global, g", Usage: "global"}, } - app.Action = func(c *cli.Context) { + app.Action = func(c *Context) { globalFlag = c.GlobalString("global") globalFlagSet = c.GlobalIsSet("global") } @@ -647,22 +645,22 @@ func TestGlobalFlag(t *testing.T) { func TestGlobalFlagsInSubcommands(t *testing.T) { subcommandRun := false parentFlag := false - app := cli.NewApp() + app := NewApp() - app.Flags = []cli.Flag{ - cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, + app.Flags = []Flag{ + BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, } - app.Commands = []cli.Command{ - cli.Command{ + app.Commands = []Command{ + Command{ Name: "foo", - Flags: []cli.Flag{ - cli.BoolFlag{Name: "parent, p", Usage: "Parent flag"}, + Flags: []Flag{ + BoolFlag{Name: "parent, p", Usage: "Parent flag"}, }, - Subcommands: []cli.Command{ + Subcommands: []Command{ { Name: "bar", - Action: func(c *cli.Context) { + Action: func(c *Context) { if c.GlobalBool("debug") { subcommandRun = true } @@ -691,25 +689,25 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { for _, flagSet := range subcommandHelpTopics { t.Logf("==> checking with flags %v", flagSet) - app := cli.NewApp() + app := NewApp() buf := new(bytes.Buffer) app.Writer = buf - subCmdBar := cli.Command{ + subCmdBar := Command{ Name: "bar", Usage: "does bar things", } - subCmdBaz := cli.Command{ + subCmdBaz := Command{ Name: "baz", Usage: "does baz things", } - cmd := cli.Command{ + cmd := Command{ Name: "foo", Description: "descriptive wall of text about how it does foo things", - Subcommands: []cli.Command{subCmdBar, subCmdBaz}, + Subcommands: []Command{subCmdBar, subCmdBaz}, } - app.Commands = []cli.Command{cmd} + app.Commands = []Command{cmd} err := app.Run(flagSet) if err != nil { @@ -736,20 +734,20 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } func TestApp_Run_SubcommandFullPath(t *testing.T) { - app := cli.NewApp() + app := NewApp() buf := new(bytes.Buffer) app.Writer = buf - subCmd := cli.Command{ + subCmd := Command{ Name: "bar", Usage: "does bar things", } - cmd := cli.Command{ + cmd := Command{ Name: "foo", Description: "foo commands", - Subcommands: []cli.Command{subCmd}, + Subcommands: []Command{subCmd}, } - app.Commands = []cli.Command{cmd} + app.Commands = []Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -773,11 +771,11 @@ func TestApp_Run_Help(t *testing.T) { t.Logf("==> checking with arguments %v", args) - app := cli.NewApp() + app := NewApp() app.Name = "boom" app.Usage = "make an explosive entrance" app.Writer = buf - app.Action = func(c *cli.Context) { + app.Action = func(c *Context) { buf.WriteString("boom I say!") } @@ -803,12 +801,12 @@ func TestApp_Run_Version(t *testing.T) { t.Logf("==> checking with arguments %v", args) - app := cli.NewApp() + app := NewApp() app.Name = "boom" app.Usage = "make an explosive entrance" app.Version = "0.1.0" app.Writer = buf - app.Action = func(c *cli.Context) { + app.Action = func(c *Context) { buf.WriteString("boom I say!") } @@ -827,10 +825,10 @@ func TestApp_Run_Version(t *testing.T) { } func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { - app := cli.NewApp() - app.Action = func(c *cli.Context) {} - app.Before = func(c *cli.Context) error { return fmt.Errorf("before error") } - app.After = func(c *cli.Context) error { return fmt.Errorf("after error") } + app := NewApp() + app.Action = func(c *Context) {} + app.Before = func(c *Context) error { return fmt.Errorf("before error") } + app.After = func(c *Context) error { return fmt.Errorf("after error") } err := app.Run([]string{"foo"}) if err == nil { @@ -846,12 +844,12 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { } func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { - app := cli.NewApp() - app.Commands = []cli.Command{ - cli.Command{ + app := NewApp() + app.Commands = []Command{ + Command{ Name: "bar", - Before: func(c *cli.Context) error { return fmt.Errorf("before error") }, - After: func(c *cli.Context) error { return fmt.Errorf("after error") }, + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, }, } diff --git a/cli_test.go b/cli_test.go index 8a8df97..e54f8e2 100644 --- a/cli_test.go +++ b/cli_test.go @@ -1,21 +1,19 @@ -package cli_test +package cli import ( "os" - - "github.com/codegangsta/cli" ) func Example() { - app := cli.NewApp() + app := NewApp() app.Name = "todo" app.Usage = "task list on the command line" - app.Commands = []cli.Command{ + app.Commands = []Command{ { Name: "add", Aliases: []string{"a"}, Usage: "add a task to the list", - Action: func(c *cli.Context) { + Action: func(c *Context) { println("added task: ", c.Args().First()) }, }, @@ -23,7 +21,7 @@ func Example() { Name: "complete", Aliases: []string{"c"}, Usage: "complete a task on the list", - Action: func(c *cli.Context) { + Action: func(c *Context) { println("completed task: ", c.Args().First()) }, }, @@ -33,56 +31,56 @@ func Example() { } func ExampleSubcommand() { - app := cli.NewApp() + app := NewApp() app.Name = "say" - app.Commands = []cli.Command{ + app.Commands = []Command{ { Name: "hello", Aliases: []string{"hi"}, Usage: "use it to see a description", Description: "This is how we describe hello the function", - Subcommands: []cli.Command{ + Subcommands: []Command{ { Name: "english", Aliases: []string{"en"}, Usage: "sends a greeting in english", Description: "greets someone in english", - Flags: []cli.Flag{ - cli.StringFlag{ + Flags: []Flag{ + StringFlag{ Name: "name", Value: "Bob", Usage: "Name of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *Context) { println("Hello, ", c.String("name")) }, }, { Name: "spanish", Aliases: []string{"sp"}, Usage: "sends a greeting in spanish", - Flags: []cli.Flag{ - cli.StringFlag{ + Flags: []Flag{ + StringFlag{ Name: "surname", Value: "Jones", Usage: "Surname of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *Context) { println("Hola, ", c.String("surname")) }, }, { Name: "french", Aliases: []string{"fr"}, Usage: "sends a greeting in french", - Flags: []cli.Flag{ - cli.StringFlag{ + Flags: []Flag{ + StringFlag{ Name: "nickname", Value: "Stevie", Usage: "Nickname of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *Context) { println("Bonjour, ", c.String("nickname")) }, }, @@ -90,7 +88,7 @@ func ExampleSubcommand() { }, { Name: "bye", Usage: "says goodbye", - Action: func(c *cli.Context) { + Action: func(c *Context) { println("bye") }, }, diff --git a/command_test.go b/command_test.go index db81db2..688d12c 100644 --- a/command_test.go +++ b/command_test.go @@ -1,26 +1,24 @@ -package cli_test +package cli import ( "flag" "testing" - - "github.com/codegangsta/cli" ) func TestCommandDoNotIgnoreFlags(t *testing.T) { - app := cli.NewApp() + app := NewApp() set := flag.NewFlagSet("test", 0) test := []string{"blah", "blah", "-break"} set.Parse(test) - c := cli.NewContext(app, set, nil) + c := NewContext(app, set, nil) - command := cli.Command{ + command := Command{ Name: "test-cmd", Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(_ *cli.Context) {}, + Action: func(_ *Context) {}, } err := command.Run(c) @@ -28,19 +26,19 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) { } func TestCommandIgnoreFlags(t *testing.T) { - app := cli.NewApp() + app := NewApp() set := flag.NewFlagSet("test", 0) test := []string{"blah", "blah"} set.Parse(test) - c := cli.NewContext(app, set, nil) + c := NewContext(app, set, nil) - command := cli.Command{ + command := Command{ Name: "test-cmd", Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(_ *cli.Context) {}, + Action: func(_ *Context) {}, SkipFlagParsing: true, } err := command.Run(c) diff --git a/context_test.go b/context_test.go index 6c27d06..7f8e928 100644 --- a/context_test.go +++ b/context_test.go @@ -1,11 +1,9 @@ -package cli_test +package cli import ( "flag" "testing" "time" - - "github.com/codegangsta/cli" ) func TestNewContext(t *testing.T) { @@ -13,9 +11,9 @@ func TestNewContext(t *testing.T) { set.Int("myflag", 12, "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Int("myflag", 42, "doc") - globalCtx := cli.NewContext(nil, globalSet, nil) - command := cli.Command{Name: "mycommand"} - c := cli.NewContext(nil, set, globalCtx) + globalCtx := NewContext(nil, globalSet, nil) + command := Command{Name: "mycommand"} + c := NewContext(nil, set, globalCtx) c.Command = command expect(t, c.Int("myflag"), 12) expect(t, c.GlobalInt("myflag"), 42) @@ -25,42 +23,42 @@ func TestNewContext(t *testing.T) { func TestContext_Int(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") - c := cli.NewContext(nil, set, nil) + c := NewContext(nil, set, nil) 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, nil) + c := NewContext(nil, set, nil) 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") - c := cli.NewContext(nil, set, nil) + c := NewContext(nil, set, nil) expect(t, c.String("myflag"), "hello world") } func TestContext_Bool(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") - c := cli.NewContext(nil, set, nil) + c := NewContext(nil, set, nil) expect(t, c.Bool("myflag"), false) } func TestContext_BoolT(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", true, "doc") - c := cli.NewContext(nil, set, nil) + c := NewContext(nil, set, nil) expect(t, c.BoolT("myflag"), true) } func TestContext_Args(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") - c := cli.NewContext(nil, set, nil) + c := NewContext(nil, set, nil) set.Parse([]string{"--myflag", "bat", "baz"}) expect(t, len(c.Args()), 2) expect(t, c.Bool("myflag"), true) @@ -72,8 +70,8 @@ func TestContext_IsSet(t *testing.T) { set.String("otherflag", "hello world", "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Bool("myflagGlobal", true, "doc") - globalCtx := cli.NewContext(nil, globalSet, nil) - c := cli.NewContext(nil, set, globalCtx) + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) set.Parse([]string{"--myflag", "bat", "baz"}) globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) expect(t, c.IsSet("myflag"), true) @@ -89,8 +87,8 @@ func TestContext_GlobalIsSet(t *testing.T) { globalSet := flag.NewFlagSet("test", 0) globalSet.Bool("myflagGlobal", true, "doc") globalSet.Bool("myflagGlobalUnset", true, "doc") - globalCtx := cli.NewContext(nil, globalSet, nil) - c := cli.NewContext(nil, set, globalCtx) + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) set.Parse([]string{"--myflag", "bat", "baz"}) globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) expect(t, c.GlobalIsSet("myflag"), false) @@ -107,8 +105,8 @@ func TestContext_NumFlags(t *testing.T) { set.String("otherflag", "hello world", "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Bool("myflagGlobal", true, "doc") - globalCtx := cli.NewContext(nil, globalSet, nil) - c := cli.NewContext(nil, set, globalCtx) + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) set.Parse([]string{"--myflag", "--otherflag=foo"}) globalSet.Parse([]string{"--myflagGlobal"}) expect(t, c.NumFlags(), 2) diff --git a/flag_test.go b/flag_test.go index f0f096a..3606102 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1,4 +1,4 @@ -package cli_test +package cli import ( "fmt" @@ -6,8 +6,6 @@ import ( "reflect" "strings" "testing" - - "github.com/codegangsta/cli" ) var boolFlagTests = []struct { @@ -21,7 +19,7 @@ var boolFlagTests = []struct { func TestBoolFlagHelpOutput(t *testing.T) { for _, test := range boolFlagTests { - flag := cli.BoolFlag{Name: test.name} + flag := BoolFlag{Name: test.name} output := flag.String() if output != test.expected { @@ -44,7 +42,7 @@ var stringFlagTests = []struct { func TestStringFlagHelpOutput(t *testing.T) { for _, test := range stringFlagTests { - flag := cli.StringFlag{Name: test.name, Value: test.value} + flag := StringFlag{Name: test.name, Value: test.value} output := flag.String() if output != test.expected { @@ -57,7 +55,7 @@ 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"} + flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} output := flag.String() if !strings.HasSuffix(output, " [$APP_FOO]") { @@ -68,26 +66,26 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { var stringSliceFlagTests = []struct { name string - value *cli.StringSlice + value *StringSlice expected string }{ - {"help", func() *cli.StringSlice { - s := &cli.StringSlice{} + {"help", func() *StringSlice { + s := &StringSlice{} s.Set("") return s }(), "--help [--help option --help option]\t"}, - {"h", func() *cli.StringSlice { - s := &cli.StringSlice{} + {"h", func() *StringSlice { + s := &StringSlice{} s.Set("") return s }(), "-h [-h option -h option]\t"}, - {"h", func() *cli.StringSlice { - s := &cli.StringSlice{} + {"h", func() *StringSlice { + s := &StringSlice{} s.Set("") return s }(), "-h [-h option -h option]\t"}, - {"test", func() *cli.StringSlice { - s := &cli.StringSlice{} + {"test", func() *StringSlice { + s := &StringSlice{} s.Set("Something") return s }(), "--test [--test option --test option]\t"}, @@ -96,7 +94,7 @@ var stringSliceFlagTests = []struct { func TestStringSliceFlagHelpOutput(t *testing.T) { for _, test := range stringSliceFlagTests { - flag := cli.StringSliceFlag{Name: test.name, Value: test.value} + flag := StringSliceFlag{Name: test.name, Value: test.value} output := flag.String() if output != test.expected { @@ -109,7 +107,7 @@ 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"} + flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} output := flag.String() if !strings.HasSuffix(output, " [$APP_QWWX]") { @@ -129,7 +127,7 @@ var intFlagTests = []struct { func TestIntFlagHelpOutput(t *testing.T) { for _, test := range intFlagTests { - flag := cli.IntFlag{Name: test.name} + flag := IntFlag{Name: test.name} output := flag.String() if output != test.expected { @@ -142,7 +140,7 @@ 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"} + flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} output := flag.String() if !strings.HasSuffix(output, " [$APP_BAR]") { @@ -162,7 +160,7 @@ var durationFlagTests = []struct { func TestDurationFlagHelpOutput(t *testing.T) { for _, test := range durationFlagTests { - flag := cli.DurationFlag{Name: test.name} + flag := DurationFlag{Name: test.name} output := flag.String() if output != test.expected { @@ -175,7 +173,7 @@ 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"} + flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} output := flag.String() if !strings.HasSuffix(output, " [$APP_BAR]") { @@ -186,14 +184,14 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { var intSliceFlagTests = []struct { name string - value *cli.IntSlice + value *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", func() *cli.IntSlice { - i := &cli.IntSlice{} + {"help", &IntSlice{}, "--help [--help option --help option]\t"}, + {"h", &IntSlice{}, "-h [-h option -h option]\t"}, + {"h", &IntSlice{}, "-h [-h option -h option]\t"}, + {"test", func() *IntSlice { + i := &IntSlice{} i.Set("9") return i }(), "--test [--test option --test option]\t"}, @@ -202,7 +200,7 @@ var intSliceFlagTests = []struct { func TestIntSliceFlagHelpOutput(t *testing.T) { for _, test := range intSliceFlagTests { - flag := cli.IntSliceFlag{Name: test.name, Value: test.value} + flag := IntSliceFlag{Name: test.name, Value: test.value} output := flag.String() if output != test.expected { @@ -215,7 +213,7 @@ 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"} + flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} output := flag.String() if !strings.HasSuffix(output, " [$APP_SMURF]") { @@ -235,7 +233,7 @@ var float64FlagTests = []struct { func TestFloat64FlagHelpOutput(t *testing.T) { for _, test := range float64FlagTests { - flag := cli.Float64Flag{Name: test.name} + flag := Float64Flag{Name: test.name} output := flag.String() if output != test.expected { @@ -248,7 +246,7 @@ 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"} + flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} output := flag.String() if !strings.HasSuffix(output, " [$APP_BAZ]") { @@ -259,7 +257,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { var genericFlagTests = []struct { name string - value cli.Generic + value Generic expected string }{ {"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"}, @@ -269,7 +267,7 @@ var genericFlagTests = []struct { func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { - flag := cli.GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} + flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} output := flag.String() if output != test.expected { @@ -282,7 +280,7 @@ 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"} + flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} output := flag.String() if !strings.HasSuffix(output, " [$APP_ZAP]") { @@ -292,11 +290,11 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { } func TestParseMultiString(t *testing.T) { - (&cli.App{ - Flags: []cli.Flag{ - cli.StringFlag{Name: "serve, s"}, + (&App{ + Flags: []Flag{ + StringFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.String("serve") != "10" { t.Errorf("main name not set") } @@ -310,11 +308,11 @@ func TestParseMultiString(t *testing.T) { func TestParseMultiStringFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_COUNT", "20") - (&cli.App{ - Flags: []cli.Flag{ - cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, + (&App{ + Flags: []Flag{ + StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.String("count") != "20" { t.Errorf("main name not set") } @@ -328,11 +326,11 @@ func TestParseMultiStringFromEnv(t *testing.T) { 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"}, + (&App{ + Flags: []Flag{ + StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.String("count") != "20" { t.Errorf("main name not set") } @@ -344,11 +342,11 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { } func TestParseMultiStringSlice(t *testing.T) { - (&cli.App{ - Flags: []cli.Flag{ - cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}}, + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "serve, s", Value: &StringSlice{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { t.Errorf("main name not set") } @@ -363,11 +361,11 @@ func TestParseMultiStringSliceFromEnv(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: "APP_INTERVALS"}, + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { t.Errorf("main name not set from env") } @@ -382,11 +380,11 @@ 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"}, + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { t.Errorf("main name not set from env") } @@ -398,11 +396,11 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { } func TestParseMultiInt(t *testing.T) { - a := cli.App{ - Flags: []cli.Flag{ - cli.IntFlag{Name: "serve, s"}, + a := App{ + Flags: []Flag{ + IntFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Int("serve") != 10 { t.Errorf("main name not set") } @@ -417,11 +415,11 @@ func TestParseMultiInt(t *testing.T) { func TestParseMultiIntFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "10") - a := cli.App{ - Flags: []cli.Flag{ - cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + a := App{ + Flags: []Flag{ + IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } @@ -436,11 +434,11 @@ func TestParseMultiIntFromEnv(t *testing.T) { 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"}, + a := App{ + Flags: []Flag{ + IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } @@ -453,11 +451,11 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { } func TestParseMultiIntSlice(t *testing.T) { - (&cli.App{ - Flags: []cli.Flag{ - cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}}, + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "serve, s", Value: &IntSlice{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { t.Errorf("main name not set") } @@ -472,11 +470,11 @@ func TestParseMultiIntSliceFromEnv(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: "APP_INTERVALS"}, + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { t.Errorf("main name not set from env") } @@ -491,11 +489,11 @@ 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"}, + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { t.Errorf("main name not set from env") } @@ -507,11 +505,11 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { } func TestParseMultiFloat64(t *testing.T) { - a := cli.App{ - Flags: []cli.Flag{ - cli.Float64Flag{Name: "serve, s"}, + a := App{ + Flags: []Flag{ + Float64Flag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Float64("serve") != 10.2 { t.Errorf("main name not set") } @@ -526,11 +524,11 @@ 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{ - cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } @@ -545,11 +543,11 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { 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"}, + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } @@ -562,11 +560,11 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { } func TestParseMultiBool(t *testing.T) { - a := cli.App{ - Flags: []cli.Flag{ - cli.BoolFlag{Name: "serve, s"}, + a := App{ + Flags: []Flag{ + BoolFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Bool("serve") != true { t.Errorf("main name not set") } @@ -581,11 +579,11 @@ func TestParseMultiBool(t *testing.T) { func TestParseMultiBoolFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_DEBUG", "1") - a := cli.App{ - Flags: []cli.Flag{ - cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + a := App{ + Flags: []Flag{ + BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Bool("debug") != true { t.Errorf("main name not set from env") } @@ -600,11 +598,11 @@ func TestParseMultiBoolFromEnv(t *testing.T) { 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"}, + a := App{ + Flags: []Flag{ + BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.Bool("debug") != true { t.Errorf("main name not set from env") } @@ -617,11 +615,11 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { } func TestParseMultiBoolT(t *testing.T) { - a := cli.App{ - Flags: []cli.Flag{ - cli.BoolTFlag{Name: "serve, s"}, + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.BoolT("serve") != true { t.Errorf("main name not set") } @@ -636,11 +634,11 @@ func TestParseMultiBoolT(t *testing.T) { func TestParseMultiBoolTFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_DEBUG", "0") - a := cli.App{ - Flags: []cli.Flag{ - cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.BoolT("debug") != false { t.Errorf("main name not set from env") } @@ -655,11 +653,11 @@ func TestParseMultiBoolTFromEnv(t *testing.T) { 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"}, + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if ctx.BoolT("debug") != false { t.Errorf("main name not set from env") } @@ -690,11 +688,11 @@ func (p *Parser) String() string { } func TestParseGeneric(t *testing.T) { - a := cli.App{ - Flags: []cli.Flag{ - cli.GenericFlag{Name: "serve, s", Value: &Parser{}}, + a := App{ + Flags: []Flag{ + GenericFlag{Name: "serve, s", Value: &Parser{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { t.Errorf("main name not set") } @@ -709,11 +707,11 @@ func TestParseGeneric(t *testing.T) { func TestParseGenericFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_SERVE", "20,30") - a := cli.App{ - Flags: []cli.Flag{ - cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, + a := App{ + Flags: []Flag{ + GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { t.Errorf("main name not set from env") } @@ -728,11 +726,11 @@ func TestParseGenericFromEnv(t *testing.T) { 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"}, + a := App{ + Flags: []Flag{ + GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { t.Errorf("value not set from env") } diff --git a/help_test.go b/help_test.go index c85f957..42d0284 100644 --- a/help_test.go +++ b/help_test.go @@ -1,20 +1,18 @@ -package cli_test +package cli import ( "bytes" "testing" - - "github.com/codegangsta/cli" ) func Test_ShowAppHelp_NoAuthor(t *testing.T) { output := new(bytes.Buffer) - app := cli.NewApp() + app := NewApp() app.Writer = output - c := cli.NewContext(app, nil, nil) + c := NewContext(app, nil, nil) - cli.ShowAppHelp(c) + ShowAppHelp(c) if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 { t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):") @@ -23,14 +21,14 @@ func Test_ShowAppHelp_NoAuthor(t *testing.T) { func Test_ShowAppHelp_NoVersion(t *testing.T) { output := new(bytes.Buffer) - app := cli.NewApp() + app := NewApp() app.Writer = output app.Version = "" - c := cli.NewContext(app, nil, nil) + c := NewContext(app, nil, nil) - cli.ShowAppHelp(c) + ShowAppHelp(c) if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") diff --git a/helpers_test.go b/helpers_test.go index cdc4feb..3ce8e93 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -1,4 +1,4 @@ -package cli_test +package cli import ( "reflect" From 9c0db3f4ac44d769b59c5d9ca2c95ea822cc1b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarc=C3=ADsio=20Gruppi?= Date: Tue, 28 Jul 2015 20:02:18 +0200 Subject: [PATCH 045/381] Created types for functions The function used by BashComplete, Before, After, Action and CommandNotFound have their won type. This makes easier to change/update the API --- app.go | 10 +++++----- command.go | 8 ++++---- funcs.go | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 funcs.go diff --git a/app.go b/app.go index e7caec9..41b08a8 100644 --- a/app.go +++ b/app.go @@ -28,17 +28,17 @@ type App struct { // Boolean to hide built-in version flag HideVersion bool // An action to execute when the bash-completion flag is set - BashComplete func(context *Context) + BashComplete BashCompleteFn // 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 + Before BeforeFn // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics - After func(context *Context) error + After AfterFn // The action to execute when no subcommands are specified - Action func(context *Context) + Action ActionFn // Execute this function if the proper command cannot be found - CommandNotFound func(context *Context, command string) + CommandNotFound CommandNotFoundFn // Compilation date Compiled time.Time // List of all authors who contributed diff --git a/command.go b/command.go index 54617af..7022f8c 100644 --- a/command.go +++ b/command.go @@ -19,15 +19,15 @@ type Command struct { // A longer explanation of how the command works Description string // The function to call when checking for bash command completions - BashComplete func(context *Context) + BashComplete BashCompleteFn // 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 + Before BeforeFn // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics - After func(context *Context) error + After AfterFn // The function to call when this command is invoked - Action func(context *Context) + Action ActionFn // List of child commands Subcommands []Command // List of flags to parse diff --git a/funcs.go b/funcs.go new file mode 100644 index 0000000..6807c98 --- /dev/null +++ b/funcs.go @@ -0,0 +1,18 @@ +package cli + +// An action to execute when the bash-completion flag is set +type BashCompleteFn func(*Context) + +// 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 +type BeforeFn func(*Context) error + +// An action to execute after any subcommands are run, but after the subcommand has finished +// It is run even if Action() panics +type AfterFn func(*Context) error + +// The action to execute when no subcommands are specified +type ActionFn func(*Context) + +// Execute this function if the proper command cannot be found +type CommandNotFoundFn func(*Context, string) From 49c1229409f2539e19561b11f973f426014d0c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarc=C3=ADsio=20Gruppi?= Date: Tue, 28 Jul 2015 20:05:14 +0200 Subject: [PATCH 046/381] Added exit code support Now the exit code can be returned by BeforeFn, ActionFn and AfterFn. The `os.Exit` function is not called by this packaged This closes #66 and closes #164 --- app.go | 54 +++++++++++++++++++++++++++++------------------------- command.go | 15 +++++++-------- funcs.go | 6 +++--- help.go | 6 ++++-- 4 files changed, 43 insertions(+), 38 deletions(-) diff --git a/app.go b/app.go index 41b08a8..46a699e 100644 --- a/app.go +++ b/app.go @@ -8,6 +8,11 @@ import ( "time" ) +var ( + // Set to 125 which is the highest number not used in most shells + DefaultExitCode int = 0 +) + // App is the main structure of a cli application. It is recomended that // an app be created with the cli.NewApp() function type App struct { @@ -77,7 +82,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) (err error) { +func (a *App) Run(arguments []string) (ec int, err error) { if a.Author != "" || a.Email != "" { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } @@ -108,7 +113,7 @@ func (a *App) Run(arguments []string) (err error) { fmt.Fprintln(a.Writer, nerr) context := NewContext(a, set, nil) ShowAppHelp(context) - return nerr + return DefaultExitCode, nerr } context := NewContext(a, set, nil) @@ -116,24 +121,24 @@ func (a *App) Run(arguments []string) (err error) { fmt.Fprintln(a.Writer, "Incorrect Usage.") fmt.Fprintln(a.Writer) ShowAppHelp(context) - return err + return DefaultExitCode, err } if checkCompletions(context) { - return nil + return 0, nil } if checkHelp(context) { - return nil + return 0, nil } if checkVersion(context) { - return nil + return 0, nil } if a.After != nil { defer func() { - afterErr := a.After(context) + afterEc, afterErr := a.After(context) if afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) @@ -141,13 +146,14 @@ func (a *App) Run(arguments []string) (err error) { err = afterErr } } + ec = afterEc }() } if a.Before != nil { - err := a.Before(context) + ec, err = a.Before(context) if err != nil { - return err + return ec, err } } @@ -161,20 +167,19 @@ func (a *App) Run(arguments []string) (err error) { } // Run default Action - a.Action(context) - return nil + return a.Action(context), 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 { + if exitCode, err := a.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) - os.Exit(1) + os.Exit(exitCode) } } // 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) (ec int, err error) { // append help to commands if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { @@ -205,33 +210,33 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } else { ShowCommandHelp(ctx, context.Args().First()) } - return nerr + return DefaultExitCode, nerr } if err != nil { fmt.Fprintln(a.Writer, "Incorrect Usage.") fmt.Fprintln(a.Writer) ShowSubcommandHelp(context) - return err + return DefaultExitCode, err } if checkCompletions(context) { - return nil + return 0, nil } if len(a.Commands) > 0 { if checkSubcommandHelp(context) { - return nil + return 0, nil } } else { if checkCommandHelp(ctx, context.Args().First()) { - return nil + return 0, nil } } if a.After != nil { defer func() { - afterErr := a.After(context) + afterEc, afterErr := a.After(context) if afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) @@ -239,13 +244,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { err = afterErr } } + ec = afterEc }() } if a.Before != nil { - err := a.Before(context) + ec, err = a.Before(context) if err != nil { - return err + return ec, err } } @@ -259,9 +265,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } // Run default Action - a.Action(context) - - return nil + return a.Action(context), nil } // Returns the named command on App. Returns nil if the command does not exist diff --git a/command.go b/command.go index 7022f8c..9aa42db 100644 --- a/command.go +++ b/command.go @@ -50,7 +50,7 @@ func (c Command) FullName() string { } // Invokes the command given the context, parses ctx.Args() to generate command-specific flags -func (c Command) Run(ctx *Context) error { +func (c Command) Run(ctx *Context) (int, error) { if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { return c.startApp(ctx) } @@ -104,7 +104,7 @@ func (c Command) Run(ctx *Context) error { fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - return err + return DefaultExitCode, err } nerr := normalizeFlags(c.Flags, set) @@ -112,20 +112,19 @@ func (c Command) Run(ctx *Context) error { fmt.Fprintln(ctx.App.Writer, nerr) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - return nerr + return DefaultExitCode, nerr } context := NewContext(ctx.App, set, ctx) if checkCommandCompletions(context, c.Name) { - return nil + return 0, nil } if checkCommandHelp(context, c.Name) { - return nil + return 0, nil } context.Command = c - c.Action(context) - return nil + return c.Action(context), nil } func (c Command) Names() []string { @@ -148,7 +147,7 @@ func (c Command) HasName(name string) bool { return false } -func (c Command) startApp(ctx *Context) error { +func (c Command) startApp(ctx *Context) (int, error) { app := NewApp() // set the name and usage diff --git a/funcs.go b/funcs.go index 6807c98..48909c1 100644 --- a/funcs.go +++ b/funcs.go @@ -5,14 +5,14 @@ type BashCompleteFn func(*Context) // 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 -type BeforeFn func(*Context) error +type BeforeFn func(*Context) (int, error) // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics -type AfterFn func(*Context) error +type AfterFn func(*Context) (int, error) // The action to execute when no subcommands are specified -type ActionFn func(*Context) +type ActionFn func(*Context) int // Execute this function if the proper command cannot be found type CommandNotFoundFn func(*Context, string) diff --git a/help.go b/help.go index 66ef2fb..e1faef6 100644 --- a/help.go +++ b/help.go @@ -72,13 +72,14 @@ var helpCommand = Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", - Action: func(c *Context) { + Action: func(c *Context) int { args := c.Args() if args.Present() { ShowCommandHelp(c, args.First()) } else { ShowAppHelp(c) } + return 0 }, } @@ -86,13 +87,14 @@ var helpSubcommand = Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", - Action: func(c *Context) { + Action: func(c *Context) int { args := c.Args() if args.Present() { ShowCommandHelp(c, args.First()) } else { ShowSubcommandHelp(c) } + return 0 }, } From b79f884410d3bed89d0346dd393791f715e1acc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarc=C3=ADsio=20Gruppi?= Date: Tue, 28 Jul 2015 20:06:46 +0200 Subject: [PATCH 047/381] Updated README.md with exit code sample --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 85b9cda..7c161a6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ export PATH=$PATH:$GOPATH/bin ``` ## Getting Started -One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`. +One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`. ``` go package main @@ -57,7 +57,7 @@ func main() { app.Action = func(c *cli.Context) { println("boom! I say!") } - + app.Run(os.Args) } ``` @@ -251,6 +251,28 @@ app.Commands = []cli.Command{ ... ``` +### Exit code + +It is your responsability to call `os.Exit` with the exit code returned by +`app.Run` + +```go +package main + +import ( + "os" + "github.com/codegangsta/cli" +) + +func main() { + exitCode, err := cli.NewApp().Run(os.Args) + if err != nil { + log.Println(err) + } + os.Exit(exitCode) +} +``` + ### Bash Completion You can enable completion commands by setting the `EnableBashCompletion` From 1510d7e722cceb8515d51786328b5a8df20ed27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarc=C3=ADsio=20Gruppi?= Date: Tue, 28 Jul 2015 20:28:37 +0200 Subject: [PATCH 048/381] Updated tests to support exit code --- app_test.go | 137 ++++++++++++++++++++++++++++++++---------------- cli_test.go | 18 ++++--- command_test.go | 8 +-- flag_test.go | 72 ++++++++++++++++--------- 4 files changed, 155 insertions(+), 80 deletions(-) diff --git a/app_test.go b/app_test.go index 2d52e88..a59d671 100644 --- a/app_test.go +++ b/app_test.go @@ -21,8 +21,9 @@ func ExampleApp() { app.Flags = []cli.Flag{ cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) int { fmt.Printf("Hello %v\n", c.String("name")) + return 0 } app.Author = "Harrison" app.Email = "harrison@lolwut.com" @@ -56,8 +57,9 @@ func ExampleAppSubcommand() { Usage: "Name of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { fmt.Println("Hello,", c.String("name")) + return 0 }, }, }, @@ -84,8 +86,9 @@ func ExampleAppHelp() { Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { fmt.Printf("i like to describe things") + return 0 }, }, } @@ -114,15 +117,17 @@ func ExampleAppBashComplete() { Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { fmt.Printf("i like to describe things") + return 0 }, }, { Name: "next", Usage: "next example", Description: "more stuff to see when generating bash completion", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { fmt.Printf("the next example") + return 0 }, }, } @@ -140,14 +145,17 @@ func TestApp_Run(t *testing.T) { s := "" app := cli.NewApp() - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) int { s = s + c.Args().First() + return 0 } - err := app.Run([]string{"command", "foo"}) + ec, err := app.Run([]string{"command", "foo"}) expect(t, err, nil) - err = app.Run([]string{"command", "bar"}) + expect(t, ec, 0) + ec, err = app.Run([]string{"command", "bar"}) expect(t, err, nil) + expect(t, ec, 0) expect(t, s, "foobar") } @@ -186,9 +194,10 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { Flags: []cli.Flag{ cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { parsedOption = c.String("option") firstArg = c.Args().First() + return 0 }, } app.Commands = []cli.Command{command} @@ -206,8 +215,9 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { a.Commands = []cli.Command{ { Name: "foo", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { context = c + return 0 }, Flags: []cli.Flag{ cli.StringFlag{ @@ -216,7 +226,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { Usage: "language for the greeting", }, }, - Before: func(_ *cli.Context) error { return nil }, + Before: func(_ *cli.Context) (int, error) { return 0, nil }, }, } a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) @@ -235,9 +245,10 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { Flags: []cli.Flag{ cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { parsedOption = c.String("option") args = c.Args() + return 0 }, } app.Commands = []cli.Command{command} @@ -256,8 +267,9 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { app := cli.NewApp() command := cli.Command{ Name: "cmd", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { args = c.Args() + return 0 }, } app.Commands = []cli.Command{command} @@ -276,8 +288,9 @@ func TestApp_Float64Flag(t *testing.T) { app.Flags = []cli.Flag{ cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, } - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) int { meters = c.Float64("height") + return 0 } app.Run([]string{"", "--height", "1.93"}) @@ -296,11 +309,12 @@ func TestApp_ParseSliceFlags(t *testing.T) { cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"}, cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"}, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") parsedOption = c.String("option") firstArg = c.Args().First() + return 0 }, } app.Commands = []cli.Command{command} @@ -353,9 +367,10 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { cli.IntSliceFlag{Name: "a", Usage: "set numbers"}, cli.StringSliceFlag{Name: "str", Usage: "set strings"}, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { parsedIntSlice = c.IntSlice("a") parsedStringSlice = c.StringSlice("str") + return 0 }, } app.Commands = []cli.Command{command} @@ -407,7 +422,7 @@ func TestApp_SetStdout(t *testing.T) { app.Name = "test" app.Writer = w - err := app.Run([]string{"help"}) + _, err := app.Run([]string{"help"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -425,21 +440,22 @@ func TestApp_BeforeFunc(t *testing.T) { app := cli.NewApp() - app.Before = func(c *cli.Context) error { + app.Before = func(c *cli.Context) (int, error) { beforeRun = true s := c.String("opt") if s == "fail" { - return beforeError + return 1, beforeError } - return nil + return 0, nil } app.Commands = []cli.Command{ cli.Command{ Name: "sub", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { subcommandRun = true + return 0 }, }, } @@ -449,7 +465,7 @@ func TestApp_BeforeFunc(t *testing.T) { } // run with the Before() func succeeding - err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + ec, err := app.Run([]string{"command", "--opt", "succeed", "sub"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -463,11 +479,15 @@ func TestApp_BeforeFunc(t *testing.T) { t.Errorf("Subcommand not executed when expected") } + if ec != 0 { + t.Errorf("Expected exit code to be %d but got %d", 0, ec) + } + // reset beforeRun, subcommandRun = false, false // run with the Before() func failing - err = app.Run([]string{"command", "--opt", "fail", "sub"}) + ec, err = app.Run([]string{"command", "--opt", "fail", "sub"}) // should be the same error produced by the Before func if err != beforeError { @@ -482,6 +502,9 @@ func TestApp_BeforeFunc(t *testing.T) { t.Errorf("Subcommand executed when NOT expected") } + if ec != 1 { + t.Errorf("Expected exit code to be %d but got %d", 1, ec) + } } func TestApp_AfterFunc(t *testing.T) { @@ -491,21 +514,22 @@ func TestApp_AfterFunc(t *testing.T) { app := cli.NewApp() - app.After = func(c *cli.Context) error { + app.After = func(c *cli.Context) (int, error) { afterRun = true s := c.String("opt") if s == "fail" { - return afterError + return 1, afterError } - return nil + return 0, nil } app.Commands = []cli.Command{ cli.Command{ Name: "sub", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { subcommandRun = true + return 0 }, }, } @@ -515,7 +539,7 @@ func TestApp_AfterFunc(t *testing.T) { } // run with the After() func succeeding - err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + ec, err := app.Run([]string{"command", "--opt", "succeed", "sub"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -529,11 +553,15 @@ func TestApp_AfterFunc(t *testing.T) { t.Errorf("Subcommand not executed when expected") } + if ec != 0 { + t.Errorf("Expected exit code to be %d but got %d", 0, ec) + } + // reset afterRun, subcommandRun = false, false // run with the Before() func failing - err = app.Run([]string{"command", "--opt", "fail", "sub"}) + ec, err = app.Run([]string{"command", "--opt", "fail", "sub"}) // should be the same error produced by the Before func if err != afterError { @@ -547,6 +575,10 @@ func TestApp_AfterFunc(t *testing.T) { if subcommandRun == false { t.Errorf("Subcommand not executed when expected") } + + if ec != 1 { + t.Errorf("Expected exit code to be %d but got %d", 1, ec) + } } func TestAppNoHelpFlag(t *testing.T) { @@ -558,7 +590,7 @@ func TestAppNoHelpFlag(t *testing.T) { cli.HelpFlag = cli.BoolFlag{} app := cli.NewApp() - err := app.Run([]string{"test", "-h"}) + _, err := app.Run([]string{"test", "-h"}) if err != flag.ErrHelp { t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) @@ -615,8 +647,9 @@ func TestAppCommandNotFound(t *testing.T) { app.Commands = []cli.Command{ cli.Command{ Name: "bar", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { subcommandRun = true + return 0 }, }, } @@ -634,9 +667,10 @@ func TestGlobalFlag(t *testing.T) { app.Flags = []cli.Flag{ cli.StringFlag{Name: "global, g", Usage: "global"}, } - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) int { globalFlag = c.GlobalString("global") globalFlagSet = c.GlobalIsSet("global") + return 0 } app.Run([]string{"command", "-g", "foo"}) expect(t, globalFlag, "foo") @@ -662,13 +696,14 @@ func TestGlobalFlagsInSubcommands(t *testing.T) { Subcommands: []cli.Command{ { Name: "bar", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { if c.GlobalBool("debug") { subcommandRun = true } if c.GlobalBool("parent") { parentFlag = true } + return 0 }, }, }, @@ -710,7 +745,7 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } app.Commands = []cli.Command{cmd} - err := app.Run(flagSet) + _, err := app.Run(flagSet) if err != nil { t.Error(err) @@ -751,7 +786,7 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } app.Commands = []cli.Command{cmd} - err := app.Run([]string{"command", "foo", "bar", "--help"}) + _, err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { t.Error(err) } @@ -777,11 +812,12 @@ func TestApp_Run_Help(t *testing.T) { app.Name = "boom" app.Usage = "make an explosive entrance" app.Writer = buf - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) int { buf.WriteString("boom I say!") + return 0 } - err := app.Run(args) + _, err := app.Run(args) if err != nil { t.Error(err) } @@ -808,11 +844,12 @@ func TestApp_Run_Version(t *testing.T) { app.Usage = "make an explosive entrance" app.Version = "0.1.0" app.Writer = buf - app.Action = func(c *cli.Context) { + app.Action = func(c *cli.Context) int { buf.WriteString("boom I say!") + return 0 } - err := app.Run(args) + _, err := app.Run(args) if err != nil { t.Error(err) } @@ -828,11 +865,11 @@ func TestApp_Run_Version(t *testing.T) { func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := cli.NewApp() - app.Action = func(c *cli.Context) {} - app.Before = func(c *cli.Context) error { return fmt.Errorf("before error") } - app.After = func(c *cli.Context) error { return fmt.Errorf("after error") } + app.Action = func(c *cli.Context) int { return 0 } + app.Before = func(c *cli.Context) (int, error) { return 1, fmt.Errorf("before error") } + app.After = func(c *cli.Context) (int, error) { return 2, fmt.Errorf("after error") } - err := app.Run([]string{"foo"}) + ec, err := app.Run([]string{"foo"}) if err == nil { t.Fatalf("expected to recieve error from Run, got none") } @@ -843,6 +880,10 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { if !strings.Contains(err.Error(), "after error") { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } + + if ec != 2 { + t.Errorf("Expected exit code to be %d but got %d", 2, ec) + } } func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { @@ -850,12 +891,12 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { app.Commands = []cli.Command{ cli.Command{ Name: "bar", - Before: func(c *cli.Context) error { return fmt.Errorf("before error") }, - After: func(c *cli.Context) error { return fmt.Errorf("after error") }, + Before: func(c *cli.Context) (int, error) { return 1, fmt.Errorf("before error") }, + After: func(c *cli.Context) (int, error) { return 2, fmt.Errorf("after error") }, }, } - err := app.Run([]string{"foo", "bar"}) + ec, err := app.Run([]string{"foo", "bar"}) if err == nil { t.Fatalf("expected to recieve error from Run, got none") } @@ -866,4 +907,8 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { if !strings.Contains(err.Error(), "after error") { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } + + if ec != 2 { + t.Errorf("Expected exit code to be %d but got %d", 2, ec) + } } diff --git a/cli_test.go b/cli_test.go index 8a8df97..f71f7ee 100644 --- a/cli_test.go +++ b/cli_test.go @@ -15,16 +15,18 @@ func Example() { Name: "add", Aliases: []string{"a"}, Usage: "add a task to the list", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { println("added task: ", c.Args().First()) + return 0 }, }, { Name: "complete", Aliases: []string{"c"}, Usage: "complete a task on the list", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { println("completed task: ", c.Args().First()) + return 0 }, }, } @@ -54,8 +56,9 @@ func ExampleSubcommand() { Usage: "Name of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { println("Hello, ", c.String("name")) + return 0 }, }, { Name: "spanish", @@ -68,8 +71,9 @@ func ExampleSubcommand() { Usage: "Surname of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { println("Hola, ", c.String("surname")) + return 0 }, }, { Name: "french", @@ -82,16 +86,18 @@ func ExampleSubcommand() { Usage: "Nickname of the person to greet", }, }, - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { println("Bonjour, ", c.String("nickname")) + return 0 }, }, }, }, { Name: "bye", Usage: "says goodbye", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) int { println("bye") + return 0 }, }, } diff --git a/command_test.go b/command_test.go index db81db2..ba9091e 100644 --- a/command_test.go +++ b/command_test.go @@ -20,9 +20,9 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(_ *cli.Context) {}, + Action: func(_ *cli.Context) int { return 0 }, } - err := command.Run(c) + _, err := command.Run(c) expect(t, err.Error(), "flag provided but not defined: -break") } @@ -40,10 +40,10 @@ func TestCommandIgnoreFlags(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(_ *cli.Context) {}, + Action: func(_ *cli.Context) int { return 0 }, SkipFlagParsing: true, } - err := command.Run(c) + _, err := command.Run(c) expect(t, err, nil) } diff --git a/flag_test.go b/flag_test.go index f0f096a..6eb0c8e 100644 --- a/flag_test.go +++ b/flag_test.go @@ -296,13 +296,14 @@ func TestParseMultiString(t *testing.T) { Flags: []cli.Flag{ cli.StringFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.String("serve") != "10" { t.Errorf("main name not set") } if ctx.String("s") != "10" { t.Errorf("short name not set") } + return 0 }, }).Run([]string{"run", "-s", "10"}) } @@ -314,13 +315,14 @@ func TestParseMultiStringFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.String("count") != "20" { t.Errorf("main name not set") } if ctx.String("c") != "20" { t.Errorf("short name not set") } + return 0 }, }).Run([]string{"run"}) } @@ -332,13 +334,14 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { Flags: []cli.Flag{ cli.StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.String("count") != "20" { t.Errorf("main name not set") } if ctx.String("c") != "20" { t.Errorf("short name not set") } + return 0 }, }).Run([]string{"run"}) } @@ -348,13 +351,14 @@ func TestParseMultiStringSlice(t *testing.T) { Flags: []cli.Flag{ cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { t.Errorf("short name not set") } + return 0 }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -367,13 +371,14 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, }).Run([]string{"run"}) } @@ -386,13 +391,14 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { Flags: []cli.Flag{ cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, }).Run([]string{"run"}) } @@ -402,13 +408,14 @@ func TestParseMultiInt(t *testing.T) { Flags: []cli.Flag{ cli.IntFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.Int("serve") != 10 { t.Errorf("main name not set") } if ctx.Int("s") != 10 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "-s", "10"}) @@ -421,13 +428,14 @@ func TestParseMultiIntFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } if ctx.Int("t") != 10 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run"}) @@ -440,13 +448,14 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { Flags: []cli.Flag{ cli.IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } if ctx.Int("t") != 10 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run"}) @@ -457,13 +466,14 @@ func TestParseMultiIntSlice(t *testing.T) { Flags: []cli.Flag{ cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -476,13 +486,14 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, }).Run([]string{"run"}) } @@ -495,13 +506,14 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { Flags: []cli.Flag{ cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, }).Run([]string{"run"}) } @@ -511,13 +523,14 @@ func TestParseMultiFloat64(t *testing.T) { Flags: []cli.Flag{ cli.Float64Flag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.Float64("serve") != 10.2 { t.Errorf("main name not set") } if ctx.Float64("s") != 10.2 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "-s", "10.2"}) @@ -530,13 +543,14 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { Flags: []cli.Flag{ cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } if ctx.Float64("t") != 15.5 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run"}) @@ -549,13 +563,14 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { Flags: []cli.Flag{ cli.Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } if ctx.Float64("t") != 15.5 { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run"}) @@ -566,13 +581,14 @@ func TestParseMultiBool(t *testing.T) { Flags: []cli.Flag{ cli.BoolFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.Bool("serve") != true { t.Errorf("main name not set") } if ctx.Bool("s") != true { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "--serve"}) @@ -585,13 +601,14 @@ func TestParseMultiBoolFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, } a.Run([]string{"run"}) @@ -604,13 +621,14 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { Flags: []cli.Flag{ cli.BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, } a.Run([]string{"run"}) @@ -621,13 +639,14 @@ func TestParseMultiBoolT(t *testing.T) { Flags: []cli.Flag{ cli.BoolTFlag{Name: "serve, s"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if ctx.BoolT("serve") != true { t.Errorf("main name not set") } if ctx.BoolT("s") != true { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "--serve"}) @@ -640,13 +659,14 @@ func TestParseMultiBoolTFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, } a.Run([]string{"run"}) @@ -659,13 +679,14 @@ func TestParseMultiBoolTFromEnvCascade(t *testing.T) { Flags: []cli.Flag{ cli.BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, } a.Run([]string{"run"}) @@ -694,13 +715,14 @@ func TestParseGeneric(t *testing.T) { Flags: []cli.Flag{ cli.GenericFlag{Name: "serve, s", Value: &Parser{}}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) { t.Errorf("short name not set") } + return 0 }, } a.Run([]string{"run", "-s", "10,20"}) @@ -713,13 +735,14 @@ func TestParseGenericFromEnv(t *testing.T) { Flags: []cli.Flag{ cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { 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") } + return 0 }, } a.Run([]string{"run"}) @@ -732,10 +755,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) { Flags: []cli.Flag{ cli.GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) int { if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { t.Errorf("value not set from env") } + return 0 }, } a.Run([]string{"run"}) From ef65245544538d1c4c87140a811a223a2a2a1686 Mon Sep 17 00:00:00 2001 From: Tristan Zajonc Date: Mon, 3 Aug 2015 16:51:11 -0700 Subject: [PATCH 049/381] add ArgsUsage to App and Command --- app.go | 3 +++ command.go | 5 +++++ help.go | 20 +++++++++++--------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index e7caec9..aadbea6 100644 --- a/app.go +++ b/app.go @@ -15,6 +15,8 @@ type App struct { Name string // Description of the program. Usage string + // Description of the program argument format. + ArgsUsage string // Version of the program Version string // List of commands to execute @@ -68,6 +70,7 @@ func NewApp() *App { return &App{ Name: os.Args[0], Usage: "A new cli application", + ArgsUsage: "[arguments...]", Version: "0.0.0", BashComplete: DefaultAppComplete, Action: helpCommand.Action, diff --git a/command.go b/command.go index 54617af..5b6cd66 100644 --- a/command.go +++ b/command.go @@ -18,6 +18,8 @@ type Command struct { Usage string // A longer explanation of how the command works Description string + // A short description of the arguments of this command + ArgsUsage string // The function to call when checking for bash command completions BashComplete func(context *Context) // An action to execute before any sub-subcommands are run, but after the context is ready @@ -158,6 +160,9 @@ func (c Command) startApp(ctx *Context) error { } else { app.Usage = c.Usage } + if c.ArgsUsage == "" { + c.ArgsUsage = "[arguments...]" + } // set CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound diff --git a/help.go b/help.go index 66ef2fb..0855eb6 100644 --- a/help.go +++ b/help.go @@ -15,7 +15,7 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} [arguments...] + {{.Name}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{.ArgsUsage}} {{if .Version}} VERSION: {{.Version}} @@ -41,7 +41,7 @@ var CommandHelpTemplate = `NAME: {{.FullName}} - {{.Usage}} USAGE: - command {{.FullName}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}} + command {{.FullName}}{{if .Flags}} [command options]{{end}} {{.ArgsUsage}}{{if .Description}} DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} @@ -58,7 +58,7 @@ var SubcommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...] + {{.Name}} command{{if .Flags}} [command options]{{end}} {{.ArgsUsage}} COMMANDS: {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} @@ -69,9 +69,10 @@ OPTIONS: ` var helpCommand = Command{ - Name: "help", - Aliases: []string{"h"}, - Usage: "Shows a list of commands or help for one command", + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", Action: func(c *Context) { args := c.Args() if args.Present() { @@ -83,9 +84,10 @@ var helpCommand = Command{ } var helpSubcommand = Command{ - Name: "help", - Aliases: []string{"h"}, - Usage: "Shows a list of commands or help for one command", + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", Action: func(c *Context) { args := c.Args() if args.Present() { From ecb0b5ac0a95eadc723d375ebb5d4f45af9f36b2 Mon Sep 17 00:00:00 2001 From: Tristan Zajonc Date: Tue, 4 Aug 2015 21:13:28 -0700 Subject: [PATCH 050/381] improve help by including parent command for subcommands --- command.go | 4 ++++ help.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 5b6cd66..d03b7e7 100644 --- a/command.go +++ b/command.go @@ -39,6 +39,9 @@ type Command struct { // Boolean to hide built-in help command HideHelp bool + // Name of parent command for help, defaults to app.Name and parent + // command.Name. + ParentName string commandNamePath []string } @@ -196,6 +199,7 @@ func (c Command) startApp(ctx *Context) error { var newCmds []Command for _, cc := range app.Commands { + cc.ParentName = app.Name cc.commandNamePath = []string{c.Name, cc.Name} newCmds = append(newCmds, cc) } diff --git a/help.go b/help.go index 0855eb6..ff739ff 100644 --- a/help.go +++ b/help.go @@ -38,10 +38,10 @@ COPYRIGHT: // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{.FullName}} - {{.Usage}} + {{.ParentName}} {{.Name}} - {{.Usage}} USAGE: - command {{.FullName}}{{if .Flags}} [command options]{{end}} {{.ArgsUsage}}{{if .Description}} + {{.ParentName}} {{.Name}}{{if .Flags}} [command options]{{end}} {{.ArgsUsage}}{{if .Description}} DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} From aced6e8739166ac46d05f741f60891d3cf2331e9 Mon Sep 17 00:00:00 2001 From: Tristan Zajonc Date: Wed, 12 Aug 2015 21:43:14 -0700 Subject: [PATCH 051/381] fix tests --- app.go | 18 +++++++++++++++++- app_test.go | 8 ++++---- command.go | 10 +++------- help.go | 10 +++++----- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/app.go b/app.go index aadbea6..7da79d8 100644 --- a/app.go +++ b/app.go @@ -13,6 +13,8 @@ import ( type App struct { // The name of the program. Defaults to os.Args[0] Name string + // Name of command for help, defaults to Name + HelpName string // Description of the program. Usage string // Description of the program argument format. @@ -69,8 +71,8 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: os.Args[0], + HelpName: os.Args[0], Usage: "A new cli application", - ArgsUsage: "[arguments...]", Version: "0.0.0", BashComplete: DefaultAppComplete, Action: helpCommand.Action, @@ -85,6 +87,13 @@ func (a *App) Run(arguments []string) (err error) { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } + newCmds := []Command{} + for _, c := range a.Commands { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + newCmds = append(newCmds, c) + } + a.Commands = newCmds + // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) @@ -188,6 +197,13 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } + newCmds := []Command{} + for _, c := range a.Commands { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + newCmds = append(newCmds, c) + } + a.Commands = newCmds + // append flags if a.EnableBashCompletion { a.appendFlag(BashCompletionFlag) diff --git a/app_test.go b/app_test.go index 4c6787a..2b53b4f 100644 --- a/app_test.go +++ b/app_test.go @@ -90,10 +90,10 @@ func ExampleAppHelp() { app.Run(os.Args) // Output: // NAME: - // describeit - use it to see a description + // greet describeit - use it to see a description // // USAGE: - // command describeit [arguments...] + // greet describeit [arguments...] // // DESCRIPTION: // This is how we describe describeit the function @@ -737,7 +737,7 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { app := NewApp() buf := new(bytes.Buffer) app.Writer = buf - + app.Name = "command" subCmd := Command{ Name: "bar", Usage: "does bar things", @@ -755,7 +755,7 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "foo bar - does bar things") { + if !strings.Contains(output, "command foo bar - does bar things") { t.Errorf("expected full path to subcommand: %s", output) } if !strings.Contains(output, "command foo bar [arguments...]") { diff --git a/command.go b/command.go index d03b7e7..2851291 100644 --- a/command.go +++ b/command.go @@ -39,9 +39,8 @@ type Command struct { // Boolean to hide built-in help command HideHelp bool - // Name of parent command for help, defaults to app.Name and parent - // command.Name. - ParentName string + // Name of command for help, defaults to full command name + HelpName string commandNamePath []string } @@ -158,14 +157,12 @@ func (c Command) startApp(ctx *Context) error { // set the name and usage app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) if c.Description != "" { app.Usage = c.Description } else { app.Usage = c.Usage } - if c.ArgsUsage == "" { - c.ArgsUsage = "[arguments...]" - } // set CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound @@ -199,7 +196,6 @@ func (c Command) startApp(ctx *Context) error { var newCmds []Command for _, cc := range app.Commands { - cc.ParentName = app.Name cc.commandNamePath = []string{c.Name, cc.Name} newCmds = append(newCmds, cc) } diff --git a/help.go b/help.go index ff739ff..e6ba0de 100644 --- a/help.go +++ b/help.go @@ -15,7 +15,7 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{.ArgsUsage}} + {{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{if .Version}} VERSION: {{.Version}} @@ -38,10 +38,10 @@ COPYRIGHT: // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{.ParentName}} {{.Name}} - {{.Usage}} + {{.HelpName}} - {{.Usage}} USAGE: - {{.ParentName}} {{.Name}}{{if .Flags}} [command options]{{end}} {{.ArgsUsage}}{{if .Description}} + {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}} DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} @@ -55,10 +55,10 @@ OPTIONS: // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var SubcommandHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} + {{.HelpName}} - {{.Usage}} USAGE: - {{.Name}} command{{if .Flags}} [command options]{{end}} {{.ArgsUsage}} + {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} COMMANDS: {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} From cc46ca102013a8010e23ce62ee94986e40a48c3c Mon Sep 17 00:00:00 2001 From: Tristan Zajonc Date: Wed, 12 Aug 2015 21:58:25 -0700 Subject: [PATCH 052/381] allow overriding help name --- app.go | 8 ++++++-- command.go | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 7da79d8..26dfd7d 100644 --- a/app.go +++ b/app.go @@ -89,7 +89,9 @@ func (a *App) Run(arguments []string) (err error) { newCmds := []Command{} for _, c := range a.Commands { - c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + if c.HelpName == "" { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + } newCmds = append(newCmds, c) } a.Commands = newCmds @@ -199,7 +201,9 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { newCmds := []Command{} for _, c := range a.Commands { - c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + if c.HelpName == "" { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + } newCmds = append(newCmds, c) } a.Commands = newCmds diff --git a/command.go b/command.go index 2851291..9491c30 100644 --- a/command.go +++ b/command.go @@ -157,7 +157,12 @@ func (c Command) startApp(ctx *Context) error { // set the name and usage app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) - app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + if c.HelpName == "" { + app.HelpName = c.HelpName + } else { + app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + } + if c.Description != "" { app.Usage = c.Description } else { From c7aac252f1b9115886ff47f2ffbba0db3d693d99 Mon Sep 17 00:00:00 2001 From: Tristan Zajonc Date: Wed, 12 Aug 2015 22:14:26 -0700 Subject: [PATCH 053/381] add tests --- app.go | 2 +- app_test.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++ command.go | 2 +- 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 26dfd7d..4d40014 100644 --- a/app.go +++ b/app.go @@ -13,7 +13,7 @@ import ( type App struct { // The name of the program. Defaults to os.Args[0] Name string - // Name of command for help, defaults to Name + // Full name of command for help, defaults to Name HelpName string // Description of the program. Usage string diff --git a/app_test.go b/app_test.go index 2b53b4f..ada5d69 100644 --- a/app_test.go +++ b/app_test.go @@ -763,6 +763,99 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } } +func TestApp_Run_SubcommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "command" + subCmd := Command{ + Name: "bar", + HelpName: "custom", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "custom - does bar things") { + t.Errorf("expected HelpName for subcommand: %s", output) + } + if !strings.Contains(output, "custom [arguments...]") { + t.Errorf("expected HelpName to subcommand: %s", output) + } +} + +func TestApp_Run_CommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "command" + subCmd := Command{ + Name: "bar", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + HelpName: "custom", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "command foo bar - does bar things") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "command foo bar [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + +func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "base" + subCmd := Command{ + Name: "bar", + HelpName: "custom", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "base foo - foo commands") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "base foo command [command options] [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + func TestApp_Run_Help(t *testing.T) { var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}} diff --git a/command.go b/command.go index 9491c30..fac754d 100644 --- a/command.go +++ b/command.go @@ -39,7 +39,7 @@ type Command struct { // Boolean to hide built-in help command HideHelp bool - // Name of command for help, defaults to full command name + // Full name of command for help, defaults to full command name, including parent commands. HelpName string commandNamePath []string } From 543102d9c3b12b0ce0d33ed80966c8e9945a98ec Mon Sep 17 00:00:00 2001 From: Kevin Klues Date: Fri, 28 Aug 2015 18:15:08 -0700 Subject: [PATCH 054/381] Set default PROG in bash_autocomplete By setting the default value of PROG to the basname of whatever the filename is, we allow bash_autocomplete to be copied into /etc/bash_completion.d without modification. --- README.md | 19 +++++++++++++------ autocomplete/bash_autocomplete | 2 ++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 85b9cda..02da429 100644 --- a/README.md +++ b/README.md @@ -293,12 +293,19 @@ setting the `PROG` variable to the name of your program: #### To Distribute -Copy and modify `autocomplete/bash_autocomplete` to use your program name -rather than `$PROG` and have the user copy the file into -`/etc/bash_completion.d/` (or automatically install it there if you are -distributing a package). Alternatively you can just document that users should -source the generic `autocomplete/bash_autocomplete` with `$PROG` set to your -program name in their bash configuration. +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file to make it active in the current shell. + +``` + sudo cp src/bash_autocomplete /etc/bash_completion.d/ + source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should source the generic +`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set +to the name of their program (as above). ## Contribution Guidelines Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. diff --git a/autocomplete/bash_autocomplete b/autocomplete/bash_autocomplete index 9b55dd9..d9231f4 100644 --- a/autocomplete/bash_autocomplete +++ b/autocomplete/bash_autocomplete @@ -1,5 +1,7 @@ #! /bin/bash +: ${PROG:=$(basename ${BASH_SOURCE})} + _cli_bash_autocomplete() { local cur prev opts base COMPREPLY=() From 4ad8f298e2f29062ee18744d7f608e1106339111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Dupanovi=C4=87?= Date: Tue, 8 Sep 2015 11:00:04 +0200 Subject: [PATCH 055/381] Improve formatting --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 02da429..fa220e2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) # cli.go -cli.go is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. +`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. You can view the API docs here: http://godoc.org/github.com/codegangsta/cli @@ -9,7 +9,7 @@ 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). @@ -25,7 +25,7 @@ export PATH=$PATH:$GOPATH/bin ``` ## Getting Started -One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`. +One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. ``` go package main @@ -103,7 +103,8 @@ $ greet Hello friend! ``` -cli.go also generates some bitchass help text: +`cli.go` also generates some bitchass help text: + ``` $ greet help NAME: From c80fcac42b33d736f9fce2c64146fb7304e80076 Mon Sep 17 00:00:00 2001 From: Nodir Turakulov Date: Sun, 27 Sep 2015 23:42:17 -0700 Subject: [PATCH 056/381] Honor HideHelp and HideVersion App.Run does not check a.Hide{Help,Version} before checking it, as a result customers cannot define their own -v flag (e.g. for verbose) --- app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index e7caec9..10386c4 100644 --- a/app.go +++ b/app.go @@ -123,11 +123,11 @@ func (a *App) Run(arguments []string) (err error) { return nil } - if checkHelp(context) { + if !a.HideHelp && checkHelp(context) { return nil } - if checkVersion(context) { + if !a.HideVersion && checkVersion(context) { return nil } From a9585bb11cf5bca52069c8f88ff95fa9c469b1a4 Mon Sep 17 00:00:00 2001 From: Emil Thelin Date: Sat, 3 Oct 2015 17:19:04 +0200 Subject: [PATCH 057/381] Added a flag for TestCommandIgnoreFlags --- command_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command_test.go b/command_test.go index 688d12c..fd39548 100644 --- a/command_test.go +++ b/command_test.go @@ -28,7 +28,7 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) { func TestCommandIgnoreFlags(t *testing.T) { app := NewApp() set := flag.NewFlagSet("test", 0) - test := []string{"blah", "blah"} + test := []string{"blah", "blah", "-break"} set.Parse(test) c := NewContext(app, set, nil) From be8ef1524568165aef564d2ecc64972131912bd7 Mon Sep 17 00:00:00 2001 From: Lowe Thiderman Date: Tue, 6 Oct 2015 20:17:33 +0200 Subject: [PATCH 058/381] Remove 'bitchass' from README Having needless derogatory words in the description of the project is just juvenile, and this stood out as a blemish on an otherwise pretty flawless introduction. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa220e2..cdac25a 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ $ greet Hello friend! ``` -`cli.go` also generates some bitchass help text: +`cli.go` also generates neat help text: ``` $ greet help From db7af859d2c107233bdc71be3cf174ea094435ed Mon Sep 17 00:00:00 2001 From: elij Date: Tue, 13 Oct 2015 15:03:47 -0700 Subject: [PATCH 059/381] make help and version parsing use actual specified values fixes #254 --- help.go | 28 ++++++++++++++++--------- help_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/help.go b/help.go index e6ba0de..5bfe401 100644 --- a/help.go +++ b/help.go @@ -186,21 +186,29 @@ func printHelp(out io.Writer, templ string, data interface{}) { } func checkVersion(c *Context) bool { - if c.GlobalBool("version") || c.GlobalBool("v") || c.Bool("version") || c.Bool("v") { - ShowVersion(c) - return true + found := false + if VersionFlag.Name != "" { + eachName(VersionFlag.Name, func(name string) { + if c.GlobalBool(name) || c.Bool(name) { + ShowVersion(c) + found = true + } + }) } - - return false + return found } func checkHelp(c *Context) bool { - if c.GlobalBool("h") || c.GlobalBool("help") || c.Bool("h") || c.Bool("help") { - ShowAppHelp(c) - return true + found := false + if HelpFlag.Name != "" { + eachName(HelpFlag.Name, func(name string) { + if c.GlobalBool(name) || c.Bool(name) { + ShowAppHelp(c) + found = true + } + }) } - - return false + return found } func checkCommandHelp(c *Context, name string) bool { diff --git a/help_test.go b/help_test.go index 42d0284..350e263 100644 --- a/help_test.go +++ b/help_test.go @@ -34,3 +34,61 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) { t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") } } + +func Test_Help_Custom_Flags(t *testing.T) { + oldFlag := HelpFlag + defer func() { + HelpFlag = oldFlag + }() + + HelpFlag = BoolFlag{ + Name: "help, x", + Usage: "show help", + } + + app := App{ + Flags: []Flag{ + BoolFlag{Name: "foo, h"}, + }, + Action: func(ctx *Context) { + if ctx.Bool("h") != true { + t.Errorf("custom help flag not set") + } + }, + } + output := new(bytes.Buffer) + app.Writer = output + app.Run([]string{"test", "-h"}) + if output.Len() > 0 { + t.Errorf("unexpected output: %s", output.String()) + } +} + +func Test_Version_Custom_Flags(t *testing.T) { + oldFlag := VersionFlag + defer func() { + VersionFlag = oldFlag + }() + + VersionFlag = BoolFlag{ + Name: "version, V", + Usage: "show version", + } + + app := App{ + Flags: []Flag{ + BoolFlag{Name: "foo, v"}, + }, + Action: func(ctx *Context) { + if ctx.Bool("v") != true { + t.Errorf("custom version flag not set") + } + }, + } + output := new(bytes.Buffer) + app.Writer = output + app.Run([]string{"test", "-v"}) + if output.Len() > 0 { + t.Errorf("unexpected output: %s", output.String()) + } +} From 443fff6934951d49f530371c5e20329cb21b5d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Bj=C3=B6rklin?= Date: Sun, 18 Oct 2015 20:32:50 +0200 Subject: [PATCH 060/381] Added coverage and reference logos to README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cdac25a..2346557 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ +[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) [![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) +[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) # cli.go `cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. -You can view the API docs here: -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. From 732e97aee8f2fd38b1ff346354d81bdfb5e1a5ad Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Sun, 18 Oct 2015 17:02:23 -0700 Subject: [PATCH 061/381] only display app version and help message once When processing the flags for -h/--help and -v/--version, only check the flags in checkVersion() and checkHelp() instead of also printing the associated message. This fixes the problem of `app -h` and `app -v` printing their output twice. The doubling was caused by printing the message once for each registred alias for the given flags (-h/--help and -v/--version). Resolves #285 --- app.go | 2 ++ help.go | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 1a77f3b..9a15c0c 100644 --- a/app.go +++ b/app.go @@ -138,10 +138,12 @@ func (a *App) Run(arguments []string) (err error) { } if !a.HideHelp && checkHelp(context) { + ShowAppHelp(context) return nil } if !a.HideVersion && checkVersion(context) { + ShowVersion(context) return nil } diff --git a/help.go b/help.go index 5bfe401..a246f63 100644 --- a/help.go +++ b/help.go @@ -190,7 +190,6 @@ func checkVersion(c *Context) bool { if VersionFlag.Name != "" { eachName(VersionFlag.Name, func(name string) { if c.GlobalBool(name) || c.Bool(name) { - ShowVersion(c) found = true } }) @@ -203,7 +202,6 @@ func checkHelp(c *Context) bool { if HelpFlag.Name != "" { eachName(HelpFlag.Name, func(name string) { if c.GlobalBool(name) || c.Bool(name) { - ShowAppHelp(c) found = true } }) From 8b46886de806f530a31802bd135a808ff33d5761 Mon Sep 17 00:00:00 2001 From: Kaushal Subedi Date: Sat, 24 Oct 2015 23:37:21 -0600 Subject: [PATCH 062/381] added flag to have a custom text on the USAGE section of help --- command.go | 2 ++ help.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index fac754d..500f235 100644 --- a/command.go +++ b/command.go @@ -16,6 +16,8 @@ type Command struct { Aliases []string // A short description of the usage of this command Usage string + // Custom text to show on USAGE section of help + UsageText string // A longer explanation of how the command works Description string // A short description of the arguments of this command diff --git a/help.go b/help.go index a246f63..ff7c4cb 100644 --- a/help.go +++ b/help.go @@ -15,7 +15,7 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} {{if .Version}} VERSION: {{.Version}} From c70ad9b688cec7cea81b9886467987bf478bb5c5 Mon Sep 17 00:00:00 2001 From: Kaushal Subedi Date: Sat, 24 Oct 2015 23:51:06 -0600 Subject: [PATCH 063/381] fixed tests --- app.go | 3 +++ app_test.go | 1 + 2 files changed, 4 insertions(+) diff --git a/app.go b/app.go index 9a15c0c..df6ecaf 100644 --- a/app.go +++ b/app.go @@ -17,6 +17,8 @@ type App struct { HelpName string // Description of the program. Usage string + // Text to override the USAGE section of help + UsageText string // Description of the program argument format. ArgsUsage string // Version of the program @@ -73,6 +75,7 @@ func NewApp() *App { Name: os.Args[0], HelpName: os.Args[0], Usage: "A new cli application", + UsageText: "", Version: "0.0.0", BashComplete: DefaultAppComplete, Action: helpCommand.Action, diff --git a/app_test.go b/app_test.go index ada5d69..93a67d0 100644 --- a/app_test.go +++ b/app_test.go @@ -22,6 +22,7 @@ func ExampleApp() { app.Action = func(c *Context) { fmt.Printf("Hello %v\n", c.String("name")) } + app.UsageText = "app [first_arg] [second_arg]" app.Author = "Harrison" app.Email = "harrison@lolwut.com" app.Authors = []Author{Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} From 34639643ac150e3138f728e1c657a61ba3fa4bf7 Mon Sep 17 00:00:00 2001 From: Jille Timmermans Date: Wed, 28 Oct 2015 14:39:36 +0000 Subject: [PATCH 064/381] bash_autocomplete: Remove unused local variable prev --- autocomplete/bash_autocomplete | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/autocomplete/bash_autocomplete b/autocomplete/bash_autocomplete index d9231f4..21a232f 100644 --- a/autocomplete/bash_autocomplete +++ b/autocomplete/bash_autocomplete @@ -3,13 +3,12 @@ : ${PROG:=$(basename ${BASH_SOURCE})} _cli_bash_autocomplete() { - local cur prev opts base + local cur opts base COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 } - complete -F _cli_bash_autocomplete $PROG \ No newline at end of file + complete -F _cli_bash_autocomplete $PROG From c538c376c9859540836cbbf2096fa90efa9a602f Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 25 Sep 2015 14:13:36 -0700 Subject: [PATCH 065/381] Do not return error when flag parsing should be skipped Signed-off-by: Nathan LeClaire --- command.go | 6 ++++++ command_test.go | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/command.go b/command.go index fac754d..7a1dcab 100644 --- a/command.go +++ b/command.go @@ -102,6 +102,12 @@ func (c Command) Run(ctx *Context) error { err = set.Parse(append(flagArgs, regularArgs...)) } else { err = set.Parse(ctx.Args().Tail()) + + // Work around issue where if the first arg in ctx.Args.Tail() + // is a flag, set.Parse returns an error + if c.SkipFlagParsing { + err = nil + } } if err != nil { diff --git a/command_test.go b/command_test.go index fd39548..bf7cc99 100644 --- a/command_test.go +++ b/command_test.go @@ -45,3 +45,25 @@ func TestCommandIgnoreFlags(t *testing.T) { expect(t, err, nil) } + +// Fix bug with ignoring flag parsing that would still parse the first flag +func TestCommandIgnoreFlagsIncludingFirstArgument(t *testing.T) { + app := NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"blah", "-break"} + set.Parse(test) + + c := NewContext(app, set, nil) + + command := Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) {}, + SkipFlagParsing: true, + } + err := command.Run(c) + + expect(t, err, nil) +} From bc3cb33cef749380a3e1ae4c1a04312521c5f0be Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 17 Oct 2015 11:36:09 -0700 Subject: [PATCH 066/381] Actually skip parsing of flags if SkipFlagParsing is set Previous just skipped if firstFlagIndex was > -1 --- command.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/command.go b/command.go index 7a1dcab..c63b049 100644 --- a/command.go +++ b/command.go @@ -86,27 +86,23 @@ func (c Command) Run(ctx *Context) error { } var err error - if firstFlagIndex > -1 && !c.SkipFlagParsing { - args := ctx.Args() - regularArgs := make([]string, len(args[1:firstFlagIndex])) - copy(regularArgs, args[1:firstFlagIndex]) - - var flagArgs []string - if terminatorIndex > -1 { - flagArgs = args[firstFlagIndex:terminatorIndex] - regularArgs = append(regularArgs, args[terminatorIndex:]...) + if !c.SkipFlagParsing { + if firstFlagIndex > -1 { + args := ctx.Args() + regularArgs := make([]string, len(args[1:firstFlagIndex])) + copy(regularArgs, args[1:firstFlagIndex]) + + var flagArgs []string + if terminatorIndex > -1 { + flagArgs = args[firstFlagIndex:terminatorIndex] + regularArgs = append(regularArgs, args[terminatorIndex:]...) + } else { + flagArgs = args[firstFlagIndex:] + } + + err = set.Parse(append(flagArgs, regularArgs...)) } else { - flagArgs = args[firstFlagIndex:] - } - - err = set.Parse(append(flagArgs, regularArgs...)) - } else { - err = set.Parse(ctx.Args().Tail()) - - // Work around issue where if the first arg in ctx.Args.Tail() - // is a flag, set.Parse returns an error - if c.SkipFlagParsing { - err = nil + err = set.Parse(ctx.Args().Tail()) } } From 6191d931b7fc228924bd8dd6a3952301b14078af Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 26 Oct 2015 21:40:03 -0700 Subject: [PATCH 067/381] When skipping flag parsing, still parse into arguments Fool the FlagSet into thinking that all arguments are actually arguments rather than attempting to parse them as flags. --- command.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/command.go b/command.go index c63b049..824e77b 100644 --- a/command.go +++ b/command.go @@ -74,19 +74,19 @@ func (c Command) Run(ctx *Context) error { set := flagSet(c.Name, c.Flags) set.SetOutput(ioutil.Discard) - firstFlagIndex := -1 - terminatorIndex := -1 - for index, arg := range ctx.Args() { - if arg == "--" { - terminatorIndex = index - break - } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { - firstFlagIndex = index - } - } - var err error if !c.SkipFlagParsing { + firstFlagIndex := -1 + terminatorIndex := -1 + for index, arg := range ctx.Args() { + if arg == "--" { + terminatorIndex = index + break + } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { + firstFlagIndex = index + } + } + if firstFlagIndex > -1 { args := ctx.Args() regularArgs := make([]string, len(args[1:firstFlagIndex])) @@ -104,6 +104,10 @@ func (c Command) Run(ctx *Context) error { } else { err = set.Parse(ctx.Args().Tail()) } + } else { + if c.SkipFlagParsing { + err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) + } } if err != nil { From 8cd49b108c0b76b4de71d89a106c7f6e59e536fe Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 26 Oct 2015 21:45:28 -0700 Subject: [PATCH 068/381] Update TestCommandIgnoreFlagsIncludingFirstArgument to test for arguments --- command_test.go | 3 ++- helpers_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/command_test.go b/command_test.go index bf7cc99..1e9e7fa 100644 --- a/command_test.go +++ b/command_test.go @@ -64,6 +64,7 @@ func TestCommandIgnoreFlagsIncludingFirstArgument(t *testing.T) { SkipFlagParsing: true, } err := command.Run(c) - expect(t, err, nil) + + expect(t, []string(c.Args()), []string{"blah", "-break"}) } diff --git a/helpers_test.go b/helpers_test.go index 3ce8e93..b1b7339 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -7,13 +7,13 @@ import ( /* Test Helpers */ func expect(t *testing.T, a interface{}, b interface{}) { - if a != b { + if !reflect.DeepEqual(a, b) { t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } func refute(t *testing.T, a interface{}, b interface{}) { - if a == b { + if reflect.DeepEqual(a, b) { t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } From 3323ab44608a53d356d25fa462a114b111950f2f Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Tue, 27 Oct 2015 13:33:55 -0700 Subject: [PATCH 069/381] Use a test table and add --help test Signed-off-by: Nathan LeClaire --- command_test.go | 81 +++++++++++++++++-------------------------------- 1 file changed, 27 insertions(+), 54 deletions(-) diff --git a/command_test.go b/command_test.go index 1e9e7fa..dd9fc87 100644 --- a/command_test.go +++ b/command_test.go @@ -1,70 +1,43 @@ package cli import ( + "errors" "flag" "testing" ) -func TestCommandDoNotIgnoreFlags(t *testing.T) { - app := NewApp() - set := flag.NewFlagSet("test", 0) - test := []string{"blah", "blah", "-break"} - set.Parse(test) - - c := NewContext(app, set, nil) - - command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) {}, +func TestCommandFlagParsing(t *testing.T) { + cases := []struct { + testArgs []string + skipFlagParsing bool + expectedErr error + }{ + {[]string{"blah", "blah", "-break"}, false, errors.New("flag provided but not defined: -break")}, // Test normal "not ignoring flags" flow + {[]string{"blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags + {[]string{"blah", "-break"}, true, nil}, // Test SkipFlagParsing with random flag arg + {[]string{"blah", "-help"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg } - err := command.Run(c) - - expect(t, err.Error(), "flag provided but not defined: -break") -} -func TestCommandIgnoreFlags(t *testing.T) { - app := NewApp() - set := flag.NewFlagSet("test", 0) - test := []string{"blah", "blah", "-break"} - set.Parse(test) + for _, c := range cases { + app := NewApp() + set := flag.NewFlagSet("test", 0) + set.Parse(c.testArgs) - c := NewContext(app, set, nil) + context := NewContext(app, set, nil) - command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) {}, - SkipFlagParsing: true, - } - err := command.Run(c) - - expect(t, err, nil) -} + command := Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) {}, + } -// Fix bug with ignoring flag parsing that would still parse the first flag -func TestCommandIgnoreFlagsIncludingFirstArgument(t *testing.T) { - app := NewApp() - set := flag.NewFlagSet("test", 0) - test := []string{"blah", "-break"} - set.Parse(test) + command.SkipFlagParsing = c.skipFlagParsing - c := NewContext(app, set, nil) + err := command.Run(context) - command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) {}, - SkipFlagParsing: true, + expect(t, err, c.expectedErr) + expect(t, []string(context.Args()), c.testArgs) } - err := command.Run(c) - expect(t, err, nil) - - expect(t, []string(c.Args()), []string{"blah", "-break"}) } From 7c6caf69efa3618d60d2f287280d903d6bb7116a Mon Sep 17 00:00:00 2001 From: Gabe Rosenhouse Date: Fri, 13 Nov 2015 23:35:57 -0800 Subject: [PATCH 070/381] Update golang versions used by Travis CI - current latest: 1.5.1 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 34d39c8..b08641d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ go: - 1.2.2 - 1.3.3 - 1.4.2 +- 1.5.1 script: - go vet ./... From bb7e45acf18a321a6ae06bd7c8f7ead624907c67 Mon Sep 17 00:00:00 2001 From: ston1th Date: Sat, 14 Nov 2015 20:01:15 +0100 Subject: [PATCH 071/381] Added destination scan support for flags --- flag.go | 63 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/flag.go b/flag.go index 531b091..ea830f8 100644 --- a/flag.go +++ b/flag.go @@ -237,9 +237,10 @@ func (f IntSliceFlag) getName() string { // BoolFlag is a switch that defaults to false type BoolFlag struct { - Name string - Usage string - EnvVar string + Name string + Usage string + EnvVar string + Destination *bool } // String returns a readable representation of this value (for usage defaults) @@ -264,6 +265,10 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { } eachName(f.Name, func(name string) { + if f.Destination != nil { + set.BoolVar(f.Destination, name, val, f.Usage) + return + } set.Bool(name, val, f.Usage) }) } @@ -312,10 +317,11 @@ func (f BoolTFlag) getName() string { // StringFlag represents a flag that takes as string value type StringFlag struct { - Name string - Value string - Usage string - EnvVar string + Name string + Value string + Usage string + EnvVar string + Destination *string } // String returns the usage @@ -345,6 +351,10 @@ func (f StringFlag) Apply(set *flag.FlagSet) { } eachName(f.Name, func(name string) { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) + return + } set.String(name, f.Value, f.Usage) }) } @@ -356,10 +366,11 @@ func (f StringFlag) getName() string { // IntFlag is a flag that takes an integer // Errors if the value provided cannot be parsed type IntFlag struct { - Name string - Value int - Usage string - EnvVar string + Name string + Value int + Usage string + EnvVar string + Destination *int } // String returns the usage @@ -383,6 +394,10 @@ func (f IntFlag) Apply(set *flag.FlagSet) { } eachName(f.Name, func(name string) { + if f.Destination != nil { + set.IntVar(f.Destination, name, f.Value, f.Usage) + return + } set.Int(name, f.Value, f.Usage) }) } @@ -394,10 +409,11 @@ func (f IntFlag) getName() string { // DurationFlag is a flag that takes a duration specified in Go's duration // format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { - Name string - Value time.Duration - Usage string - EnvVar string + Name string + Value time.Duration + Usage string + EnvVar string + Destination *time.Duration } // String returns a readable representation of this value (for usage defaults) @@ -421,6 +437,10 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { } eachName(f.Name, func(name string) { + if f.Destination != nil { + set.DurationVar(f.Destination, name, f.Value, f.Usage) + return + } set.Duration(name, f.Value, f.Usage) }) } @@ -432,10 +452,11 @@ func (f DurationFlag) getName() string { // Float64Flag is a flag that takes an float value // Errors if the value provided cannot be parsed type Float64Flag struct { - Name string - Value float64 - Usage string - EnvVar string + Name string + Value float64 + Usage string + EnvVar string + Destination *float64 } // String returns the usage @@ -458,6 +479,10 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { } eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Float64Var(f.Destination, name, f.Value, f.Usage) + return + } set.Float64(name, f.Value, f.Usage) }) } From 25ef3682351649cc0cedf9f5bbd14399247fb33b Mon Sep 17 00:00:00 2001 From: ston1th Date: Sat, 14 Nov 2015 22:39:38 +0100 Subject: [PATCH 072/381] added destination scan testing and BoolT --- flag.go | 11 +++++-- flag_test.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/flag.go b/flag.go index ea830f8..9b22d7f 100644 --- a/flag.go +++ b/flag.go @@ -280,9 +280,10 @@ func (f BoolFlag) getName() string { // BoolTFlag this represents a boolean flag that is true by default, but can // still be set to false by --some-flag=false type BoolTFlag struct { - Name string - Usage string - EnvVar string + Name string + Usage string + EnvVar string + Destination *bool } // String returns a readable representation of this value (for usage defaults) @@ -307,6 +308,10 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { } eachName(f.Name, func(name string) { + if f.Destination != nil { + set.BoolVar(f.Destination, name, val, f.Usage) + return + } set.Bool(name, val, f.Usage) }) } diff --git a/flag_test.go b/flag_test.go index 3606102..4462d3f 100644 --- a/flag_test.go +++ b/flag_test.go @@ -305,6 +305,24 @@ func TestParseMultiString(t *testing.T) { }).Run([]string{"run", "-s", "10"}) } +func TestParseDestinationString(t *testing.T) { + var dest string + a := App{ + Flags: []Flag{ + StringFlag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) { + if dest != "10" { + t.Errorf("expected destination String 10") + } + }, + } + a.Run([]string{"run", "--dest", "10"}) +} + func TestParseMultiStringFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_COUNT", "20") @@ -412,6 +430,24 @@ func TestParseMultiInt(t *testing.T) { a.Run([]string{"run", "-s", "10"}) } +func TestParseDestinationInt(t *testing.T) { + var dest int + a := App{ + Flags: []Flag{ + IntFlag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) { + if dest != 10 { + t.Errorf("expected destination Int 10") + } + }, + } + a.Run([]string{"run", "--dest", "10"}) +} + func TestParseMultiIntFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "10") @@ -521,6 +557,24 @@ func TestParseMultiFloat64(t *testing.T) { a.Run([]string{"run", "-s", "10.2"}) } +func TestParseDestinationFloat64(t *testing.T) { + var dest float64 + a := App{ + Flags: []Flag{ + Float64Flag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) { + if dest != 10.2 { + t.Errorf("expected destination Float64 10.2") + } + }, + } + a.Run([]string{"run", "--dest", "10.2"}) +} + func TestParseMultiFloat64FromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "15.5") @@ -576,6 +630,24 @@ func TestParseMultiBool(t *testing.T) { a.Run([]string{"run", "--serve"}) } +func TestParseDestinationBool(t *testing.T) { + var dest bool + a := App{ + Flags: []Flag{ + BoolFlag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) { + if dest != true { + t.Errorf("expected destination Bool true") + } + }, + } + a.Run([]string{"run", "--dest"}) +} + func TestParseMultiBoolFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_DEBUG", "1") @@ -631,6 +703,24 @@ func TestParseMultiBoolT(t *testing.T) { a.Run([]string{"run", "--serve"}) } +func TestParseDestinationBoolT(t *testing.T) { + var dest bool + a := App{ + Flags: []Flag{ + BoolTFlag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) { + if dest != true { + t.Errorf("expected destination BoolT true") + } + }, + } + a.Run([]string{"run", "--dest"}) +} + func TestParseMultiBoolTFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_DEBUG", "0") From 4579bbf129a8e607313bf03af8d24e0f79ed0aa4 Mon Sep 17 00:00:00 2001 From: ston1th Date: Sun, 15 Nov 2015 10:59:12 +0100 Subject: [PATCH 073/381] added description to README.md --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 2346557..26a1838 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,32 @@ app.Action = func(c *cli.Context) { ... ``` +You can also set a destination variable for a flag, to which the content will be scanned. +``` go +... +var language string +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, +} +app.Action = func(c *cli.Context) { + name := "someone" + if len(c.Args()) > 0 { + name = c.Args()[0] + } + if language == "spanish" { + println("Hola", name) + } else { + println("Hello", name) + } +} +... +``` + See full list of flags at http://godoc.org/github.com/codegangsta/cli #### Alternate Names From 4fc241fb17ec6371aedd3beaf8af22647bb16369 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 15 Nov 2015 13:07:28 -0800 Subject: [PATCH 074/381] Update examples to use correct naming convention See https://golang.org/pkg/testing/#hdr-Examples --- app_test.go | 8 ++++---- cli_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app_test.go b/app_test.go index ada5d69..28d8e0f 100644 --- a/app_test.go +++ b/app_test.go @@ -10,7 +10,7 @@ import ( "testing" ) -func ExampleApp() { +func ExampleApp_Run() { // set args for examples sake os.Args = []string{"greet", "--name", "Jeremy"} @@ -30,7 +30,7 @@ func ExampleApp() { // Hello Jeremy } -func ExampleAppSubcommand() { +func ExampleApp_Run_subcommand() { // set args for examples sake os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} app := NewApp() @@ -67,7 +67,7 @@ func ExampleAppSubcommand() { // Hello, Jeremy } -func ExampleAppHelp() { +func ExampleApp_Run_help() { // set args for examples sake os.Args = []string{"greet", "h", "describeit"} @@ -99,7 +99,7 @@ func ExampleAppHelp() { // This is how we describe describeit the function } -func ExampleAppBashComplete() { +func ExampleApp_Run_bashComplete() { // set args for examples sake os.Args = []string{"greet", "--generate-bash-completion"} diff --git a/cli_test.go b/cli_test.go index e54f8e2..4488939 100644 --- a/cli_test.go +++ b/cli_test.go @@ -4,7 +4,7 @@ import ( "os" ) -func Example() { +func Example_App_Run() { app := NewApp() app.Name = "todo" app.Usage = "task list on the command line" @@ -30,7 +30,7 @@ func Example() { app.Run(os.Args) } -func ExampleSubcommand() { +func Example_App_Run_subcommand() { app := NewApp() app.Name = "say" app.Commands = []Command{ From 631930114fcba871b80c458357930649d5783c48 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 15 Nov 2015 13:07:49 -0800 Subject: [PATCH 075/381] Also test on Go tip But allow it to fail --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index b08641d..87ba52f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,11 @@ go: - 1.3.3 - 1.4.2 - 1.5.1 +- tip + +matrix: + allow_failures: + - go: tip script: - go vet ./... From 0f218fffa5d7091ccf05ff6568b9f421945e4d19 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 15 Nov 2015 13:34:53 -0800 Subject: [PATCH 076/381] Remave cli_test.go Examples are redundant with the ones in app_test.go --- cli_test.go | 98 ----------------------------------------------------- 1 file changed, 98 deletions(-) delete mode 100644 cli_test.go diff --git a/cli_test.go b/cli_test.go deleted file mode 100644 index 4488939..0000000 --- a/cli_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package cli - -import ( - "os" -) - -func Example_App_Run() { - app := NewApp() - app.Name = "todo" - app.Usage = "task list on the command line" - app.Commands = []Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *Context) { - println("added task: ", c.Args().First()) - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *Context) { - println("completed task: ", c.Args().First()) - }, - }, - } - - app.Run(os.Args) -} - -func Example_App_Run_subcommand() { - app := NewApp() - app.Name = "say" - app.Commands = []Command{ - { - Name: "hello", - Aliases: []string{"hi"}, - Usage: "use it to see a description", - Description: "This is how we describe hello the function", - Subcommands: []Command{ - { - Name: "english", - Aliases: []string{"en"}, - Usage: "sends a greeting in english", - Description: "greets someone in english", - Flags: []Flag{ - StringFlag{ - Name: "name", - Value: "Bob", - Usage: "Name of the person to greet", - }, - }, - Action: func(c *Context) { - println("Hello, ", c.String("name")) - }, - }, { - Name: "spanish", - Aliases: []string{"sp"}, - Usage: "sends a greeting in spanish", - Flags: []Flag{ - StringFlag{ - Name: "surname", - Value: "Jones", - Usage: "Surname of the person to greet", - }, - }, - Action: func(c *Context) { - println("Hola, ", c.String("surname")) - }, - }, { - Name: "french", - Aliases: []string{"fr"}, - Usage: "sends a greeting in french", - Flags: []Flag{ - StringFlag{ - Name: "nickname", - Value: "Stevie", - Usage: "Nickname of the person to greet", - }, - }, - Action: func(c *Context) { - println("Bonjour, ", c.String("nickname")) - }, - }, - }, - }, { - Name: "bye", - Usage: "says goodbye", - Action: func(c *Context) { - println("bye") - }, - }, - } - - app.Run(os.Args) -} From 7b94fd3aad1db29bdf260402c6edaa6e5488bfe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Haugen?= Date: Sat, 28 Nov 2015 18:26:10 +0100 Subject: [PATCH 077/381] test: avoid output from "go test" when tests pass Some tests where printing to os.Stdout as a side effect even if the output was not used/checked in the test. --- app_test.go | 2 ++ command_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app_test.go b/app_test.go index 28d8e0f..59fa75a 100644 --- a/app_test.go +++ b/app_test.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io" + "io/ioutil" "os" "strings" "testing" @@ -556,6 +557,7 @@ func TestAppNoHelpFlag(t *testing.T) { HelpFlag = BoolFlag{} app := NewApp() + app.Writer = ioutil.Discard err := app.Run([]string{"test", "-h"}) if err != flag.ErrHelp { diff --git a/command_test.go b/command_test.go index dd9fc87..ac10652 100644 --- a/command_test.go +++ b/command_test.go @@ -3,6 +3,7 @@ package cli import ( "errors" "flag" + "io/ioutil" "testing" ) @@ -20,6 +21,7 @@ func TestCommandFlagParsing(t *testing.T) { for _, c := range cases { app := NewApp() + app.Writer = ioutil.Discard set := flag.NewFlagSet("test", 0) set.Parse(c.testArgs) From 4a8406ac8979d1df509f963e538a5b843fae53c1 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Sun, 13 Dec 2015 09:31:32 +1000 Subject: [PATCH 078/381] Run check completion before error checking Running check completion before error checking allows for completion of flags when no character has been typed yet --- app.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app.go b/app.go index 9a15c0c..0805fd6 100644 --- a/app.go +++ b/app.go @@ -126,6 +126,10 @@ func (a *App) Run(arguments []string) (err error) { } context := NewContext(a, set, nil) + if checkCompletions(context) { + return nil + } + if err != nil { fmt.Fprintln(a.Writer, "Incorrect Usage.") fmt.Fprintln(a.Writer) @@ -133,10 +137,6 @@ func (a *App) Run(arguments []string) (err error) { return err } - if checkCompletions(context) { - return nil - } - if !a.HideHelp && checkHelp(context) { ShowAppHelp(context) return nil @@ -233,6 +233,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { return nerr } + if checkCompletions(context) { + return nil + } + if err != nil { fmt.Fprintln(a.Writer, "Incorrect Usage.") fmt.Fprintln(a.Writer) @@ -240,10 +244,6 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { return err } - if checkCompletions(context) { - return nil - } - if len(a.Commands) > 0 { if checkSubcommandHelp(context) { return nil From f101a0001843491f80a9cf991cf1649598db9f57 Mon Sep 17 00:00:00 2001 From: Jille Timmermans Date: Tue, 15 Dec 2015 17:29:03 +0000 Subject: [PATCH 079/381] Export cli.Flag.GetName (previously cli.Flag.getName) --- context.go | 6 +++--- flag.go | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/context.go b/context.go index f541f41..0513d34 100644 --- a/context.go +++ b/context.go @@ -163,7 +163,7 @@ func (c *Context) GlobalIsSet(name string) bool { // Returns a slice of flag names used in this context. func (c *Context) FlagNames() (names []string) { for _, flag := range c.Command.Flags { - name := strings.Split(flag.getName(), ",")[0] + name := strings.Split(flag.GetName(), ",")[0] if name == "help" { continue } @@ -175,7 +175,7 @@ func (c *Context) FlagNames() (names []string) { // Returns a slice of global flag names used by the app. func (c *Context) GlobalFlagNames() (names []string) { for _, flag := range c.App.Flags { - name := strings.Split(flag.getName(), ",")[0] + name := strings.Split(flag.GetName(), ",")[0] if name == "help" || name == "version" { continue } @@ -360,7 +360,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { visited[f.Name] = true }) for _, f := range flags { - parts := strings.Split(f.getName(), ",") + parts := strings.Split(f.GetName(), ",") if len(parts) == 1 { continue } diff --git a/flag.go b/flag.go index 9b22d7f..49f3099 100644 --- a/flag.go +++ b/flag.go @@ -35,7 +35,7 @@ type Flag interface { fmt.Stringer // Apply Flag settings to the given flag set Apply(*flag.FlagSet) - getName() string + GetName() string } func flagSet(name string, flags []Flag) *flag.FlagSet { @@ -95,7 +95,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { }) } -func (f GenericFlag) getName() string { +func (f GenericFlag) GetName() string { return f.Name } @@ -159,7 +159,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { }) } -func (f StringSliceFlag) getName() string { +func (f StringSliceFlag) GetName() string { return f.Name } @@ -231,7 +231,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { }) } -func (f IntSliceFlag) getName() string { +func (f IntSliceFlag) GetName() string { return f.Name } @@ -273,7 +273,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { }) } -func (f BoolFlag) getName() string { +func (f BoolFlag) GetName() string { return f.Name } @@ -316,7 +316,7 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { }) } -func (f BoolTFlag) getName() string { +func (f BoolTFlag) GetName() string { return f.Name } @@ -364,7 +364,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { }) } -func (f StringFlag) getName() string { +func (f StringFlag) GetName() string { return f.Name } @@ -407,7 +407,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { }) } -func (f IntFlag) getName() string { +func (f IntFlag) GetName() string { return f.Name } @@ -450,7 +450,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { }) } -func (f DurationFlag) getName() string { +func (f DurationFlag) GetName() string { return f.Name } @@ -492,7 +492,7 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { }) } -func (f Float64Flag) getName() string { +func (f Float64Flag) GetName() string { return f.Name } From b0b9bd5dac340942e2f28cff01395f9f674ec3ae Mon Sep 17 00:00:00 2001 From: Yagnesh Mistry Date: Fri, 18 Dec 2015 23:28:32 +0530 Subject: [PATCH 080/381] use path.Base in Name & HelpName as default values --- app.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 0805fd6..2f992d0 100644 --- a/app.go +++ b/app.go @@ -5,13 +5,14 @@ import ( "io" "io/ioutil" "os" + "path" "time" ) // App is the main structure of a cli application. It is recomended that // an app be created with the cli.NewApp() function type App struct { - // The name of the program. Defaults to os.Args[0] + // The name of the program. Defaults to path.Base(os.Args[0]) Name string // Full name of command for help, defaults to Name HelpName string @@ -70,8 +71,8 @@ func compileTime() time.Time { // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. func NewApp() *App { return &App{ - Name: os.Args[0], - HelpName: os.Args[0], + Name: path.Base(os.Args[0]), + HelpName: path.Base(os.Args[0]), Usage: "A new cli application", Version: "0.0.0", BashComplete: DefaultAppComplete, From f90cd5664724a17d49d488c26c3b4676540e844a Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Fri, 25 Dec 2015 21:45:58 +0100 Subject: [PATCH 081/381] Handle Before and After of Command without handling it as subCommand if there is no subCommand. --- app_test.go | 5 +++++ command.go | 26 +++++++++++++++++++++++--- command_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/app_test.go b/app_test.go index 59fa75a..9a09405 100644 --- a/app_test.go +++ b/app_test.go @@ -942,6 +942,11 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Commands = []Command{ Command{ + Subcommands: []Command{ + Command{ + Name: "sub", + }, + }, Name: "bar", Before: func(c *Context) error { return fmt.Errorf("before error") }, After: func(c *Context) error { return fmt.Errorf("after error") }, diff --git a/command.go b/command.go index 824e77b..23eca5a 100644 --- a/command.go +++ b/command.go @@ -54,8 +54,8 @@ func (c Command) FullName() string { } // Invokes the command given the context, parses ctx.Args() to generate command-specific flags -func (c Command) Run(ctx *Context) error { - if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { +func (c Command) Run(ctx *Context) (err error) { + if len(c.Subcommands) > 0 { return c.startApp(ctx) } @@ -74,7 +74,6 @@ func (c Command) Run(ctx *Context) error { set := flagSet(c.Name, c.Flags) set.SetOutput(ioutil.Discard) - var err error if !c.SkipFlagParsing { firstFlagIndex := -1 terminatorIndex := -1 @@ -133,6 +132,27 @@ func (c Command) Run(ctx *Context) error { if checkCommandHelp(context, c.Name) { return nil } + + if c.After != nil { + defer func() { + afterErr := c.After(context) + if afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if c.Before != nil { + err := c.Before(context) + if err != nil { + return err + } + } + context.Command = c c.Action(context) return nil diff --git a/command_test.go b/command_test.go index ac10652..13ebccb 100644 --- a/command_test.go +++ b/command_test.go @@ -5,6 +5,8 @@ import ( "flag" "io/ioutil" "testing" + "fmt" + "strings" ) func TestCommandFlagParsing(t *testing.T) { @@ -43,3 +45,26 @@ func TestCommandFlagParsing(t *testing.T) { expect(t, []string(context.Args()), c.testArgs) } } + +func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + Command{ + Name: "bar", + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + if err == nil { + t.Fatalf("expected to recieve error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} From f3c8e07836a9d73d1183d8990c2017a589cd1eb3 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Fri, 25 Dec 2015 22:08:22 +0100 Subject: [PATCH 082/381] Also show help if App/Command Before produces error. --- app.go | 3 +++ command.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app.go b/app.go index 2f992d0..6884920 100644 --- a/app.go +++ b/app.go @@ -164,6 +164,9 @@ func (a *App) Run(arguments []string) (err error) { if a.Before != nil { err := a.Before(context) if err != nil { + fmt.Fprintln(a.Writer, err) + fmt.Fprintln(a.Writer) + ShowAppHelp(context) return err } } diff --git a/command.go b/command.go index 23eca5a..e42178e 100644 --- a/command.go +++ b/command.go @@ -149,6 +149,9 @@ func (c Command) Run(ctx *Context) (err error) { if c.Before != nil { err := c.Before(context) if err != nil { + fmt.Fprintln(ctx.App.Writer, err) + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) return err } } From 01fdb2cca9c904b2dc6f6125ddcba83e484e6105 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Wed, 20 Jan 2016 10:56:46 +0100 Subject: [PATCH 083/381] #315 fixed typo --- command_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command_test.go b/command_test.go index 13ebccb..50bd875 100644 --- a/command_test.go +++ b/command_test.go @@ -58,7 +58,7 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { err := app.Run([]string{"foo", "bar"}) if err == nil { - t.Fatalf("expected to recieve error from Run, got none") + t.Fatalf("expected to receive error from Run, got none") } if !strings.Contains(err.Error(), "before error") { From 54b6cca78e5a5193d358055b8d6585d6b2307d09 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Jan 2016 14:51:55 -0700 Subject: [PATCH 084/381] Remove panic from help. There is a panic in printHelp that can be trivially triggered when the shell closes os.Stdout. This happens, for example, when data is piped between a cli app and something else. See https://github.com/helm/helm/issues/387 --- help.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/help.go b/help.go index a246f63..ecb67c2 100644 --- a/help.go +++ b/help.go @@ -180,7 +180,9 @@ func printHelp(out io.Writer, templ string, data interface{}) { t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) err := t.Execute(w, data) if err != nil { - panic(err) + // If the writer is closed, t.Execute will fail, and there's nothing + // we can do to recover. We could send this to os.Stderr if we need. + return } w.Flush() } From 82ddbd9a0748a666ee92c658532fd669aa242498 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Fri, 22 Jan 2016 15:08:27 +0100 Subject: [PATCH 085/381] * Improve GenericFlag.String() by suppressing empty "" for GenericFlags on nil or empty Generic.String() * Cleanup StringFlag.String() * Add os specific envHint handling for Windows (%ENV_VAR% instead of $ENV_VAR on posix systems) --- flag.go | 38 ++++++++++++++++++++++++++--------- flag_test.go | 57 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/flag.go b/flag.go index 49f3099..9cf562d 100644 --- a/flag.go +++ b/flag.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" "time" + "runtime" ) // This flag enables bash-completion for all commands and subcommands @@ -73,7 +74,18 @@ type GenericFlag struct { // help text to the user (uses the String() method of the generic flag to show // the value) func (f GenericFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage)) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) +} + +func (f GenericFlag) FormatValueHelp() string { + if f.Value == nil { + return "" + } + s := f.Value.String() + if len(s) == 0 { + return "" + } + return fmt.Sprintf("\"%s\"", s) } // Apply takes the flagset and calls Set on the generic flag with the value @@ -331,16 +343,16 @@ type StringFlag struct { // String returns the usage func (f StringFlag) String() string { - var fmtString string - fmtString = "%s %v\t%v" + return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) +} - if len(f.Value) > 0 { - fmtString = "%s \"%v\"\t%v" +func (f StringFlag) FormatValueHelp() string { + s := f.Value + if len(s) == 0 { + return "" } else { - fmtString = "%s %v\t%v" + return fmt.Sprintf("\"%s\"", s) } - - return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) } // Apply populates the flag given the flag set and environment @@ -521,7 +533,15 @@ func prefixedNames(fullName string) (prefixed string) { func withEnvHint(envVar, str string) string { envText := "" if envVar != "" { - envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $")) + prefix := "$" + suffix := "" + sep := ", $" + if runtime.GOOS == "windows" { + prefix = "%" + suffix = "%" + sep = "%, %" + } + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) } return str + envText } diff --git a/flag_test.go b/flag_test.go index 4462d3f..3caa70a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -6,6 +6,7 @@ import ( "reflect" "strings" "testing" + "runtime" ) var boolFlagTests = []struct { @@ -58,8 +59,12 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_FOO]") { - t.Errorf("%s does not end with [$APP_FOO]", output) + expectedSuffix := " [$APP_FOO]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_FOO%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } @@ -110,8 +115,12 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_QWWX]") { - t.Errorf("%q does not end with [$APP_QWWX]", output) + expectedSuffix := " [$APP_QWWX]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_QWWX%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with" + expectedSuffix, output) } } } @@ -143,8 +152,12 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_BAR]") { - t.Errorf("%s does not end with [$APP_BAR]", output) + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } @@ -176,8 +189,12 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_BAR]") { - t.Errorf("%s does not end with [$APP_BAR]", output) + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } @@ -216,8 +233,12 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_SMURF]") { - t.Errorf("%q does not end with [$APP_SMURF]", output) + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with" + expectedSuffix, output) } } } @@ -249,8 +270,12 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_BAZ]") { - t.Errorf("%s does not end with [$APP_BAZ]", output) + expectedSuffix := " [$APP_BAZ]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAZ%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } @@ -283,8 +308,12 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} output := flag.String() - if !strings.HasSuffix(output, " [$APP_ZAP]") { - t.Errorf("%s does not end with [$APP_ZAP]", output) + expectedSuffix := " [$APP_ZAP]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_ZAP%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with" + expectedSuffix, output) } } } From 09e2c89597afd31cb88f66c404cd7fc0b783238b Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 12:01:49 +0100 Subject: [PATCH 086/381] * Changed the way how to return the result. Because of strange ci failure --- flag.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flag.go b/flag.go index 9cf562d..c8fb68b 100644 --- a/flag.go +++ b/flag.go @@ -350,9 +350,8 @@ func (f StringFlag) FormatValueHelp() string { s := f.Value if len(s) == 0 { return "" - } else { - return fmt.Sprintf("\"%s\"", s) } + return fmt.Sprintf("\"%s\"", s) } // Apply populates the flag given the flag set and environment From 7319f042e4ef05856815f00ee070f567816e98ed Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 12:47:24 +0100 Subject: [PATCH 087/381] * Added ability to customize usage error messages --- app.go | 30 +++++++++++++++++------- app_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ command.go | 17 ++++++++++---- command_test.go | 27 ++++++++++++++++++++++ 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/app.go b/app.go index 6884920..be35114 100644 --- a/app.go +++ b/app.go @@ -44,6 +44,10 @@ type App struct { Action func(context *Context) // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) + // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to manipulate the original error in another. + // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error, isSubcommand bool) error // Compilation date Compiled time.Time // List of all authors who contributed @@ -132,10 +136,15 @@ func (a *App) Run(arguments []string) (err error) { } if err != nil { - fmt.Fprintln(a.Writer, "Incorrect Usage.") - fmt.Fprintln(a.Writer) - ShowAppHelp(context) - return err + if a.OnUsageError != nil { + err := a.OnUsageError(context, err, false) + return err + } else { + fmt.Fprintln(a.Writer, "Incorrect Usage.") + fmt.Fprintln(a.Writer) + ShowAppHelp(context) + return err + } } if !a.HideHelp && checkHelp(context) { @@ -242,10 +251,15 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if err != nil { - fmt.Fprintln(a.Writer, "Incorrect Usage.") - fmt.Fprintln(a.Writer) - ShowSubcommandHelp(context) - return err + if a.OnUsageError != nil { + err = a.OnUsageError(context, err, true) + return err + } else { + fmt.Fprintln(a.Writer, "Incorrect Usage.") + fmt.Fprintln(a.Writer) + ShowSubcommandHelp(context) + return err + } } if len(a.Commands) > 0 { diff --git a/app_test.go b/app_test.go index 9a09405..fa8e2b1 100644 --- a/app_test.go +++ b/app_test.go @@ -9,6 +9,7 @@ import ( "os" "strings" "testing" +"errors" ) func ExampleApp_Run() { @@ -965,3 +966,63 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } } + +func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect no subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + Command{ + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + Command{ + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong", "bar"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} diff --git a/command.go b/command.go index e42178e..e448e2a 100644 --- a/command.go +++ b/command.go @@ -30,6 +30,10 @@ type Command struct { After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) + // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to manipulate the original error in another. + // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error) error // List of child commands Subcommands []Command // List of flags to parse @@ -110,10 +114,15 @@ func (c Command) Run(ctx *Context) (err error) { } if err != nil { - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return err + if c.OnUsageError != nil { + err := c.OnUsageError(ctx, err) + return err + } else { + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err + } } nerr := normalizeFlags(c.Flags, set) diff --git a/command_test.go b/command_test.go index 50bd875..87ede75 100644 --- a/command_test.go +++ b/command_test.go @@ -68,3 +68,30 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } } + +func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + Command{ + Name: "bar", + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} From cb7a7c56eea7f9210e93e75ac149b90c357b7f97 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 14:50:25 +0100 Subject: [PATCH 088/381] * Fixed typos --- app.go | 6 +++--- command.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index be35114..d4d278f 100644 --- a/app.go +++ b/app.go @@ -44,9 +44,9 @@ type App struct { Action func(context *Context) // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) - // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to manipulate the original error in another. - // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error, isSubcommand bool) error // Compilation date Compiled time.Time diff --git a/command.go b/command.go index e448e2a..b2d797f 100644 --- a/command.go +++ b/command.go @@ -30,9 +30,9 @@ type Command struct { After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) - // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to manipulate the original error in another. - // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error) error // List of child commands Subcommands []Command From bb4e78eb6a9155dc7de6630f16551f71e0ce5ac4 Mon Sep 17 00:00:00 2001 From: Andreas Kupries Date: Tue, 26 Jan 2016 15:34:53 -0800 Subject: [PATCH 089/381] Fixed mishandling of a "-"(dash)-argument causing reordering of cli non-flag arguments. Added test demonstrating issue (PASS with fix, FAIL without). --- app_test.go | 18 ++++++++++++++++++ command.go | 3 +++ 2 files changed, 21 insertions(+) diff --git a/app_test.go b/app_test.go index 9a09405..28f96a6 100644 --- a/app_test.go +++ b/app_test.go @@ -249,6 +249,24 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { expect(t, args[2], "--notARealFlag") } +func TestApp_CommandWithDash(t *testing.T) { + var args []string + + app := NewApp() + command := Command{ + Name: "cmd", + Action: func(c *Context) { + args = c.Args() + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "-"}) + + expect(t, args[0], "my-arg") + expect(t, args[1], "-") +} + func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { var args []string diff --git a/command.go b/command.go index e42178e..aebb4bb 100644 --- a/command.go +++ b/command.go @@ -81,6 +81,9 @@ func (c Command) Run(ctx *Context) (err error) { if arg == "--" { terminatorIndex = index break + } else if arg == "-" { + // Do nothing. A dash alone is not really a flag. + continue } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { firstFlagIndex = index } From 918b268473e8e81aa8bbea2b41fa361213cf2711 Mon Sep 17 00:00:00 2001 From: Timothy Cyrus Date: Thu, 28 Jan 2016 20:42:14 -0500 Subject: [PATCH 090/381] Update README.md Replace PNG Badge With SVG and other minor fixes --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 26a1838..ae0a4ca 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ [![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) -[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) +[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) # cli.go + `cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. ## Overview + Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. **This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive! ## Installation + Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). To install `cli.go`, simply run: @@ -24,6 +27,7 @@ export PATH=$PATH:$GOPATH/bin ``` ## Getting Started + One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. ``` go @@ -123,6 +127,7 @@ GLOBAL OPTIONS ``` ### Arguments + You can lookup arguments by calling the `Args` function on `cli.Context`. ``` go @@ -134,7 +139,9 @@ app.Action = func(c *cli.Context) { ``` ### Flags + Setting and querying flags is simple. + ``` go ... app.Flags = []cli.Flag { @@ -159,6 +166,7 @@ app.Action = func(c *cli.Context) { ``` You can also set a destination variable for a flag, to which the content will be scanned. + ``` go ... var language string @@ -233,6 +241,7 @@ app.Flags = []cli.Flag { ### Subcommands Subcommands can be defined for a more git-like command line app. + ```go ... app.Commands = []cli.Command{ @@ -283,6 +292,7 @@ You can enable completion commands by setting the `EnableBashCompletion` flag on the `App` object. By default, this setting will only auto-complete to show an app's subcommands, but you can write your own completion methods for the App or its subcommands. + ```go ... var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} @@ -325,8 +335,8 @@ automatically install it there if you are distributing a package). Don't forget to source the file to make it active in the current shell. ``` - sudo cp src/bash_autocomplete /etc/bash_completion.d/ - source /etc/bash_completion.d/ +sudo cp src/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ ``` Alternatively, you can just document that users should source the generic @@ -334,6 +344,7 @@ Alternatively, you can just document that users should source the generic to the name of their program (as above). ## Contribution Guidelines + Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. From 6f6e8caf6c31a0505991cfa6983fbd9f75f564fa Mon Sep 17 00:00:00 2001 From: leonardyp Date: Thu, 4 Feb 2016 15:25:41 +0800 Subject: [PATCH 091/381] Repeat context statement because of a is a pointer performance optimization gofmt code --- app.go | 17 ++++++----------- command.go | 9 +++------ command_test.go | 4 ++-- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/app.go b/app.go index 6884920..d3db6ab 100644 --- a/app.go +++ b/app.go @@ -119,21 +119,19 @@ func (a *App) Run(arguments []string) (err error) { set.SetOutput(ioutil.Discard) err = set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) + context := NewContext(a, set, nil) if nerr != nil { fmt.Fprintln(a.Writer, nerr) - context := NewContext(a, set, nil) ShowAppHelp(context) return nerr } - context := NewContext(a, set, nil) if checkCompletions(context) { return nil } if err != nil { - fmt.Fprintln(a.Writer, "Incorrect Usage.") - fmt.Fprintln(a.Writer) + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowAppHelp(context) return err } @@ -150,8 +148,7 @@ func (a *App) Run(arguments []string) (err error) { if a.After != nil { defer func() { - afterErr := a.After(context) - if afterErr != nil { + if afterErr := a.After(context); afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) } else { @@ -162,10 +159,9 @@ func (a *App) Run(arguments []string) (err error) { } if a.Before != nil { - err := a.Before(context) + err = a.Before(context) if err != nil { - fmt.Fprintln(a.Writer, err) - fmt.Fprintln(a.Writer) + fmt.Fprintf(a.Writer, "%v\n\n", err) ShowAppHelp(context) return err } @@ -242,8 +238,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if err != nil { - fmt.Fprintln(a.Writer, "Incorrect Usage.") - fmt.Fprintln(a.Writer) + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index aebb4bb..077d8e2 100644 --- a/command.go +++ b/command.go @@ -192,7 +192,7 @@ func (c Command) startApp(ctx *Context) error { if c.HelpName == "" { app.HelpName = c.HelpName } else { - app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + app.HelpName = app.Name } if c.Description != "" { @@ -231,12 +231,9 @@ func (c Command) startApp(ctx *Context) error { app.Action = helpSubcommand.Action } - var newCmds []Command - for _, cc := range app.Commands { - cc.commandNamePath = []string{c.Name, cc.Name} - newCmds = append(newCmds, cc) + for index, cc := range app.Commands { + app.Commands[index].commandNamePath = []string{c.Name, cc.Name} } - app.Commands = newCmds return app.RunAsSubcommand(ctx) } diff --git a/command_test.go b/command_test.go index 50bd875..536392f 100644 --- a/command_test.go +++ b/command_test.go @@ -3,10 +3,10 @@ package cli import ( "errors" "flag" - "io/ioutil" - "testing" "fmt" + "io/ioutil" "strings" + "testing" ) func TestCommandFlagParsing(t *testing.T) { From c462071a52c9343b4174380c1ae0eb0306ebe531 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 12:47:24 +0100 Subject: [PATCH 092/381] * Added ability to customize usage error messages --- app.go | 26 ++++++++++++++++----- app_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ command.go | 17 ++++++++++---- command_test.go | 27 ++++++++++++++++++++++ 4 files changed, 121 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index d3db6ab..857f422 100644 --- a/app.go +++ b/app.go @@ -44,6 +44,10 @@ type App struct { Action func(context *Context) // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) + // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to manipulate the original error in another. + // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error, isSubcommand bool) error // Compilation date Compiled time.Time // List of all authors who contributed @@ -131,9 +135,14 @@ func (a *App) Run(arguments []string) (err error) { } if err != nil { - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") - ShowAppHelp(context) - return err + if a.OnUsageError != nil { + err := a.OnUsageError(context, err, false) + return err + } else { + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + ShowAppHelp(context) + return err + } } if !a.HideHelp && checkHelp(context) { @@ -238,9 +247,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if err != nil { - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") - ShowSubcommandHelp(context) - return err + if a.OnUsageError != nil { + err = a.OnUsageError(context, err, true) + return err + } else { + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + ShowSubcommandHelp(context) + return err + } } if len(a.Commands) > 0 { diff --git a/app_test.go b/app_test.go index 28f96a6..09032d1 100644 --- a/app_test.go +++ b/app_test.go @@ -9,6 +9,7 @@ import ( "os" "strings" "testing" +"errors" ) func ExampleApp_Run() { @@ -983,3 +984,63 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } } + +func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect no subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + Command{ + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + Command{ + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong", "bar"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} diff --git a/command.go b/command.go index 077d8e2..980094f 100644 --- a/command.go +++ b/command.go @@ -30,6 +30,10 @@ type Command struct { After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) + // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to manipulate the original error in another. + // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error) error // List of child commands Subcommands []Command // List of flags to parse @@ -113,10 +117,15 @@ func (c Command) Run(ctx *Context) (err error) { } if err != nil { - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return err + if c.OnUsageError != nil { + err := c.OnUsageError(ctx, err) + return err + } else { + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err + } } nerr := normalizeFlags(c.Flags, set) diff --git a/command_test.go b/command_test.go index 536392f..827da1d 100644 --- a/command_test.go +++ b/command_test.go @@ -68,3 +68,30 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } } + +func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + Command{ + Name: "bar", + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} From 66c17420121916c62683c3d60b2131eacd8aff48 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sat, 23 Jan 2016 14:50:25 +0100 Subject: [PATCH 093/381] * Fixed typos --- app.go | 6 +++--- command.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index 857f422..634c825 100644 --- a/app.go +++ b/app.go @@ -44,9 +44,9 @@ type App struct { Action func(context *Context) // Execute this function if the proper command cannot be found CommandNotFound func(context *Context, command string) - // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to manipulate the original error in another. - // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error, isSubcommand bool) error // Compilation date Compiled time.Time diff --git a/command.go b/command.go index 980094f..f987b52 100644 --- a/command.go +++ b/command.go @@ -30,9 +30,9 @@ type Command struct { After func(context *Context) error // The function to call when this command is invoked Action func(context *Context) - // Execute this function if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to manipulate the original error in another. - // If this function is not set the "Incorrect usage" is displayed and the execution is interrupted. + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error) error // List of child commands Subcommands []Command From d81f6829643e1e2d9ede25ef09d79e726ca06d4c Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Sun, 7 Feb 2016 12:54:15 +0100 Subject: [PATCH 094/381] * Added ci configuration for windows using AppVeyor --- appveyor.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..3ca7afa --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,16 @@ +version: "{build}" + +os: Windows Server 2012 R2 + +install: + - go version + - go env + +build_script: + - cd %APPVEYOR_BUILD_FOLDER% + - go vet ./... + - go test -v ./... + +test: off + +deploy: off From 0c3102278a483ded20aea55092d9bd000832221c Mon Sep 17 00:00:00 2001 From: chrisprobinson Date: Sun, 6 Dec 2015 15:43:18 -0800 Subject: [PATCH 095/381] Update to add yaml support --- inputfilesupport/flag.go | 356 +++++++++++++++++++++++ inputfilesupport/flag_test.go | 336 +++++++++++++++++++++ inputfilesupport/helpers_test.go | 18 ++ inputfilesupport/input_source_context.go | 21 ++ inputfilesupport/map_input_source.go | 132 +++++++++ inputfilesupport/yaml_command_test.go | 167 +++++++++++ inputfilesupport/yaml_file_loader.go | 99 +++++++ 7 files changed, 1129 insertions(+) create mode 100644 inputfilesupport/flag.go create mode 100644 inputfilesupport/flag_test.go create mode 100644 inputfilesupport/helpers_test.go create mode 100644 inputfilesupport/input_source_context.go create mode 100644 inputfilesupport/map_input_source.go create mode 100644 inputfilesupport/yaml_command_test.go create mode 100644 inputfilesupport/yaml_file_loader.go diff --git a/inputfilesupport/flag.go b/inputfilesupport/flag.go new file mode 100644 index 0000000..c168bd4 --- /dev/null +++ b/inputfilesupport/flag.go @@ -0,0 +1,356 @@ +package inputfilesupport + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + "github.com/codegangsta/cli" +) + +// FlagInputSourceExtension is an extension interface of cli.Flag that +// allows a value to be set on the existing parsed flags. +type FlagInputSourceExtension interface { + cli.Flag + ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) +} + +// GenericFlag is the flag type that wraps cli.GenericFlag to allow +// for other values to be specified +type GenericFlag struct { + cli.GenericFlag + set *flag.FlagSet +} + +// NewGenericFlag creates a new GenericFlag +func NewGenericFlag(flag cli.GenericFlag) *GenericFlag { + return &GenericFlag{GenericFlag: flag, set: nil} +} + +// ApplyInputSourceValue applies a generic value to the flagSet if required +func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value := isc.Generic(f.GenericFlag.Name) + if value != nil { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, value.String()) + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped GenericFlag.Apply +func (f *GenericFlag) Apply(set *flag.FlagSet) { + f.set = set + f.GenericFlag.Apply(set) +} + +// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow +// for other values to be specified +type StringSliceFlag struct { + cli.StringSliceFlag + set *flag.FlagSet +} + +// NewStringSliceFlag creates a new StringSliceFlag +func NewStringSliceFlag(flag cli.StringSliceFlag) *StringSliceFlag { + return &StringSliceFlag{StringSliceFlag: flag, set: nil} +} + +// ApplyInputSourceValue applies a StringSlice value to the flagSet if required +func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value := isc.StringSlice(f.StringSliceFlag.Name) + if value != nil { + var sliceValue cli.StringSlice = value + eachName(f.Name, func(name string) { + underlyingFlag := f.set.Lookup(f.Name) + if underlyingFlag != nil { + underlyingFlag.Value = &sliceValue + } + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped StringSliceFlag.Apply +func (f *StringSliceFlag) Apply(set *flag.FlagSet) { + f.set = set + f.StringSliceFlag.Apply(set) +} + +// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow +// for other values to be specified +type IntSliceFlag struct { + cli.IntSliceFlag + set *flag.FlagSet +} + +// NewIntSliceFlag creates a new IntSliceFlag +func NewIntSliceFlag(flag cli.IntSliceFlag) *IntSliceFlag { + return &IntSliceFlag{IntSliceFlag: flag, set: nil} +} + +// ApplyInputSourceValue applies a IntSlice value if required +func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value := isc.IntSlice(f.IntSliceFlag.Name) + if value != nil { + var sliceValue cli.IntSlice = value + eachName(f.Name, func(name string) { + underlyingFlag := f.set.Lookup(f.Name) + if underlyingFlag != nil { + underlyingFlag.Value = &sliceValue + } + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped IntSliceFlag.Apply +func (f *IntSliceFlag) Apply(set *flag.FlagSet) { + f.set = set + f.IntSliceFlag.Apply(set) +} + +// BoolFlag is the flag type that wraps cli.BoolFlag to allow +// for other values to be specified +type BoolFlag struct { + cli.BoolFlag + set *flag.FlagSet +} + +// NewBoolFlag creates a new BoolFlag +func NewBoolFlag(flag cli.BoolFlag) *BoolFlag { + return &BoolFlag{BoolFlag: flag, set: nil} +} + +// ApplyInputSourceValue applies a Bool value to the flagSet if required +func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value := isc.Bool(f.BoolFlag.Name) + if value { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, strconv.FormatBool(value)) + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped BoolFlag.Apply +func (f *BoolFlag) Apply(set *flag.FlagSet) { + f.set = set + f.BoolFlag.Apply(set) +} + +// BoolTFlag is the flag type that wraps cli.BoolTFlag to allow +// for other values to be specified +type BoolTFlag struct { + cli.BoolTFlag + set *flag.FlagSet +} + +// NewBoolTFlag creates a new BoolTFlag +func NewBoolTFlag(flag cli.BoolTFlag) *BoolTFlag { + return &BoolTFlag{BoolTFlag: flag, set: nil} +} + +// ApplyInputSourceValue applies a BoolT value to the flagSet if required +func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value := isc.BoolT(f.BoolTFlag.Name) + if !value { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, strconv.FormatBool(value)) + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped BoolTFlag.Apply +func (f *BoolTFlag) Apply(set *flag.FlagSet) { + f.set = set + + f.BoolTFlag.Apply(set) +} + +// StringFlag is the flag type that wraps cli.StringFlag to allow +// for other values to be specified +type StringFlag struct { + cli.StringFlag + set *flag.FlagSet +} + +// NewStringFlag creates a new StringFlag +func NewStringFlag(flag cli.StringFlag) *StringFlag { + return &StringFlag{StringFlag: flag, set: nil} +} + +// ApplyInputSourceValue applies a String value to the flagSet if required +func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + value := isc.String(f.StringFlag.Name) + if value != "" { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, value) + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped StringFlag.Apply +func (f *StringFlag) Apply(set *flag.FlagSet) { + f.set = set + + f.StringFlag.Apply(set) +} + +// IntFlag is the flag type that wraps cli.IntFlag to allow +// for other values to be specified +type IntFlag struct { + cli.IntFlag + set *flag.FlagSet +} + +// NewIntFlag creates a new IntFlag +func NewIntFlag(flag cli.IntFlag) *IntFlag { + return &IntFlag{IntFlag: flag, set: nil} +} + +// ApplyInputSourceValue applies a int value to the flagSet if required +func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + value := isc.Int(f.IntFlag.Name) + if value > 0 { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, strconv.FormatInt(int64(value), 10)) + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped IntFlag.Apply +func (f *IntFlag) Apply(set *flag.FlagSet) { + f.set = set + f.IntFlag.Apply(set) +} + +// DurationFlag is the flag type that wraps cli.DurationFlag to allow +// for other values to be specified +type DurationFlag struct { + cli.DurationFlag + set *flag.FlagSet +} + +// NewDurationFlag creates a new DurationFlag +func NewDurationFlag(flag cli.DurationFlag) *DurationFlag { + return &DurationFlag{DurationFlag: flag, set: nil} +} + +// ApplyInputSourceValue applies a Duration value to the flagSet if required +func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + value := isc.Duration(f.DurationFlag.Name) + if value > 0 { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, value.String()) + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped DurationFlag.Apply +func (f *DurationFlag) Apply(set *flag.FlagSet) { + f.set = set + + f.DurationFlag.Apply(set) +} + +// Float64Flag is the flag type that wraps cli.Float64Flag to allow +// for other values to be specified +type Float64Flag struct { + cli.Float64Flag + set *flag.FlagSet +} + +// NewFloat64Flag creates a new Float64Flag +func NewFloat64Flag(flag cli.Float64Flag) *Float64Flag { + return &Float64Flag{Float64Flag: flag, set: nil} +} + +// ApplyInputSourceValue applies a Float64 value to the flagSet if required +func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + value := isc.Float64(f.Float64Flag.Name) + if value > 0 { + floatStr := float64ToString(value) + eachName(f.Name, func(name string) { + f.set.Set(f.Name, floatStr) + }) + } + } + } +} + +// Apply saves the flagSet for later usage then calls +// the wrapped Float64Flag.Apply +func (f *Float64Flag) Apply(set *flag.FlagSet) { + f.set = set + + f.Float64Flag.Apply(set) +} + +func isEnvVarSet(envVars string) bool { + for _, envVar := range strings.Split(envVars, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + // TODO: Can't use this for bools as + // set means that it was true or false based on + // Bool flag type, should work for other types + if len(envVal) > 0 { + return true + } + } + } + + return false +} + +func float64ToString(f float64) string { + return fmt.Sprintf("%v", f) +} + +func eachName(longName string, fn func(string)) { + parts := strings.Split(longName, ",") + for _, name := range parts { + name = strings.Trim(name, " ") + fn(name) + } +} diff --git a/inputfilesupport/flag_test.go b/inputfilesupport/flag_test.go new file mode 100644 index 0000000..3ef7ce0 --- /dev/null +++ b/inputfilesupport/flag_test.go @@ -0,0 +1,336 @@ +package inputfilesupport + +import ( + "flag" + "fmt" + "os" + "strings" + "testing" + "time" + + "github.com/codegangsta/cli" +) + +type testApplyInputSource struct { + Flag FlagInputSourceExtension + FlagName string + FlagSetName string + Expected string + ContextValueString string + ContextValue flag.Value + EnvVarValue string + EnvVarName string + MapValue interface{} +} + +func TestGenericApplyInputSourceValue(t *testing.T) { + v := &Parser{"abc", "def"} + c := runTest(t, testApplyInputSource{ + Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + FlagName: "test", + MapValue: v, + }) + expect(t, v, c.Generic("test")) +} + +func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { + p := &Parser{"abc", "def"} + c := runTest(t, testApplyInputSource{ + Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + FlagName: "test", + MapValue: &Parser{"efg", "hig"}, + ContextValueString: p.String(), + }) + expect(t, p, c.Generic("test")) +} + +func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}), + FlagName: "test", + MapValue: &Parser{"efg", "hij"}, + EnvVarName: "TEST", + EnvVarValue: "abc,def", + }) + expect(t, &Parser{"abc", "def"}, c.Generic("test")) +} + +func TestStringSliceApplyInputSourceValue(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + FlagName: "test", + MapValue: []string{"hello", "world"}, + }) + expect(t, c.StringSlice("test"), []string{"hello", "world"}) +} + +func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + FlagName: "test", + MapValue: []string{"hello", "world"}, + ContextValueString: "ohno", + }) + expect(t, c.StringSlice("test"), []string{"ohno"}) +} + +func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: []string{"hello", "world"}, + EnvVarName: "TEST", + EnvVarValue: "oh,no", + }) + expect(t, c.StringSlice("test"), []string{"oh", "no"}) +} + +func TestIntSliceApplyInputSourceValue(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + FlagName: "test", + MapValue: []int{1, 2}, + }) + expect(t, c.IntSlice("test"), []int{1, 2}) +} + +func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + FlagName: "test", + MapValue: []int{1, 2}, + ContextValueString: "3", + }) + expect(t, c.IntSlice("test"), []int{3}) +} + +func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: []int{1, 2}, + EnvVarName: "TEST", + EnvVarValue: "3,4", + }) + expect(t, c.IntSlice("test"), []int{3, 4}) +} + +func TestBoolApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + FlagName: "test", + MapValue: true, + }) + expect(t, true, c.Bool("test")) +} + +func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + FlagName: "test", + MapValue: false, + ContextValueString: "true", + }) + expect(t, true, c.Bool("test")) +} + +func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: false, + EnvVarName: "TEST", + EnvVarValue: "true", + }) + expect(t, true, c.Bool("test")) +} + +func TestBoolTApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}), + FlagName: "test", + MapValue: false, + }) + expect(t, false, c.BoolT("test")) +} + +func TestBoolTApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}), + FlagName: "test", + MapValue: true, + ContextValueString: "false", + }) + expect(t, false, c.BoolT("test")) +} + +func TestBoolTApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: true, + EnvVarName: "TEST", + EnvVarValue: "false", + }) + expect(t, false, c.BoolT("test")) +} + +func TestStringApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + FlagName: "test", + MapValue: "hello", + }) + expect(t, "hello", c.String("test")) +} + +func TestStringApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + FlagName: "test", + MapValue: "hello", + ContextValueString: "goodbye", + }) + expect(t, "goodbye", c.String("test")) +} + +func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: "hello", + EnvVarName: "TEST", + EnvVarValue: "goodbye", + }) + expect(t, "goodbye", c.String("test")) +} + +func TestIntApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + FlagName: "test", + MapValue: 15, + }) + expect(t, 15, c.Int("test")) +} + +func TestIntApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + FlagName: "test", + MapValue: 15, + ContextValueString: "7", + }) + expect(t, 7, c.Int("test")) +} + +func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: 15, + EnvVarName: "TEST", + EnvVarValue: "12", + }) + expect(t, 12, c.Int("test")) +} + +func TestDurationApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + FlagName: "test", + MapValue: time.Duration(30 * time.Second), + }) + expect(t, time.Duration(30*time.Second), c.Duration("test")) +} + +func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + FlagName: "test", + MapValue: time.Duration(30 * time.Second), + ContextValueString: time.Duration(15 * time.Second).String(), + }) + expect(t, time.Duration(15*time.Second), c.Duration("test")) +} + +func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: time.Duration(30 * time.Second), + EnvVarName: "TEST", + EnvVarValue: time.Duration(15 * time.Second).String(), + }) + expect(t, time.Duration(15*time.Second), c.Duration("test")) +} + +func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + FlagName: "test", + MapValue: 1.3, + }) + expect(t, 1.3, c.Float64("test")) +} + +func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + FlagName: "test", + MapValue: 1.3, + ContextValueString: fmt.Sprintf("%v", 1.4), + }) + expect(t, 1.4, c.Float64("test")) +} + +func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: 1.3, + EnvVarName: "TEST", + EnvVarValue: fmt.Sprintf("%v", 1.4), + }) + expect(t, 1.4, c.Float64("test")) +} + +func runTest(t *testing.T, test testApplyInputSource) *cli.Context { + inputSource := &MapInputSource{valueMap: map[string]interface{}{test.FlagName: test.MapValue}} + set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError) + c := cli.NewContext(nil, set, nil) + if test.EnvVarName != "" && test.EnvVarValue != "" { + os.Setenv(test.EnvVarName, test.EnvVarValue) + defer os.Setenv(test.EnvVarName, "") + } + + test.Flag.Apply(set) + if test.ContextValue != nil { + flag := set.Lookup(test.FlagName) + flag.Value = test.ContextValue + } + if test.ContextValueString != "" { + set.Set(test.FlagName, test.ContextValueString) + } + test.Flag.ApplyInputSourceValue(c, inputSource) + + return c +} + +type Parser [2]string + +func (p *Parser) Set(value string) error { + parts := strings.Split(value, ",") + if len(parts) != 2 { + return fmt.Errorf("invalid format") + } + + (*p)[0] = parts[0] + (*p)[1] = parts[1] + + return nil +} + +func (p *Parser) String() string { + return fmt.Sprintf("%s,%s", p[0], p[1]) +} diff --git a/inputfilesupport/helpers_test.go b/inputfilesupport/helpers_test.go new file mode 100644 index 0000000..47b0069 --- /dev/null +++ b/inputfilesupport/helpers_test.go @@ -0,0 +1,18 @@ +package inputfilesupport + +import ( + "reflect" + "testing" +) + +func expect(t *testing.T, a interface{}, b interface{}) { + if !reflect.DeepEqual(b, a) { + t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func refute(t *testing.T, a interface{}, b interface{}) { + if a == b { + t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} diff --git a/inputfilesupport/input_source_context.go b/inputfilesupport/input_source_context.go new file mode 100644 index 0000000..e759781 --- /dev/null +++ b/inputfilesupport/input_source_context.go @@ -0,0 +1,21 @@ +package inputfilesupport + +import ( + "time" + + "github.com/codegangsta/cli" +) + +// InputSourceContext is an interface used to allow +// other input sources to be implemented as needed. +type InputSourceContext interface { + Int(name string) int + Duration(name string) time.Duration + Float64(name string) float64 + String(name string) string + StringSlice(name string) []string + IntSlice(name string) []int + Generic(name string) cli.Generic + Bool(name string) bool + BoolT(name string) bool +} diff --git a/inputfilesupport/map_input_source.go b/inputfilesupport/map_input_source.go new file mode 100644 index 0000000..1100fbf --- /dev/null +++ b/inputfilesupport/map_input_source.go @@ -0,0 +1,132 @@ +package inputfilesupport + +import ( + "time" + + "github.com/codegangsta/cli" +) + +// MapInputSource implements InputSourceContext to return +// data from the map that is loaded. +// TODO: Didn't implement a way to write out various errors +// need to figure this part out. +type MapInputSource struct { + valueMap map[string]interface{} +} + +// Int returns an int from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Int(name string) int { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(int) + if isType { + return otherValue + } + } + + return 0 +} + +// Duration returns a duration from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Duration(name string) time.Duration { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(time.Duration) + if isType { + return otherValue + } + } + + return 0 +} + +// Float64 returns an float64 from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Float64(name string) float64 { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(float64) + if isType { + return otherValue + } + } + + return 0 +} + +// String returns a string from the map if it exists otherwise returns an empty string +func (fsm *MapInputSource) String(name string) string { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(string) + if isType { + return otherValue + } + } + + return "" +} + +// StringSlice returns an []string from the map if it exists otherwise returns nil +func (fsm *MapInputSource) StringSlice(name string) []string { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.([]string) + if isType { + return otherValue + } + } + + return nil +} + +// IntSlice returns an []int from the map if it exists otherwise returns nil +func (fsm *MapInputSource) IntSlice(name string) []int { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.([]int) + if isType { + return otherValue + } + } + + return nil +} + +// Generic returns an cli.Generic from the map if it exists otherwise returns nil +func (fsm *MapInputSource) Generic(name string) cli.Generic { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(cli.Generic) + if isType { + return otherValue + } + } + + return nil +} + +// Bool returns an bool from the map otherwise returns false +func (fsm *MapInputSource) Bool(name string) bool { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(bool) + if isType { + return otherValue + } + } + + return false +} + +// BoolT returns an bool from the map otherwise returns true +func (fsm *MapInputSource) BoolT(name string) bool { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(bool) + if isType { + return otherValue + } + } + + return true +} diff --git a/inputfilesupport/yaml_command_test.go b/inputfilesupport/yaml_command_test.go new file mode 100644 index 0000000..fcd8725 --- /dev/null +++ b/inputfilesupport/yaml_command_test.go @@ -0,0 +1,167 @@ +package inputfilesupport + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + "github.com/codegangsta/cli" +) + +func TestCommandYamlFileTest(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("test") + expect(t, val, 15) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitializeYaml("load", command.Flags) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("test") + expect(t, val, 10) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitializeYaml("load", command.Flags) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + + test := []string{"test-cmd", "--load", "current.yaml", "--test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("test") + expect(t, val, 7) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitializeYaml("load", command.Flags) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("test") + expect(t, val, 15) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitializeYaml("load", command.Flags) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("test") + expect(t, val, 11) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitializeYaml("load", command.Flags) + err := command.Run(c) + + expect(t, err, nil) +} diff --git a/inputfilesupport/yaml_file_loader.go b/inputfilesupport/yaml_file_loader.go new file mode 100644 index 0000000..ac0636b --- /dev/null +++ b/inputfilesupport/yaml_file_loader.go @@ -0,0 +1,99 @@ +package inputfilesupport + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + + "github.com/codegangsta/cli" + "gopkg.in/yaml.v2" +) + +// LoadFlag is a default load flag used to get a inputFilePath for a yaml file +var LoadFlag = cli.StringFlag{ + Name: "load", + Usage: "file path to a yaml file", +} + +// InitializeYaml is used to initialize Before funcs for commands. +func InitializeYaml(filePathFlagName string, flags []cli.Flag) func(context *cli.Context) error { + return func(context *cli.Context) error { + filePath := context.String(filePathFlagName) + ymlLoader := &YamlSourceLoader{FilePath: filePath} + yamlInputSource, err := ymlLoader.Load() + if err != nil { + return fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", filePath, err.Error()) + } + + for _, f := range flags { + inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension) + if isType { + inputSourceExtendedFlag.ApplyInputSourceValue(context, yamlInputSource) + } + } + + return nil + } +} + +// YamlSourceLoader can load yaml files and return a InputSourceContext +// to be used for a parameter value +type YamlSourceLoader struct { + FilePath string +} + +// Load returns an input source if successful or an error if there is a failure +// loading the yaml file +func (ysl *YamlSourceLoader) Load() (InputSourceContext, error) { + var results map[string]interface{} + err := readCommandYaml(ysl.FilePath, &results) + if err != nil { + return nil, err + } + + return &MapInputSource{valueMap: results}, nil +} + +func readCommandYaml(filePath string, container interface{}) (err error) { + b, err := loadDataFrom(filePath) + if err != nil { + return err + } + + err = yaml.Unmarshal(b, container) + if err != nil { + return err + } + + err = nil + return +} + +func loadDataFrom(filePath string) ([]byte, error) { + u, err := url.Parse(filePath) + if err != nil { + return nil, err + } + + if u.Host != "" { // i have a host, now do i support the scheme? + switch u.Scheme { + case "http", "https": + res, err := http.Get(filePath) + if err != nil { + return nil, err + } + return ioutil.ReadAll(res.Body) + default: + return nil, fmt.Errorf("scheme of %s is unsupported", filePath) + } + } else if u.Path != "" { // i dont have a host, but I have a path. I am a local file. + if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { + return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) + } + return ioutil.ReadFile(filePath) + } else { + return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) + } +} From cde8418658360e15fc2b5e1a643aca75126aefb2 Mon Sep 17 00:00:00 2001 From: adamclerk Date: Tue, 9 Feb 2016 09:36:13 -0700 Subject: [PATCH 096/381] Fixes spelling issues and import alphabetical issues Using goreportcard.com I noticed a few spelling errors. I really love codegangsta/cli and wanted to help improve it. --- app.go | 2 +- app_test.go | 6 +++--- flag.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index 0018656..1ea3fd0 100644 --- a/app.go +++ b/app.go @@ -9,7 +9,7 @@ import ( "time" ) -// App is the main structure of a cli application. It is recomended that +// App is the main structure of a cli application. It is recommended that // an app be created with the cli.NewApp() function type App struct { // The name of the program. Defaults to path.Base(os.Args[0]) diff --git a/app_test.go b/app_test.go index 709f9aa..7feaf1f 100644 --- a/app_test.go +++ b/app_test.go @@ -2,6 +2,7 @@ package cli import ( "bytes" + "errors" "flag" "fmt" "io" @@ -9,7 +10,6 @@ import ( "os" "strings" "testing" -"errors" ) func ExampleApp_Run() { @@ -947,7 +947,7 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { err := app.Run([]string{"foo"}) if err == nil { - t.Fatalf("expected to recieve error from Run, got none") + t.Fatalf("expected to receive error from Run, got none") } if !strings.Contains(err.Error(), "before error") { @@ -975,7 +975,7 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { err := app.Run([]string{"foo", "bar"}) if err == nil { - t.Fatalf("expected to recieve error from Run, got none") + t.Fatalf("expected to receive error from Run, got none") } if !strings.Contains(err.Error(), "before error") { diff --git a/flag.go b/flag.go index c8fb68b..e951c2d 100644 --- a/flag.go +++ b/flag.go @@ -4,10 +4,10 @@ import ( "flag" "fmt" "os" + "runtime" "strconv" "strings" "time" - "runtime" ) // This flag enables bash-completion for all commands and subcommands @@ -30,7 +30,7 @@ var HelpFlag = BoolFlag{ } // Flag is a common interface related to parsing flags in cli. -// For more advanced flag parsing techniques, it is recomended that +// For more advanced flag parsing techniques, it is recommended that // this interface be implemented. type Flag interface { fmt.Stringer From a755a95d01462c81aef0fc0635fcc0d138671bfb Mon Sep 17 00:00:00 2001 From: Uwe Dauernheim Date: Wed, 17 Feb 2016 14:51:16 +0000 Subject: [PATCH 097/381] Fix semantic typo --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index bbf42ae..0153713 100644 --- a/command.go +++ b/command.go @@ -27,7 +27,7 @@ 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 + // An action to execute after any subcommands are run, but before the subcommand has finished // It is run even if Action() panics After func(context *Context) error // The function to call when this command is invoked From 802f64479d6684c1735d975f519fbb8428479fd8 Mon Sep 17 00:00:00 2001 From: Omer Murat Yildirim Date: Sun, 21 Feb 2016 15:57:11 +0200 Subject: [PATCH 098/381] Add NArg method to context structure --- README.md | 6 +++--- context.go | 5 +++++ context_test.go | 8 ++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae0a4ca..5facb70 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ app.Flags = []cli.Flag { } app.Action = func(c *cli.Context) { name := "someone" - if len(c.Args()) > 0 { + if c.NArg() > 0 { name = c.Args()[0] } if c.String("lang") == "spanish" { @@ -180,7 +180,7 @@ app.Flags = []cli.Flag { } app.Action = func(c *cli.Context) { name := "someone" - if len(c.Args()) > 0 { + if c.NArg() > 0 { name = c.Args()[0] } if language == "spanish" { @@ -308,7 +308,7 @@ app.Commands = []cli.Command{ }, BashComplete: func(c *cli.Context) { // This will complete if no args are passed - if len(c.Args()) > 0 { + if c.NArg() > 0 { return } for _, t := range tasks { diff --git a/context.go b/context.go index 0513d34..b66d278 100644 --- a/context.go +++ b/context.go @@ -197,6 +197,11 @@ func (c *Context) Args() Args { return args } +// Returns the number of the command line arguments. +func (c *Context) NArg() int { + return len(c.Args()) +} + // Returns the nth argument, or else a blank string func (a Args) Get(n int) string { if len(a) > n { diff --git a/context_test.go b/context_test.go index 7f8e928..b8ab37d 100644 --- a/context_test.go +++ b/context_test.go @@ -64,6 +64,14 @@ func TestContext_Args(t *testing.T) { expect(t, c.Bool("myflag"), true) } +func TestContext_NArg(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + c := NewContext(nil, set, nil) + set.Parse([]string{"--myflag", "bat", "baz"}) + expect(t, c.NArg(), 2) +} + func TestContext_IsSet(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") From d3b02e41b0fdf1c0496499524b4d2885992bacd0 Mon Sep 17 00:00:00 2001 From: chrisprobinson Date: Wed, 9 Dec 2015 09:15:46 -0800 Subject: [PATCH 099/381] Update to add build contraints to not compile in yaml support in the case the golang version is less than golang 1.2 --- README.md | 46 +++++- {inputfilesupport => altsrc}/flag.go | 123 +++++++++++--- {inputfilesupport => altsrc}/flag_test.go | 2 +- {inputfilesupport => altsrc}/helpers_test.go | 2 +- altsrc/input_source_context.go | 21 +++ altsrc/map_input_source.go | 152 ++++++++++++++++++ .../yaml_command_test.go | 17 +- .../yaml_file_loader.go | 55 +++---- inputfilesupport/input_source_context.go | 21 --- inputfilesupport/map_input_source.go | 132 --------------- 10 files changed, 353 insertions(+), 218 deletions(-) rename {inputfilesupport => altsrc}/flag.go (72%) rename {inputfilesupport => altsrc}/flag_test.go (99%) rename {inputfilesupport => altsrc}/helpers_test.go (94%) create mode 100644 altsrc/input_source_context.go create mode 100644 altsrc/map_input_source.go rename {inputfilesupport => altsrc}/yaml_command_test.go (85%) rename {inputfilesupport => altsrc}/yaml_file_loader.go (53%) delete mode 100644 inputfilesupport/input_source_context.go delete mode 100644 inputfilesupport/map_input_source.go diff --git a/README.md b/README.md index ae0a4ca..95ac616 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ export PATH=$PATH:$GOPATH/bin ## Getting Started -One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. +One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. ``` go package main @@ -60,7 +60,7 @@ func main() { app.Action = func(c *cli.Context) { println("boom! I say!") } - + app.Run(os.Args) } ``` @@ -238,6 +238,48 @@ app.Flags = []cli.Flag { } ``` +#### Values from alternate input sources (YAML and others) + +There is a separate package altsrc that adds support for getting flag values from other input sources like YAML. + +In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below: + +``` go + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below. + +``` go + command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) +``` + +The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context. +It will then use that file name to initialize the yaml input source for any flags that are defined on that command. +As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. + +Currently only YAML files are supported but developers can add support for other input sources by implementing the +altsrc.InputSourceContext for their given sources. + +Here is a more complete sample of a command using YAML support: + +``` go + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + // Action to run + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + err := command.Run(c) +``` + ### Subcommands Subcommands can be defined for a more git-like command line app. diff --git a/inputfilesupport/flag.go b/altsrc/flag.go similarity index 72% rename from inputfilesupport/flag.go rename to altsrc/flag.go index c168bd4..f13ffb4 100644 --- a/inputfilesupport/flag.go +++ b/altsrc/flag.go @@ -1,4 +1,4 @@ -package inputfilesupport +package altsrc import ( "flag" @@ -14,7 +14,53 @@ import ( // allows a value to be set on the existing parsed flags. type FlagInputSourceExtension interface { cli.Flag - ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) + ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error +} + +// ApplyInputSourceValues iterates over all provided flags and +// executes ApplyInputSourceValue on flags implementing the +// FlagInputSourceExtension interface to initialize these flags +// to an alternate input source. +func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error { + for _, f := range flags { + inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension) + if isType { + err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext) + if err != nil { + return err + } + } + } + + return nil +} + +// InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new +// input source based on the func provided. If there is no error it will then apply the new input source to any flags +// that are supported by the input source +func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) func(context *cli.Context) error { + return func(context *cli.Context) error { + inputSource, err := createInputSource() + if err != nil { + return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error()) + } + + return ApplyInputSourceValues(context, inputSource, flags) + } +} + +// InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new +// input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is +// no error it will then apply the new input source to any flags that are supported by the input source +func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) func(context *cli.Context) error { + return func(context *cli.Context) error { + inputSource, err := createInputSource(context) + if err != nil { + return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error()) + } + + return ApplyInputSourceValues(context, inputSource, flags) + } } // GenericFlag is the flag type that wraps cli.GenericFlag to allow @@ -30,10 +76,13 @@ func NewGenericFlag(flag cli.GenericFlag) *GenericFlag { } // ApplyInputSourceValue applies a generic value to the flagSet if required -func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { - value := isc.Generic(f.GenericFlag.Name) + value, err := isc.Generic(f.GenericFlag.Name) + if err != nil { + return err + } if value != nil { eachName(f.Name, func(name string) { f.set.Set(f.Name, value.String()) @@ -41,6 +90,8 @@ func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourc } } } + + return nil } // Apply saves the flagSet for later usage then calls @@ -63,10 +114,13 @@ func NewStringSliceFlag(flag cli.StringSliceFlag) *StringSliceFlag { } // ApplyInputSourceValue applies a StringSlice value to the flagSet if required -func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { - value := isc.StringSlice(f.StringSliceFlag.Name) + value, err := isc.StringSlice(f.StringSliceFlag.Name) + if err != nil { + return err + } if value != nil { var sliceValue cli.StringSlice = value eachName(f.Name, func(name string) { @@ -78,6 +132,7 @@ func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputS } } } + return nil } // Apply saves the flagSet for later usage then calls @@ -100,10 +155,13 @@ func NewIntSliceFlag(flag cli.IntSliceFlag) *IntSliceFlag { } // ApplyInputSourceValue applies a IntSlice value if required -func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { - value := isc.IntSlice(f.IntSliceFlag.Name) + value, err := isc.IntSlice(f.IntSliceFlag.Name) + if err != nil { + return err + } if value != nil { var sliceValue cli.IntSlice = value eachName(f.Name, func(name string) { @@ -115,6 +173,7 @@ func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour } } } + return nil } // Apply saves the flagSet for later usage then calls @@ -137,10 +196,13 @@ func NewBoolFlag(flag cli.BoolFlag) *BoolFlag { } // ApplyInputSourceValue applies a Bool value to the flagSet if required -func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { - value := isc.Bool(f.BoolFlag.Name) + value, err := isc.Bool(f.BoolFlag.Name) + if err != nil { + return err + } if value { eachName(f.Name, func(name string) { f.set.Set(f.Name, strconv.FormatBool(value)) @@ -148,6 +210,7 @@ func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo } } } + return nil } // Apply saves the flagSet for later usage then calls @@ -170,10 +233,13 @@ func NewBoolTFlag(flag cli.BoolTFlag) *BoolTFlag { } // ApplyInputSourceValue applies a BoolT value to the flagSet if required -func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { - value := isc.BoolT(f.BoolTFlag.Name) + value, err := isc.BoolT(f.BoolTFlag.Name) + if err != nil { + return err + } if !value { eachName(f.Name, func(name string) { f.set.Set(f.Name, strconv.FormatBool(value)) @@ -181,6 +247,7 @@ func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceC } } } + return nil } // Apply saves the flagSet for later usage then calls @@ -204,10 +271,13 @@ func NewStringFlag(flag cli.StringFlag) *StringFlag { } // ApplyInputSourceValue applies a String value to the flagSet if required -func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { - value := isc.String(f.StringFlag.Name) + value, err := isc.String(f.StringFlag.Name) + if err != nil { + return err + } if value != "" { eachName(f.Name, func(name string) { f.set.Set(f.Name, value) @@ -215,6 +285,7 @@ func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSource } } } + return nil } // Apply saves the flagSet for later usage then calls @@ -238,10 +309,13 @@ func NewIntFlag(flag cli.IntFlag) *IntFlag { } // ApplyInputSourceValue applies a int value to the flagSet if required -func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { - value := isc.Int(f.IntFlag.Name) + value, err := isc.Int(f.IntFlag.Name) + if err != nil { + return err + } if value > 0 { eachName(f.Name, func(name string) { f.set.Set(f.Name, strconv.FormatInt(int64(value), 10)) @@ -249,6 +323,7 @@ func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCon } } } + return nil } // Apply saves the flagSet for later usage then calls @@ -271,10 +346,13 @@ func NewDurationFlag(flag cli.DurationFlag) *DurationFlag { } // ApplyInputSourceValue applies a Duration value to the flagSet if required -func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { - value := isc.Duration(f.DurationFlag.Name) + value, err := isc.Duration(f.DurationFlag.Name) + if err != nil { + return err + } if value > 0 { eachName(f.Name, func(name string) { f.set.Set(f.Name, value.String()) @@ -282,6 +360,7 @@ func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour } } } + return nil } // Apply saves the flagSet for later usage then calls @@ -305,10 +384,13 @@ func NewFloat64Flag(flag cli.Float64Flag) *Float64Flag { } // ApplyInputSourceValue applies a Float64 value to the flagSet if required -func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) { +func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { - value := isc.Float64(f.Float64Flag.Name) + value, err := isc.Float64(f.Float64Flag.Name) + if err != nil { + return err + } if value > 0 { floatStr := float64ToString(value) eachName(f.Name, func(name string) { @@ -317,6 +399,7 @@ func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourc } } } + return nil } // Apply saves the flagSet for later usage then calls diff --git a/inputfilesupport/flag_test.go b/altsrc/flag_test.go similarity index 99% rename from inputfilesupport/flag_test.go rename to altsrc/flag_test.go index 3ef7ce0..ac4d1f5 100644 --- a/inputfilesupport/flag_test.go +++ b/altsrc/flag_test.go @@ -1,4 +1,4 @@ -package inputfilesupport +package altsrc import ( "flag" diff --git a/inputfilesupport/helpers_test.go b/altsrc/helpers_test.go similarity index 94% rename from inputfilesupport/helpers_test.go rename to altsrc/helpers_test.go index 47b0069..3b7f7e9 100644 --- a/inputfilesupport/helpers_test.go +++ b/altsrc/helpers_test.go @@ -1,4 +1,4 @@ -package inputfilesupport +package altsrc import ( "reflect" diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go new file mode 100644 index 0000000..6d695ff --- /dev/null +++ b/altsrc/input_source_context.go @@ -0,0 +1,21 @@ +package altsrc + +import ( + "time" + + "github.com/codegangsta/cli" +) + +// InputSourceContext is an interface used to allow +// other input sources to be implemented as needed. +type InputSourceContext interface { + Int(name string) (int, error) + Duration(name string) (time.Duration, error) + Float64(name string) (float64, error) + String(name string) (string, error) + StringSlice(name string) ([]string, error) + IntSlice(name string) ([]int, error) + Generic(name string) (cli.Generic, error) + Bool(name string) (bool, error) + BoolT(name string) (bool, error) +} diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go new file mode 100644 index 0000000..f1670fb --- /dev/null +++ b/altsrc/map_input_source.go @@ -0,0 +1,152 @@ +package altsrc + +import ( + "fmt" + "reflect" + "time" + + "github.com/codegangsta/cli" +) + +// MapInputSource implements InputSourceContext to return +// data from the map that is loaded. +type MapInputSource struct { + valueMap map[string]interface{} +} + +// Int returns an int from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Int(name string) (int, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(int) + if !isType { + return 0, incorrectTypeForFlagError(name, "int", otherGenericValue) + } + + return otherValue, nil + } + + return 0, nil +} + +// Duration returns a duration from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Duration(name string) (time.Duration, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(time.Duration) + if !isType { + return 0, incorrectTypeForFlagError(name, "duration", otherGenericValue) + } + return otherValue, nil + } + + return 0, nil +} + +// Float64 returns an float64 from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Float64(name string) (float64, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(float64) + if !isType { + return 0, incorrectTypeForFlagError(name, "float64", otherGenericValue) + } + return otherValue, nil + } + + return 0, nil +} + +// String returns a string from the map if it exists otherwise returns an empty string +func (fsm *MapInputSource) String(name string) (string, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(string) + if !isType { + return "", incorrectTypeForFlagError(name, "string", otherGenericValue) + } + return otherValue, nil + } + + return "", nil +} + +// StringSlice returns an []string from the map if it exists otherwise returns nil +func (fsm *MapInputSource) StringSlice(name string) ([]string, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.([]string) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue) + } + return otherValue, nil + } + + return nil, nil +} + +// IntSlice returns an []int from the map if it exists otherwise returns nil +func (fsm *MapInputSource) IntSlice(name string) ([]int, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.([]int) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue) + } + return otherValue, nil + } + + return nil, nil +} + +// Generic returns an cli.Generic from the map if it exists otherwise returns nil +func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(cli.Generic) + if !isType { + return nil, incorrectTypeForFlagError(name, "cli.Generic", otherGenericValue) + } + return otherValue, nil + } + + return nil, nil +} + +// Bool returns an bool from the map otherwise returns false +func (fsm *MapInputSource) Bool(name string) (bool, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(bool) + if !isType { + return false, incorrectTypeForFlagError(name, "bool", otherGenericValue) + } + return otherValue, nil + } + + return false, nil +} + +// BoolT returns an bool from the map otherwise returns true +func (fsm *MapInputSource) BoolT(name string) (bool, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(bool) + if !isType { + return true, incorrectTypeForFlagError(name, "bool", otherGenericValue) + } + return otherValue, nil + } + + return true, nil +} + +func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error { + valueType := reflect.TypeOf(value) + valueTypeName := "" + if valueType != nil { + valueTypeName = valueType.Name() + } + + return fmt.Errorf("Mismatched type for flag '%s'. Expected '%s' but actual is '%s'", name, expectedTypeName, valueTypeName) +} diff --git a/inputfilesupport/yaml_command_test.go b/altsrc/yaml_command_test.go similarity index 85% rename from inputfilesupport/yaml_command_test.go rename to altsrc/yaml_command_test.go index fcd8725..c7ccbf7 100644 --- a/inputfilesupport/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -1,4 +1,9 @@ -package inputfilesupport +// Disabling building of yaml support in cases where golang is 1.0 or 1.1 +// as the encoding library is not implemented or supported. + +// +build !go1,!go1.1 + +package altsrc import ( "flag" @@ -32,7 +37,7 @@ func TestCommandYamlFileTest(t *testing.T) { NewIntFlag(cli.IntFlag{Name: "test"}), cli.StringFlag{Name: "load"}}, } - command.Before = InitializeYaml("load", command.Flags) + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) expect(t, err, nil) @@ -64,7 +69,7 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), cli.StringFlag{Name: "load"}}, } - command.Before = InitializeYaml("load", command.Flags) + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -95,7 +100,7 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { NewIntFlag(cli.IntFlag{Name: "test"}), cli.StringFlag{Name: "load"}}, } - command.Before = InitializeYaml("load", command.Flags) + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -126,7 +131,7 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), cli.StringFlag{Name: "load"}}, } - command.Before = InitializeYaml("load", command.Flags) + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -160,7 +165,7 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), cli.StringFlag{Name: "load"}}, } - command.Before = InitializeYaml("load", command.Flags) + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) expect(t, err, nil) diff --git a/inputfilesupport/yaml_file_loader.go b/altsrc/yaml_file_loader.go similarity index 53% rename from inputfilesupport/yaml_file_loader.go rename to altsrc/yaml_file_loader.go index ac0636b..1251aeb 100644 --- a/inputfilesupport/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -1,4 +1,9 @@ -package inputfilesupport +// Disabling building of yaml support in cases where golang is 1.0 or 1.1 +// as the encoding library is not implemented or supported. + +// +build !go1,!go1.1 + +package altsrc import ( "fmt" @@ -8,54 +13,34 @@ import ( "os" "github.com/codegangsta/cli" + "gopkg.in/yaml.v2" ) -// LoadFlag is a default load flag used to get a inputFilePath for a yaml file -var LoadFlag = cli.StringFlag{ - Name: "load", - Usage: "file path to a yaml file", -} - -// InitializeYaml is used to initialize Before funcs for commands. -func InitializeYaml(filePathFlagName string, flags []cli.Flag) func(context *cli.Context) error { - return func(context *cli.Context) error { - filePath := context.String(filePathFlagName) - ymlLoader := &YamlSourceLoader{FilePath: filePath} - yamlInputSource, err := ymlLoader.Load() - if err != nil { - return fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", filePath, err.Error()) - } - - for _, f := range flags { - inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension) - if isType { - inputSourceExtendedFlag.ApplyInputSourceValue(context, yamlInputSource) - } - } - - return nil - } -} - -// YamlSourceLoader can load yaml files and return a InputSourceContext -// to be used for a parameter value -type YamlSourceLoader struct { +type yamlSourceContext struct { FilePath string } -// Load returns an input source if successful or an error if there is a failure -// loading the yaml file -func (ysl *YamlSourceLoader) Load() (InputSourceContext, error) { +// NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath. +func NewYamlSourceFromFile(file string) (InputSourceContext, error) { + ymlLoader := &yamlSourceLoader{FilePath: file} var results map[string]interface{} err := readCommandYaml(ysl.FilePath, &results) if err != nil { - return nil, err + return fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", filePath, err.Error()) } return &MapInputSource{valueMap: results}, nil } +// NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. +func NewYamlSourceFromFlagFunc(flagFileName string) func(InputSourceContext, error) { + return func(context cli.Context) { + filePath := context.String(flagFileName) + return NewYamlSourceFromFile(filePath) + } +} + func readCommandYaml(filePath string, container interface{}) (err error) { b, err := loadDataFrom(filePath) if err != nil { diff --git a/inputfilesupport/input_source_context.go b/inputfilesupport/input_source_context.go deleted file mode 100644 index e759781..0000000 --- a/inputfilesupport/input_source_context.go +++ /dev/null @@ -1,21 +0,0 @@ -package inputfilesupport - -import ( - "time" - - "github.com/codegangsta/cli" -) - -// InputSourceContext is an interface used to allow -// other input sources to be implemented as needed. -type InputSourceContext interface { - Int(name string) int - Duration(name string) time.Duration - Float64(name string) float64 - String(name string) string - StringSlice(name string) []string - IntSlice(name string) []int - Generic(name string) cli.Generic - Bool(name string) bool - BoolT(name string) bool -} diff --git a/inputfilesupport/map_input_source.go b/inputfilesupport/map_input_source.go deleted file mode 100644 index 1100fbf..0000000 --- a/inputfilesupport/map_input_source.go +++ /dev/null @@ -1,132 +0,0 @@ -package inputfilesupport - -import ( - "time" - - "github.com/codegangsta/cli" -) - -// MapInputSource implements InputSourceContext to return -// data from the map that is loaded. -// TODO: Didn't implement a way to write out various errors -// need to figure this part out. -type MapInputSource struct { - valueMap map[string]interface{} -} - -// Int returns an int from the map if it exists otherwise returns 0 -func (fsm *MapInputSource) Int(name string) int { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(int) - if isType { - return otherValue - } - } - - return 0 -} - -// Duration returns a duration from the map if it exists otherwise returns 0 -func (fsm *MapInputSource) Duration(name string) time.Duration { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(time.Duration) - if isType { - return otherValue - } - } - - return 0 -} - -// Float64 returns an float64 from the map if it exists otherwise returns 0 -func (fsm *MapInputSource) Float64(name string) float64 { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(float64) - if isType { - return otherValue - } - } - - return 0 -} - -// String returns a string from the map if it exists otherwise returns an empty string -func (fsm *MapInputSource) String(name string) string { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(string) - if isType { - return otherValue - } - } - - return "" -} - -// StringSlice returns an []string from the map if it exists otherwise returns nil -func (fsm *MapInputSource) StringSlice(name string) []string { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.([]string) - if isType { - return otherValue - } - } - - return nil -} - -// IntSlice returns an []int from the map if it exists otherwise returns nil -func (fsm *MapInputSource) IntSlice(name string) []int { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.([]int) - if isType { - return otherValue - } - } - - return nil -} - -// Generic returns an cli.Generic from the map if it exists otherwise returns nil -func (fsm *MapInputSource) Generic(name string) cli.Generic { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(cli.Generic) - if isType { - return otherValue - } - } - - return nil -} - -// Bool returns an bool from the map otherwise returns false -func (fsm *MapInputSource) Bool(name string) bool { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(bool) - if isType { - return otherValue - } - } - - return false -} - -// BoolT returns an bool from the map otherwise returns true -func (fsm *MapInputSource) BoolT(name string) bool { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(bool) - if isType { - return otherValue - } - } - - return true -} From 88ea7cbec8b0c5e10ae30a6a597a6621b6613130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=8C=AF=E5=A8=81?= Date: Wed, 2 Mar 2016 10:45:13 +0800 Subject: [PATCH 100/381] Add App extras info --- app.go | 2 ++ command.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 1ea3fd0..503f838 100644 --- a/app.go +++ b/app.go @@ -62,6 +62,8 @@ type App struct { Email string // Writer writer to write output to Writer io.Writer + // Other custom info + Extras map[string]interface{} } // Tries to find out when this binary was compiled. diff --git a/command.go b/command.go index 0153713..0ccffa2 100644 --- a/command.go +++ b/command.go @@ -197,7 +197,7 @@ func (c Command) HasName(name string) bool { func (c Command) startApp(ctx *Context) error { app := NewApp() - + app.Extras = ctx.App.Extras // set the name and usage app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) if c.HelpName == "" { From aca5b047ed14d17224157c3434ea93bf6cdaadee Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 6 Mar 2016 20:12:29 -0800 Subject: [PATCH 101/381] Drop support for Go 1.0.3 Incompatible with the new alternate input sources and I don't see a compelling reason to continue its support. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 87ba52f..c2b5c8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: go sudo: false go: -- 1.0.3 - 1.1.2 - 1.2.2 - 1.3.3 From a4557da1b397768da4145aee42296e52632ca385 Mon Sep 17 00:00:00 2001 From: Tomasz Korzeniowski Date: Fri, 11 Mar 2016 09:37:02 +0100 Subject: [PATCH 102/381] codebeat badge Is it fine to add codebeat badge to README? codebeat is automated code review tool for Swift,Ruby,Go & Python that helps get instant feedback on code quality. "Quick wins" suggested by codebeat could be a nice candidate for a pull request and help other developers become contributors. FYI. To be fully open and honest. I'm co-founder of codebeat. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 364c964..bb769fe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) [![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) # cli.go From a0801792cc2dc77e913bd8405d5ef0e2f5ba32e8 Mon Sep 17 00:00:00 2001 From: Soulou Date: Mon, 15 Dec 2014 23:35:49 +0100 Subject: [PATCH 103/381] Allow to sort commands by category --- app.go | 11 +++++++++++ command.go | 6 +++++- help.go | 12 +++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 1ea3fd0..fdb2ba5 100644 --- a/app.go +++ b/app.go @@ -34,6 +34,10 @@ type App struct { HideHelp bool // Boolean to hide built-in version flag HideVersion bool + // Display commands by category + CategorizedHelp bool + // Populate when displaying AppHelp + Categories map[string]Commands // 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 @@ -95,6 +99,13 @@ func (a *App) Run(arguments []string) (err error) { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } + if a.CategorizedHelp { + a.Categories = make(map[string]Commands) + for _, command := range a.Commands { + a.Categories[command.Category] = append(a.Categories[command.Category], command) + } + } + newCmds := []Command{} for _, c := range a.Commands { if c.HelpName == "" { diff --git a/command.go b/command.go index 0153713..024ddbc 100644 --- a/command.go +++ b/command.go @@ -22,6 +22,8 @@ type Command struct { Description string // A short description of the arguments of this command ArgsUsage string + // The category the command is part of + Category string // The function to call when checking for bash command completions BashComplete func(context *Context) // An action to execute before any sub-subcommands are run, but after the context is ready @@ -37,7 +39,7 @@ type Command struct { // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. OnUsageError func(context *Context, err error) error // List of child commands - Subcommands []Command + Subcommands Commands // List of flags to parse Flags []Flag // Treat all flags as normal arguments if true @@ -59,6 +61,8 @@ func (c Command) FullName() string { return strings.Join(c.commandNamePath, " ") } +type Commands []Command + // Invokes the command given the context, parses ctx.Args() to generate command-specific flags func (c Command) Run(ctx *Context) (err error) { if len(c.Subcommands) > 0 { diff --git a/help.go b/help.go index 15916f8..8285ff0 100644 --- a/help.go +++ b/help.go @@ -23,9 +23,12 @@ VERSION: AUTHOR(S): {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} -COMMANDS: - {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} - {{end}}{{end}}{{if .Flags}} +COMMANDS:{{if .CategorizedHelp}}{{range $category, $commands := .Categories}}{{if $category}} + {{$category}}{{ ":" }}{{end}}{{range $commands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} +{{end}}{{else}}{{range .Commands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} +{{end}}{{end}}{{if .Flags}} GLOBAL OPTIONS: {{range .Flags}}{{.}} {{end}}{{end}}{{if .Copyright }} @@ -43,6 +46,9 @@ var CommandHelpTemplate = `NAME: USAGE: {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}} +{{if .Category}}CATEGORY: + {{.Category}} +{{end}} DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} From 994a7028e275fefa0b3bbd88c4c48c0942c9be3a Mon Sep 17 00:00:00 2001 From: Soulou Date: Fri, 21 Aug 2015 10:58:14 +0200 Subject: [PATCH 104/381] Categories as slice, not a map anymore, order is always preserved --- app.go | 8 +++++--- category.go | 30 ++++++++++++++++++++++++++++++ help.go | 4 ++-- 3 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 category.go diff --git a/app.go b/app.go index fdb2ba5..5ebf201 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path" + "sort" "time" ) @@ -37,7 +38,7 @@ type App struct { // Display commands by category CategorizedHelp bool // Populate when displaying AppHelp - Categories map[string]Commands + Categories CommandCategories // 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 @@ -100,11 +101,12 @@ func (a *App) Run(arguments []string) (err error) { } if a.CategorizedHelp { - a.Categories = make(map[string]Commands) + a.Categories = CommandCategories{} for _, command := range a.Commands { - a.Categories[command.Category] = append(a.Categories[command.Category], command) + a.Categories = a.Categories.AddCommand(command.Category, command) } } + sort.Sort(a.Categories) newCmds := []Command{} for _, c := range a.Commands { diff --git a/category.go b/category.go new file mode 100644 index 0000000..7dbf218 --- /dev/null +++ b/category.go @@ -0,0 +1,30 @@ +package cli + +type CommandCategories []*CommandCategory + +type CommandCategory struct { + Name string + Commands Commands +} + +func (c CommandCategories) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c CommandCategories) Len() int { + return len(c) +} + +func (c CommandCategories) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { + for _, commandCategory := range c { + if commandCategory.Name == category { + commandCategory.Commands = append(commandCategory.Commands, command) + return c + } + } + return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) +} diff --git a/help.go b/help.go index 8285ff0..7838c89 100644 --- a/help.go +++ b/help.go @@ -23,8 +23,8 @@ VERSION: AUTHOR(S): {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} -COMMANDS:{{if .CategorizedHelp}}{{range $category, $commands := .Categories}}{{if $category}} - {{$category}}{{ ":" }}{{end}}{{range $commands}} +COMMANDS:{{if .CategorizedHelp}}{{range .Categories}}{{if .Name}} + {{.Name}}{{ ":" }}{{end}}{{range .Commands}} {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} {{end}}{{else}}{{range .Commands}} {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} From d0997e8f99b5a3006872e0294ef7434a0e01c93a Mon Sep 17 00:00:00 2001 From: Soulou Date: Fri, 21 Aug 2015 13:25:37 +0200 Subject: [PATCH 105/381] Set Categories as a read-only method and fix tests --- app.go | 15 ++++++++++----- app_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ help.go | 3 +-- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/app.go b/app.go index 5ebf201..4bc333b 100644 --- a/app.go +++ b/app.go @@ -37,8 +37,8 @@ type App struct { HideVersion bool // Display commands by category CategorizedHelp bool - // Populate when displaying AppHelp - Categories CommandCategories + // Populate on app startup, only gettable throught method Categories() + categories CommandCategories // 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 @@ -101,12 +101,12 @@ func (a *App) Run(arguments []string) (err error) { } if a.CategorizedHelp { - a.Categories = CommandCategories{} + a.categories = CommandCategories{} for _, command := range a.Commands { - a.Categories = a.Categories.AddCommand(command.Category, command) + a.categories = a.categories.AddCommand(command.Category, command) } } - sort.Sort(a.Categories) + sort.Sort(a.categories) newCmds := []Command{} for _, c := range a.Commands { @@ -329,6 +329,11 @@ func (a *App) Command(name string) *Command { return nil } +// Returnes the array containing all the categories with the commands they contain +func (a *App) Categories() CommandCategories { + return a.categories +} + func (a *App) hasFlag(flag Flag) bool { for _, f := range a.Flags { if flag == f { diff --git a/app_test.go b/app_test.go index 7feaf1f..95bdd41 100644 --- a/app_test.go +++ b/app_test.go @@ -8,6 +8,7 @@ import ( "io" "io/ioutil" "os" + "reflect" "strings" "testing" ) @@ -939,6 +940,49 @@ func TestApp_Run_Version(t *testing.T) { } } +func TestApp_Run_Categories(t *testing.T) { + app := NewApp() + app.Name = "categories" + app.CategorizedHelp = true + app.Commands = []Command{ + Command{ + Name: "command1", + Category: "1", + }, + Command{ + Name: "command2", + Category: "1", + }, + Command{ + Name: "command3", + Category: "2", + }, + } + buf := new(bytes.Buffer) + app.Writer = buf + + app.Run([]string{"categories"}) + + expect := CommandCategories{ + &CommandCategory{ + Name: "1", + Commands: []Command{ + app.Commands[0], + app.Commands[1], + }, + }, + &CommandCategory{ + Name: "2", + Commands: []Command{ + app.Commands[2], + }, + }, + } + if !reflect.DeepEqual(app.Categories(), expect) { + t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) + } +} + func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Action = func(c *Context) {} diff --git a/help.go b/help.go index 7838c89..6708192 100644 --- a/help.go +++ b/help.go @@ -48,8 +48,7 @@ USAGE: {{if .Category}}CATEGORY: {{.Category}} -{{end}} -DESCRIPTION: +{{end}}DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} OPTIONS: From 042842b81998322ad2b94fe6a15105825036898e Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 20 Mar 2016 12:17:13 -0700 Subject: [PATCH 106/381] Remove CategorizedHelp from App and allow subcommands to have categories Just place all subcommands in categories, the default category will be "" which will properly format the output (and group commands that have no category). Also allow subcommands to have categories. Lastly, augment the test to check the output. --- app.go | 16 ++++++---------- app_test.go | 8 +++++++- command.go | 8 ++++++++ help.go | 20 ++++++++++---------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/app.go b/app.go index 4bc333b..dcab379 100644 --- a/app.go +++ b/app.go @@ -35,8 +35,6 @@ type App struct { HideHelp bool // Boolean to hide built-in version flag HideVersion bool - // Display commands by category - CategorizedHelp bool // Populate on app startup, only gettable throught method Categories() categories CommandCategories // An action to execute when the bash-completion flag is set @@ -100,14 +98,6 @@ func (a *App) Run(arguments []string) (err error) { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } - if a.CategorizedHelp { - a.categories = CommandCategories{} - for _, command := range a.Commands { - a.categories = a.categories.AddCommand(command.Category, command) - } - } - sort.Sort(a.categories) - newCmds := []Command{} for _, c := range a.Commands { if c.HelpName == "" { @@ -117,6 +107,12 @@ func (a *App) Run(arguments []string) (err error) { } a.Commands = newCmds + a.categories = CommandCategories{} + for _, command := range a.Commands { + a.categories = a.categories.AddCommand(command.Category, command) + } + sort.Sort(a.categories) + // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) diff --git a/app_test.go b/app_test.go index 95bdd41..ebf26c7 100644 --- a/app_test.go +++ b/app_test.go @@ -943,7 +943,6 @@ func TestApp_Run_Version(t *testing.T) { func TestApp_Run_Categories(t *testing.T) { app := NewApp() app.Name = "categories" - app.CategorizedHelp = true app.Commands = []Command{ Command{ Name: "command1", @@ -981,6 +980,13 @@ func TestApp_Run_Categories(t *testing.T) { if !reflect.DeepEqual(app.Categories(), expect) { t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "1:\n command1") { + t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) + } } func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { diff --git a/command.go b/command.go index 024ddbc..1a05b54 100644 --- a/command.go +++ b/command.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "io/ioutil" + "sort" "strings" ) @@ -231,6 +232,13 @@ func (c Command) startApp(ctx *Context) error { app.Email = ctx.App.Email app.Writer = ctx.App.Writer + app.categories = CommandCategories{} + for _, command := range c.Subcommands { + app.categories = app.categories.AddCommand(command.Category, command) + } + + sort.Sort(app.categories) + // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion if c.BashComplete != nil { diff --git a/help.go b/help.go index 6708192..6efb011 100644 --- a/help.go +++ b/help.go @@ -23,11 +23,9 @@ VERSION: AUTHOR(S): {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} -COMMANDS:{{if .CategorizedHelp}}{{range .Categories}}{{if .Name}} +COMMANDS:{{range .Categories}}{{if .Name}} {{.Name}}{{ ":" }}{{end}}{{range .Commands}} {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} -{{end}}{{else}}{{range .Commands}} - {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} {{end}}{{end}}{{if .Flags}} GLOBAL OPTIONS: {{range .Flags}}{{.}} @@ -44,11 +42,12 @@ var CommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}} + {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} -{{if .Category}}CATEGORY: - {{.Category}} -{{end}}DESCRIPTION: +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: {{.Description}}{{end}}{{if .Flags}} OPTIONS: @@ -65,9 +64,10 @@ var SubcommandHelpTemplate = `NAME: USAGE: {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} -COMMANDS: - {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} - {{end}}{{if .Flags}} +COMMANDS:{{range .Categories}}{{if .Name}} + {{.Name}}{{ ":" }}{{end}}{{range .Commands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} +{{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} {{end}}{{end}} From a7be4a3f196ec5ea6f6b5cf0e09bb1ee71776cee Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 20 Mar 2016 12:18:28 -0700 Subject: [PATCH 107/381] Describe category behavior in README --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index 364c964..89de990 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,45 @@ app.Commands = []cli.Command{ ... ``` +### Subcommands categories + +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +... + app.Commands = []cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + } +... +``` + +Will include: + +``` +... +COMMANDS: + noop + + Template actions: + add + remove +... +``` + ### Bash Completion You can enable completion commands by setting the `EnableBashCompletion` From ad7ed77a7e4434b280c11c9c83d4c3982363a571 Mon Sep 17 00:00:00 2001 From: Yasuhiro KANDA Date: Sun, 20 Mar 2016 03:06:53 +0900 Subject: [PATCH 108/381] Fix yaml file loader * fix typo (yamlSourceLoader -> yamlSourceContext) * fix function return type (NewYamlSourceFromFlagFunc) Also fixed build tags to properly build on >=go1.2 Signed-off-by: Jesse Szwedko --- altsrc/yaml_command_test.go | 2 +- altsrc/yaml_file_loader.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index c7ccbf7..275bc64 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -1,7 +1,7 @@ // Disabling building of yaml support in cases where golang is 1.0 or 1.1 // as the encoding library is not implemented or supported. -// +build !go1,!go1.1 +// +build go1.2 package altsrc diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 1251aeb..4fb0965 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -1,7 +1,7 @@ // Disabling building of yaml support in cases where golang is 1.0 or 1.1 // as the encoding library is not implemented or supported. -// +build !go1,!go1.1 +// +build go1.2 package altsrc @@ -23,19 +23,19 @@ type yamlSourceContext struct { // NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath. func NewYamlSourceFromFile(file string) (InputSourceContext, error) { - ymlLoader := &yamlSourceLoader{FilePath: file} + ysc := &yamlSourceContext{FilePath: file} var results map[string]interface{} - err := readCommandYaml(ysl.FilePath, &results) + err := readCommandYaml(ysc.FilePath, &results) if err != nil { - return fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", filePath, err.Error()) + return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error()) } return &MapInputSource{valueMap: results}, nil } // NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. -func NewYamlSourceFromFlagFunc(flagFileName string) func(InputSourceContext, error) { - return func(context cli.Context) { +func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { + return func(context *cli.Context) (InputSourceContext, error) { filePath := context.String(flagFileName) return NewYamlSourceFromFile(filePath) } From d21170f0e30d4adecd647f57a50cea8644da1590 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 11 Mar 2016 16:25:30 +0000 Subject: [PATCH 109/381] Never show version if HideVersion=true --- app.go | 2 +- help.go | 4 ++-- help_test.go | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 1ea3fd0..6632ec0 100644 --- a/app.go +++ b/app.go @@ -32,7 +32,7 @@ type App struct { EnableBashCompletion bool // Boolean to hide built-in help command HideHelp bool - // Boolean to hide built-in version flag + // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool // An action to execute when the bash-completion flag is set BashComplete func(context *Context) diff --git a/help.go b/help.go index 15916f8..d3a12a2 100644 --- a/help.go +++ b/help.go @@ -16,10 +16,10 @@ var AppHelpTemplate = `NAME: USAGE: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} - {{if .Version}} + {{if .Version}}{{if not .HideVersion}} VERSION: {{.Version}} - {{end}}{{if len .Authors}} + {{end}}{{end}}{{if len .Authors}} AUTHOR(S): {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} diff --git a/help_test.go b/help_test.go index 350e263..0821f48 100644 --- a/help_test.go +++ b/help_test.go @@ -35,6 +35,22 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) { } } +func Test_ShowAppHelp_HideVersion(t *testing.T) { + output := new(bytes.Buffer) + app := NewApp() + app.Writer = output + + app.HideVersion = true + + c := NewContext(app, nil, nil) + + ShowAppHelp(c) + + if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { + t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") + } +} + func Test_Help_Custom_Flags(t *testing.T) { oldFlag := HelpFlag defer func() { From 3fb51f15e8ce6ea8428a8dacb30d120fad8fa186 Mon Sep 17 00:00:00 2001 From: Felamande Date: Sun, 3 Apr 2016 17:50:08 +0800 Subject: [PATCH 110/381] use filepath.Base instead of path.Base in Name & HelpName as default values. --- app.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index b4dd201..bd20a2d 100644 --- a/app.go +++ b/app.go @@ -5,7 +5,7 @@ import ( "io" "io/ioutil" "os" - "path" + "path/filepath" "sort" "time" ) @@ -80,8 +80,8 @@ func compileTime() time.Time { // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. func NewApp() *App { return &App{ - Name: path.Base(os.Args[0]), - HelpName: path.Base(os.Args[0]), + Name: filepath.Base(os.Args[0]), + HelpName: filepath.Base(os.Args[0]), Usage: "A new cli application", UsageText: "", Version: "0.0.0", From 13b7eedc1340210c208c0853dbad66e6cd02162f Mon Sep 17 00:00:00 2001 From: Kevin Cantwell Date: Thu, 21 Apr 2016 23:02:06 -0400 Subject: [PATCH 111/381] Parses usage placeholders via back quotes. Resolves #333 --- flag.go | 52 +++++++++++++++++++++++++++++++++++++++++----------- flag_test.go | 28 +++++++++++++++------------- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/flag.go b/flag.go index e951c2d..42f6a61 100644 --- a/flag.go +++ b/flag.go @@ -74,7 +74,8 @@ 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 %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name, placeholder), f.FormatValueHelp(), usage)) } func (f GenericFlag) FormatValueHelp() string { @@ -143,7 +144,8 @@ 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)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name, placeholder), pref+firstName+" option "+pref+firstName+" option", usage)) } // Apply populates the flag given the flag set and environment @@ -212,7 +214,8 @@ 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)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name, placeholder), pref+firstName+" option "+pref+firstName+" option", usage)) } // Apply populates the flag given the flag set and environment @@ -257,7 +260,8 @@ type BoolFlag struct { // String returns a readable representation of this value (for usage defaults) func (f BoolFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name, placeholder), usage)) } // Apply populates the flag given the flag set and environment @@ -300,7 +304,8 @@ type BoolTFlag struct { // String returns a readable representation of this value (for usage defaults) func (f BoolTFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name, placeholder), usage)) } // Apply populates the flag given the flag set and environment @@ -343,7 +348,8 @@ type StringFlag struct { // String returns the usage func (f StringFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name, placeholder), f.FormatValueHelp(), usage)) } func (f StringFlag) FormatValueHelp() string { @@ -391,7 +397,8 @@ type IntFlag struct { // String returns the usage func (f IntFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage)) } // Apply populates the flag given the flag set and environment @@ -434,7 +441,8 @@ type DurationFlag struct { // String returns a readable representation of this value (for usage defaults) func (f DurationFlag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage)) } // Apply populates the flag given the flag set and environment @@ -477,7 +485,8 @@ type Float64Flag struct { // String returns the usage func (f Float64Flag) String() string { - return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) + placeholder, usage := unquoteUsage(f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage)) } // Apply populates the flag given the flag set and environment @@ -517,16 +526,37 @@ func prefixFor(name string) (prefix string) { return } -func prefixedNames(fullName string) (prefixed string) { +// Returns the placeholder, if any, and the unquoted usage string. +func unquoteUsage(usage string) (string, string) { + for i := 0; i < len(usage); i++ { + if usage[i] == '`' { + for j := i + 1; j < len(usage); j++ { + if usage[j] == '`' { + name := usage[i+1 : j] + usage = usage[:i] + name + usage[j+1:] + return name, usage + } + } + break + } + } + return "", usage +} + +func prefixedNames(fullName, placeholder string) string { + var prefixed string parts := strings.Split(fullName, ",") for i, name := range parts { name = strings.Trim(name, " ") prefixed += prefixFor(name) + name + if placeholder != "" { + prefixed += " " + placeholder + } if i < len(parts)-1 { prefixed += ", " } } - return + return prefixed } func withEnvHint(envVar, str string) string { diff --git a/flag_test.go b/flag_test.go index 3caa70a..6bebf1b 100644 --- a/flag_test.go +++ b/flag_test.go @@ -4,9 +4,9 @@ import ( "fmt" "os" "reflect" + "runtime" "strings" "testing" - "runtime" ) var boolFlagTests = []struct { @@ -31,19 +31,21 @@ func TestBoolFlagHelpOutput(t *testing.T) { var stringFlagTests = []struct { name string + usage string value string expected string }{ - {"help", "", "--help \t"}, - {"h", "", "-h \t"}, - {"h", "", "-h \t"}, - {"test", "Something", "--test \"Something\"\t"}, + {"help", "", "", "--help \t"}, + {"h", "", "", "-h \t"}, + {"h", "", "", "-h \t"}, + {"test", "", "Something", "--test \"Something\"\t"}, + {"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE \tLoad configuration from FILE"}, } func TestStringFlagHelpOutput(t *testing.T) { for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Value: test.value} + flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} output := flag.String() if output != test.expected { @@ -64,7 +66,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_FOO%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -120,7 +122,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_QWWX%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%q does not end with" + expectedSuffix, output) + t.Errorf("%q does not end with"+expectedSuffix, output) } } } @@ -157,7 +159,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAR%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -194,7 +196,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAR%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -238,7 +240,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_SMURF%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%q does not end with" + expectedSuffix, output) + t.Errorf("%q does not end with"+expectedSuffix, output) } } } @@ -275,7 +277,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAZ%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -313,7 +315,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_ZAP%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } From c1c8825a460d1440306cb6875ec9d3a9be36a93e Mon Sep 17 00:00:00 2001 From: Kevin Cantwell Date: Mon, 25 Apr 2016 10:50:29 -0400 Subject: [PATCH 112/381] updates README with flag usage placeholder instructions --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index d9371cf..524723e 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,27 @@ app.Action = func(c *cli.Context) { See full list of flags at http://godoc.org/github.com/codegangsta/cli +#### Placeholder Values + +Sometimes it's useful to specify a flag's value within the usage string itself. Such placeholders are +indicated with back quotes. + +For example this: +```go +cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +``` + +Note that only the first placeholder is used. Subsequent back-quoted words will be left as-is. + #### Alternate Names You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. From ed0056d1c15abcf9512b081604277b0ddb37bbb9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2016 15:48:21 -0400 Subject: [PATCH 113/381] Add `CHANGELOG.md` with backfilled releases --- CHANGELOG.md | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..28be743 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,217 @@ +# Change Log + +**ATTN**: This project uses [semantic versioning](http://semver.org/). + +## [Unreleased] + +## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) +### Added +- Codebeat badge +- Support for categorization via `CategorizedHelp` and `Categories` on app. + +### Changed +- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`. + +### Fixed +- Ensure version is not shown in help text when `HideVersion` set. + +## [1.13.0] - 2016-03-06 (backfilled 2016-04-25) +### Added +- YAML file input support. +- `NArg` method on context. + +## [1.12.0] - 2016-02-17 (backfilled 2016-04-25) +### Added +- Custom usage error handling. +- Custom text support in `USAGE` section of help output. +- Improved help messages for empty strings. +- AppVeyor CI configuration. + +### Changed +- Removed `panic` from default help printer func. +- De-duping and optimizations. + +### Fixed +- Correctly handle `Before`/`After` at command level when no subcommands. +- Case of literal `-` argument causing flag reordering. +- Environment variable hints on Windows. +- Docs updates. + +## [1.11.1] - 2015-12-21 (backfilled 2016-04-25) +### Changed +- Use `path.Base` in `Name` and `HelpName` +- Export `GetName` on flag types. + +### Fixed +- Flag parsing when skipping is enabled. +- Test output cleanup. +- Move completion check to account for empty input case. + +## [1.11.0] - 2015-11-15 (backfilled 2016-04-25) +### Added +- Destination scan support for flags. +- Testing against `tip` in Travis CI config. + +### Changed +- Go version in Travis CI config. + +### Fixed +- Removed redundant tests. +- Use correct example naming in tests. + +## [1.10.2] - 2015-10-29 (backfilled 2016-04-25) +### Fixed +- Remove unused var in bash completion. + +## [1.10.1] - 2015-10-21 (backfilled 2016-04-25) +### Added +- Coverage and reference logos in README. + +### Fixed +- Use specified values in help and version parsing. +- Only display app version and help message once. + +## [1.10.0] - 2015-10-06 (backfilled 2016-04-25) +### Added +- More tests for existing functionality. +- `ArgsUsage` at app and command level for help text flexibility. + +### Fixed +- Honor `HideHelp` and `HideVersion` in `App.Run`. +- Remove juvenile word from README. + +## [1.9.0] - 2015-09-08 (backfilled 2016-04-25) +### Added +- `FullName` on command with accompanying help output update. +- Set default `$PROG` in bash completion. + +### Changed +- Docs formatting. + +### Fixed +- Removed self-referential imports in tests. + +## [1.8.0] - 2015-06-30 (backfilled 2016-04-25) +### Added +- Support for `Copyright` at app level. +- `Parent` func at context level to walk up context lineage. + +### Fixed +- Global flag processing at top level. + +## [1.7.1] - 2015-06-11 (backfilled 2016-04-25) +### Added +- Aggregate errors from `Before`/`After` funcs. +- Doc comments on flag structs. +- Include non-global flags when checking version and help. +- Travis CI config updates. + +### Fixed +- Ensure slice type flags have non-nil values. +- Collect global flags from the full command hierarchy. +- Docs prose. + +## [1.7.0] - 2015-05-03 (backfilled 2016-04-25) +### Changed +- `HelpPrinter` signature includes output writer. + +### Fixed +- Specify go 1.1+ in docs. +- Set `Writer` when running command as app. + +## [1.6.0] - 2015-03-23 (backfilled 2016-04-25) +### Added +- Multiple author support. +- `NumFlags` at context level. +- `Aliases` at command level. + +### Deprecated +- `ShortName` at command level. + +### Fixed +- Subcommand help output. +- Backward compatible support for deprecated `Author` and `Email` fields. +- Docs regarding `Names`/`Aliases`. + +## [1.5.0] - 2015-02-20 (backfilled 2016-04-25) +### Added +- `After` hook func support at app and command level. + +### Fixed +- Use parsed context when running command as subcommand. +- Docs prose. + +## [1.4.1] - 2015-01-09 (backfilled 2016-04-25) +### Added +- Support for hiding `-h / --help` flags, but not `help` subcommand. +- Stop flag parsing after `--`. + +### Fixed +- Help text for generic flags to specify single value. +- Use double quotes in output for defaults. +- Use `ParseInt` instead of `ParseUint` for int environment var values. +- Use `0` as base when parsing int environment var values. + +## [1.4.0] - 2014-12-12 (backfilled 2016-04-25) +### Added +- Support for environment variable lookup "cascade". +- Support for `Stdout` on app for output redirection. + +### Fixed +- Print command help instead of app help in `ShowCommandHelp`. + +## [1.3.1] - 2014-11-13 (backfilled 2016-04-25) +### Added +- Docs and example code updates. + +### Changed +- Default `-v / --version` flag made optional. + +## [1.3.0] - 2014-08-10 (backfilled 2016-04-25) +### Added +- `FlagNames` at context level. +- Exposed `VersionPrinter` var for more control over version output. +- Zsh completion hook. +- `AUTHOR` section in default app help template. +- Contribution guidelines. +- `DurationFlag` type. + +## [1.2.0] - 2014-08-02 +### Added +- Support for environment variable defaults on flags plus tests. + +## [1.1.0] - 2014-07-15 +### Added +- Bash completion. +- Optional hiding of built-in help command. +- Optional skipping of flag parsing at command level. +- `Author`, `Email`, and `Compiled` metadata on app. +- `Before` hook func support at app and command level. +- `CommandNotFound` func support at app level. +- Command reference available on context. +- `GenericFlag` type. +- `Float64Flag` type. +- `BoolTFlag` type. +- `IsSet` flag helper on context. +- More flag lookup funcs at context level. +- More tests & docs. + +### Changed +- Help template updates to account for presence/absence of flags. +- Separated subcommand help template. +- Exposed `HelpPrinter` var for more control over help output. + +## [1.0.0] - 2013-11-01 +### Added +- `help` flag in default app flag set and each command flag set. +- Custom handling of argument parsing errors. +- Command lookup by name at app level. +- `StringSliceFlag` type and supporting `StringSlice` type. +- `IntSliceFlag` type and supporting `IntSlice` type. +- Slice type flag lookups by name at context level. +- Export of app and command help functions. +- More tests & docs. + +## [0.1.0] - 2013-07-22 +### Added +- Initial implementation. From 95d18920adb908a8d6ce6aea0c84a7b83b359e5a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2016 15:58:30 -0400 Subject: [PATCH 114/381] Linkify release headings --- CHANGELOG.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28be743..907637b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -212,6 +212,29 @@ - Export of app and command help functions. - More tests & docs. -## [0.1.0] - 2013-07-22 +## 0.1.0 - 2013-07-22 ### Added - Initial implementation. + +[Unreleased]: https://github.com/codegangsta/cli/compare/v1.14.0...HEAD +[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 +[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 +[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0 +[1.11.1]: https://github.com/codegangsta/cli/compare/v1.11.0...v1.11.1 +[1.11.0]: https://github.com/codegangsta/cli/compare/v1.10.2...v1.11.0 +[1.10.2]: https://github.com/codegangsta/cli/compare/v1.10.1...v1.10.2 +[1.10.1]: https://github.com/codegangsta/cli/compare/v1.10.0...v1.10.1 +[1.10.0]: https://github.com/codegangsta/cli/compare/v1.9.0...v1.10.0 +[1.9.0]: https://github.com/codegangsta/cli/compare/v1.8.0...v1.9.0 +[1.8.0]: https://github.com/codegangsta/cli/compare/v1.7.1...v1.8.0 +[1.7.1]: https://github.com/codegangsta/cli/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/codegangsta/cli/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/codegangsta/cli/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/codegangsta/cli/compare/v1.4.1...v1.5.0 +[1.4.1]: https://github.com/codegangsta/cli/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/codegangsta/cli/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/codegangsta/cli/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/codegangsta/cli/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/codegangsta/cli/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/codegangsta/cli/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/codegangsta/cli/compare/v0.1.0...v1.0.0 From a17c8cf1d8cce58eb467b8310ba684091d10a14c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2016 18:29:05 -0400 Subject: [PATCH 115/381] Rename func type suffixes `Fn`->`Func` and add `OnUsageErrorFunc` --- altsrc/flag.go | 4 ++-- app.go | 16 +++++++--------- command.go | 16 +++++++--------- command_test.go | 2 +- funcs.go | 16 +++++++++++----- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index 36ffa57..9aee544 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -38,7 +38,7 @@ func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSource // InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new // input source based on the func provided. If there is no error it will then apply the new input source to any flags // that are supported by the input source -func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFn { +func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc { return func(context *cli.Context) (int, error) { inputSource, err := createInputSource() if err != nil { @@ -52,7 +52,7 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont // InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new // input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is // no error it will then apply the new input source to any flags that are supported by the input source -func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFn { +func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc { return func(context *cli.Context) (int, error) { inputSource, err := createInputSource(context) if err != nil { diff --git a/app.go b/app.go index 267387c..107bb2c 100644 --- a/app.go +++ b/app.go @@ -47,21 +47,19 @@ type App struct { // Populate on app startup, only gettable throught method Categories() categories CommandCategories // An action to execute when the bash-completion flag is set - BashComplete BashCompleteFn + BashComplete BashCompleteFunc // 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 BeforeFn + Before BeforeFunc // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics - After AfterFn + After AfterFunc // The action to execute when no subcommands are specified - Action ActionFn + Action ActionFunc // Execute this function if the proper command cannot be found - CommandNotFound CommandNotFoundFn - // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to replace the original error messages. - // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. - OnUsageError func(context *Context, err error, isSubcommand bool) error + CommandNotFound CommandNotFoundFunc + // Execute this function if an usage error occurs + OnUsageError OnUsageErrorFunc // Compilation date Compiled time.Time // List of all authors who contributed diff --git a/command.go b/command.go index b9db123..7615c15 100644 --- a/command.go +++ b/command.go @@ -26,19 +26,17 @@ type Command struct { // The category the command is part of Category string // The function to call when checking for bash command completions - BashComplete BashCompleteFn + BashComplete BashCompleteFunc // 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 BeforeFn + Before BeforeFunc // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics - After AfterFn + After AfterFunc // The function to call when this command is invoked - Action ActionFn - // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. - // This function is able to replace the original error messages. - // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. - OnUsageError func(context *Context, err error) error + Action ActionFunc + // Execute this function if a usage error occurs. + OnUsageError OnUsageErrorFunc // List of child commands Subcommands Commands // List of flags to parse @@ -125,7 +123,7 @@ func (c Command) Run(ctx *Context) (ec int, err error) { if err != nil { if c.OnUsageError != nil { - err := c.OnUsageError(ctx, err) + err := c.OnUsageError(ctx, err, false) if err != nil { return DefaultErrorExitCode, err } diff --git a/command_test.go b/command_test.go index 96b20de..80dc5cd 100644 --- a/command_test.go +++ b/command_test.go @@ -81,7 +81,7 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { Flags: []Flag{ IntFlag{Name: "flag"}, }, - OnUsageError: func(c *Context, err error) error { + OnUsageError: func(c *Context, err error, _ bool) error { if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { t.Errorf("Expect an invalid value error, but got \"%v\"", err) } diff --git a/funcs.go b/funcs.go index 48909c1..f375fd9 100644 --- a/funcs.go +++ b/funcs.go @@ -1,18 +1,24 @@ package cli // An action to execute when the bash-completion flag is set -type BashCompleteFn func(*Context) +type BashCompleteFunc func(*Context) // 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 -type BeforeFn func(*Context) (int, error) +type BeforeFunc func(*Context) (int, error) // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics -type AfterFn func(*Context) (int, error) +type AfterFunc func(*Context) (int, error) // The action to execute when no subcommands are specified -type ActionFn func(*Context) int +type ActionFunc func(*Context) int // Execute this function if the proper command cannot be found -type CommandNotFoundFn func(*Context, string) +type CommandNotFoundFunc func(*Context, string) + +// Execute this function if an usage error occurs. This is useful for displaying +// customized usage error messages. This function is able to replace the +// original error messages. If this function is not set, the "Incorrect usage" +// is displayed and the execution is interrupted. +type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error From 4733699ce30faa633c4db36ebfbe427963593d7d Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 25 Apr 2016 19:47:03 -0700 Subject: [PATCH 116/381] Update changelog with placeholder support --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 907637b..074bfb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ## [Unreleased] +### Added +- Support for placeholders in flag usage strings + ## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) ### Added - Codebeat badge From b40b62794df47edfe7551f05f8f1635c0e3a2b9a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 26 Apr 2016 07:05:50 -0400 Subject: [PATCH 117/381] Ensure README examples are runnable --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 214e543..d0c3236 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,9 @@ This app will run and show help text, but is not very useful. Let's give an acti package main import ( + "fmt" "os" + "github.com/codegangsta/cli" ) @@ -58,8 +60,9 @@ func main() { app := cli.NewApp() app.Name = "boom" app.Usage = "make an explosive entrance" - app.Action = func(c *cli.Context) { - println("boom! I say!") + app.Action = func(c *cli.Context) int { + fmt.Println("boom! I say!") + return 0 } app.Run(os.Args) @@ -78,7 +81,9 @@ Start by creating a directory named `greet`, and within it, add a file, `greet.g package main import ( + "fmt" "os" + "github.com/codegangsta/cli" ) @@ -86,8 +91,9 @@ func main() { app := cli.NewApp() app.Name = "greet" app.Usage = "fight the loneliness!" - app.Action = func(c *cli.Context) { - println("Hello friend!") + app.Action = func(c *cli.Context) int { + fmt.Println("Hello friend!") + return 0 } app.Run(os.Args) @@ -370,8 +376,9 @@ COMMANDS: ### Exit code -It is your responsibility to call `os.Exit` with the exit code returned by -`app.Run`, e.g.: +Calling `App.Run` will not automatically call `os.Exit`, which means that by +default the exit code will "fall through" to being `0`. Proper exit code +propagation is the responsibility of the code that calls `App.Run`, e.g.: ```go package main @@ -382,10 +389,7 @@ import ( ) func main() { - exitCode, err := cli.NewApp().Run(os.Args) - if err != nil { - log.Println(err) - } + exitCode, _ := cli.NewApp().Run(os.Args) os.Exit(exitCode) } ``` From b7329f4968356c4747e41d88f73b999641b86c41 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 09:12:34 -0400 Subject: [PATCH 118/381] Switch from multi-return with exit codes to ExitCoder check --- altsrc/flag.go | 12 +-- altsrc/yaml_command_test.go | 30 +++---- app.go | 91 ++++++++++++------- app_test.go | 172 +++++++++++++++--------------------- cli.go | 29 ++++++ command.go | 46 ++++++---- command_test.go | 16 ++-- flag_test.go | 116 ++++++++++++------------ funcs.go | 6 +- help.go | 8 +- help_test.go | 8 +- 11 files changed, 291 insertions(+), 243 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index 9aee544..0a11ad5 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -39,13 +39,13 @@ func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSource // input source based on the func provided. If there is no error it will then apply the new input source to any flags // that are supported by the input source func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc { - return func(context *cli.Context) (int, error) { + return func(context *cli.Context) error { inputSource, err := createInputSource() if err != nil { - return cli.DefaultErrorExitCode, fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error()) + return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error()) } - return cli.DefaultSuccessExitCode, ApplyInputSourceValues(context, inputSource, flags) + return ApplyInputSourceValues(context, inputSource, flags) } } @@ -53,13 +53,13 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont // input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is // no error it will then apply the new input source to any flags that are supported by the input source func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc { - return func(context *cli.Context) (int, error) { + return func(context *cli.Context) error { inputSource, err := createInputSource(context) if err != nil { - return cli.DefaultErrorExitCode, fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error()) + return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error()) } - return cli.DefaultSuccessExitCode, ApplyInputSourceValues(context, inputSource, flags) + return ApplyInputSourceValues(context, inputSource, flags) } } diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 6909729..39c36f6 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -29,17 +29,17 @@ func TestCommandYamlFileTest(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) int { + Action: func(c *cli.Context) error { val := c.Int("test") expect(t, val, 15) - return 0 + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test"}), cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - _, err := command.Run(c) + err := command.Run(c) expect(t, err, nil) } @@ -62,10 +62,10 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) int { + Action: func(c *cli.Context) error { val := c.Int("test") expect(t, val, 10) - return 0 + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), @@ -73,7 +73,7 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - _, err := command.Run(c) + err := command.Run(c) expect(t, err, nil) } @@ -94,10 +94,10 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) int { + Action: func(c *cli.Context) error { val := c.Int("test") expect(t, val, 7) - return 0 + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test"}), @@ -105,7 +105,7 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - _, err := command.Run(c) + err := command.Run(c) expect(t, err, nil) } @@ -126,10 +126,10 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) int { + Action: func(c *cli.Context) error { val := c.Int("test") expect(t, val, 15) - return 0 + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), @@ -137,7 +137,7 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - _, err := command.Run(c) + err := command.Run(c) expect(t, err, nil) } @@ -161,17 +161,17 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) int { + Action: func(c *cli.Context) error { val := c.Int("test") expect(t, val, 11) - return 0 + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - _, err := command.Run(c) + err := command.Run(c) expect(t, err, nil) } diff --git a/app.go b/app.go index 107bb2c..70442f9 100644 --- a/app.go +++ b/app.go @@ -100,7 +100,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) (ec int, err error) { +func (a *App) Run(arguments []string) (err error) { if a.Author != "" || a.Email != "" { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } @@ -146,40 +146,43 @@ func (a *App) Run(arguments []string) (ec int, err error) { if nerr != nil { fmt.Fprintln(a.Writer, nerr) ShowAppHelp(context) - return DefaultErrorExitCode, nerr + return nerr } if checkCompletions(context) { - return DefaultSuccessExitCode, nil + return nil } if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) if err != nil { - return DefaultErrorExitCode, err + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") + } } - return DefaultSuccessExitCode, err + return err } else { fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowAppHelp(context) - return DefaultErrorExitCode, err + return err } } if !a.HideHelp && checkHelp(context) { ShowAppHelp(context) - return DefaultSuccessExitCode, nil + return nil } if !a.HideVersion && checkVersion(context) { ShowVersion(context) - return DefaultSuccessExitCode, nil + return nil } if a.After != nil { defer func() { - afterEc, afterErr := a.After(context) + afterErr := a.After(context) if afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) @@ -187,16 +190,21 @@ func (a *App) Run(arguments []string) (ec int, err error) { err = afterErr } } - ec = afterEc }() } if a.Before != nil { - ec, err = a.Before(context) + err = a.Before(context) if err != nil { fmt.Fprintf(a.Writer, "%v\n\n", err) ShowAppHelp(context) - return ec, err + + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") + } + + return err } } @@ -210,19 +218,34 @@ func (a *App) Run(arguments []string) (ec int, err error) { } // Run default Action - return a.Action(context), nil + err = a.Action(context) + + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") + } + + return err } // Another entry point to the cli app, takes care of passing arguments and error handling func (a *App) RunAndExitOnError() { - if exitCode, err := a.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(exitCode) + err := a.Run(os.Args) + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") } + + if err != nil { + os.Exit(DefaultErrorExitCode) + panic("unreachable") + } + + os.Exit(DefaultSuccessExitCode) } // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags -func (a *App) RunAsSubcommand(ctx *Context) (ec int, 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 { @@ -262,55 +285,63 @@ func (a *App) RunAsSubcommand(ctx *Context) (ec int, err error) { } else { ShowCommandHelp(ctx, context.Args().First()) } - return DefaultErrorExitCode, nerr + return nerr } if checkCompletions(context) { - return DefaultSuccessExitCode, nil + return nil } if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - if err != nil { - return DefaultErrorExitCode, err + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") } - return DefaultSuccessExitCode, err + return nil } else { fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowSubcommandHelp(context) - return DefaultErrorExitCode, err + return err } } if len(a.Commands) > 0 { if checkSubcommandHelp(context) { - return DefaultSuccessExitCode, nil + return nil } } else { if checkCommandHelp(ctx, context.Args().First()) { - return DefaultSuccessExitCode, nil + return nil } } if a.After != nil { defer func() { - afterEc, afterErr := a.After(context) + afterErr := a.After(context) if afterErr != nil { + if exitErr, ok := afterErr.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") + } if err != nil { err = NewMultiError(err, afterErr) } else { err = afterErr } } - ec = afterEc }() } if a.Before != nil { - ec, err = a.Before(context) + err = a.Before(context) if err != nil { - return ec, err + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") + } + return err } } @@ -324,7 +355,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (ec int, err error) { } // Run default Action - return a.Action(context), nil + return a.Action(context) } // Returns the named command on App. Returns nil if the command does not exist diff --git a/app_test.go b/app_test.go index d0f06f0..0241ad3 100644 --- a/app_test.go +++ b/app_test.go @@ -22,9 +22,9 @@ func ExampleApp_Run() { app.Flags = []Flag{ StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Action = func(c *Context) int { + app.Action = func(c *Context) error { fmt.Printf("Hello %v\n", c.String("name")) - return 0 + return nil } app.UsageText = "app [first_arg] [second_arg]" app.Author = "Harrison" @@ -59,9 +59,9 @@ func ExampleApp_Run_subcommand() { Usage: "Name of the person to greet", }, }, - Action: func(c *Context) int { + Action: func(c *Context) error { fmt.Println("Hello,", c.String("name")) - return 0 + return nil }, }, }, @@ -88,9 +88,9 @@ func ExampleApp_Run_help() { Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *Context) int { + Action: func(c *Context) error { fmt.Printf("i like to describe things") - return 0 + return nil }, }, } @@ -119,17 +119,17 @@ func ExampleApp_Run_bashComplete() { Aliases: []string{"d"}, Usage: "use it to see a description", Description: "This is how we describe describeit the function", - Action: func(c *Context) int { + Action: func(c *Context) error { fmt.Printf("i like to describe things") - return 0 + return nil }, }, { Name: "next", Usage: "next example", Description: "more stuff to see when generating bash completion", - Action: func(c *Context) int { + Action: func(c *Context) error { fmt.Printf("the next example") - return 0 + return nil }, }, } @@ -147,17 +147,15 @@ func TestApp_Run(t *testing.T) { s := "" app := NewApp() - app.Action = func(c *Context) int { + app.Action = func(c *Context) error { s = s + c.Args().First() - return 0 + return nil } - ec, err := app.Run([]string{"command", "foo"}) + err := app.Run([]string{"command", "foo"}) expect(t, err, nil) - expect(t, ec, 0) - ec, err = app.Run([]string{"command", "bar"}) + err = app.Run([]string{"command", "bar"}) expect(t, err, nil) - expect(t, ec, 0) expect(t, s, "foobar") } @@ -196,10 +194,10 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { Flags: []Flag{ StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *Context) int { + Action: func(c *Context) error { parsedOption = c.String("option") firstArg = c.Args().First() - return 0 + return nil }, } app.Commands = []Command{command} @@ -217,9 +215,9 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { a.Commands = []Command{ { Name: "foo", - Action: func(c *Context) int { + Action: func(c *Context) error { context = c - return 0 + return nil }, Flags: []Flag{ StringFlag{ @@ -228,7 +226,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { Usage: "language for the greeting", }, }, - Before: func(_ *Context) (int, error) { return 0, nil }, + Before: func(_ *Context) error { return nil }, }, } a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) @@ -247,10 +245,10 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { Flags: []Flag{ StringFlag{Name: "option", Value: "", Usage: "some option"}, }, - Action: func(c *Context) int { + Action: func(c *Context) error { parsedOption = c.String("option") args = c.Args() - return 0 + return nil }, } app.Commands = []Command{command} @@ -269,9 +267,9 @@ func TestApp_CommandWithDash(t *testing.T) { app := NewApp() command := Command{ Name: "cmd", - Action: func(c *Context) int { + Action: func(c *Context) error { args = c.Args() - return 0 + return nil }, } app.Commands = []Command{command} @@ -288,9 +286,9 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { app := NewApp() command := Command{ Name: "cmd", - Action: func(c *Context) int { + Action: func(c *Context) error { args = c.Args() - return 0 + return nil }, } app.Commands = []Command{command} @@ -309,9 +307,9 @@ func TestApp_Float64Flag(t *testing.T) { app.Flags = []Flag{ Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, } - app.Action = func(c *Context) int { + app.Action = func(c *Context) error { meters = c.Float64("height") - return 0 + return nil } app.Run([]string{"", "--height", "1.93"}) @@ -330,12 +328,12 @@ func TestApp_ParseSliceFlags(t *testing.T) { IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"}, StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"}, }, - Action: func(c *Context) int { + Action: func(c *Context) error { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") parsedOption = c.String("option") firstArg = c.Args().First() - return 0 + return nil }, } app.Commands = []Command{command} @@ -388,10 +386,10 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { IntSliceFlag{Name: "a", Usage: "set numbers"}, StringSliceFlag{Name: "str", Usage: "set strings"}, }, - Action: func(c *Context) int { + Action: func(c *Context) error { parsedIntSlice = c.IntSlice("a") parsedStringSlice = c.StringSlice("str") - return 0 + return nil }, } app.Commands = []Command{command} @@ -443,7 +441,7 @@ func TestApp_SetStdout(t *testing.T) { app.Name = "test" app.Writer = w - _, err := app.Run([]string{"help"}) + err := app.Run([]string{"help"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -461,22 +459,22 @@ func TestApp_BeforeFunc(t *testing.T) { app := NewApp() - app.Before = func(c *Context) (int, error) { + app.Before = func(c *Context) error { beforeRun = true s := c.String("opt") if s == "fail" { - return DefaultErrorExitCode, beforeError + return beforeError } - return DefaultSuccessExitCode, nil + return nil } app.Commands = []Command{ Command{ Name: "sub", - Action: func(c *Context) int { + Action: func(c *Context) error { subcommandRun = true - return DefaultSuccessExitCode + return nil }, }, } @@ -486,7 +484,7 @@ func TestApp_BeforeFunc(t *testing.T) { } // run with the Before() func succeeding - ec, err := app.Run([]string{"command", "--opt", "succeed", "sub"}) + err = app.Run([]string{"command", "--opt", "succeed", "sub"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -500,15 +498,11 @@ func TestApp_BeforeFunc(t *testing.T) { t.Errorf("Subcommand not executed when expected") } - if ec != DefaultSuccessExitCode { - t.Errorf("Expected exit code to be %d but got %d", DefaultSuccessExitCode, ec) - } - // reset beforeRun, subcommandRun = false, false // run with the Before() func failing - ec, err = app.Run([]string{"command", "--opt", "fail", "sub"}) + err = app.Run([]string{"command", "--opt", "fail", "sub"}) // should be the same error produced by the Before func if err != beforeError { @@ -522,10 +516,6 @@ func TestApp_BeforeFunc(t *testing.T) { if subcommandRun == true { t.Errorf("Subcommand executed when NOT expected") } - - if ec != DefaultErrorExitCode { - t.Errorf("Expected exit code to be %d but got %d", DefaultErrorExitCode, ec) - } } func TestApp_AfterFunc(t *testing.T) { @@ -535,22 +525,22 @@ func TestApp_AfterFunc(t *testing.T) { app := NewApp() - app.After = func(c *Context) (int, error) { + app.After = func(c *Context) error { afterRun = true s := c.String("opt") if s == "fail" { - return DefaultErrorExitCode, afterError + return afterError } - return DefaultSuccessExitCode, nil + return nil } app.Commands = []Command{ Command{ Name: "sub", - Action: func(c *Context) int { + Action: func(c *Context) error { subcommandRun = true - return DefaultSuccessExitCode + return nil }, }, } @@ -560,7 +550,7 @@ func TestApp_AfterFunc(t *testing.T) { } // run with the After() func succeeding - ec, err := app.Run([]string{"command", "--opt", "succeed", "sub"}) + err = app.Run([]string{"command", "--opt", "succeed", "sub"}) if err != nil { t.Fatalf("Run error: %s", err) @@ -574,15 +564,11 @@ func TestApp_AfterFunc(t *testing.T) { t.Errorf("Subcommand not executed when expected") } - if ec != DefaultSuccessExitCode { - t.Errorf("Expected exit code to be %d but got %d", DefaultSuccessExitCode, ec) - } - // reset afterRun, subcommandRun = false, false // run with the Before() func failing - ec, err = app.Run([]string{"command", "--opt", "fail", "sub"}) + err = app.Run([]string{"command", "--opt", "fail", "sub"}) // should be the same error produced by the Before func if err != afterError { @@ -596,10 +582,6 @@ func TestApp_AfterFunc(t *testing.T) { if subcommandRun == false { t.Errorf("Subcommand not executed when expected") } - - if ec != DefaultErrorExitCode { - t.Errorf("Expected exit code to be %d but got %d", DefaultErrorExitCode, ec) - } } func TestAppNoHelpFlag(t *testing.T) { @@ -612,7 +594,7 @@ func TestAppNoHelpFlag(t *testing.T) { app := NewApp() app.Writer = ioutil.Discard - _, err := app.Run([]string{"test", "-h"}) + err := app.Run([]string{"test", "-h"}) if err != flag.ErrHelp { t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) @@ -669,9 +651,9 @@ func TestAppCommandNotFound(t *testing.T) { app.Commands = []Command{ Command{ Name: "bar", - Action: func(c *Context) int { + Action: func(c *Context) error { subcommandRun = true - return 0 + return nil }, }, } @@ -689,10 +671,10 @@ func TestGlobalFlag(t *testing.T) { app.Flags = []Flag{ StringFlag{Name: "global, g", Usage: "global"}, } - app.Action = func(c *Context) int { + app.Action = func(c *Context) error { globalFlag = c.GlobalString("global") globalFlagSet = c.GlobalIsSet("global") - return 0 + return nil } app.Run([]string{"command", "-g", "foo"}) expect(t, globalFlag, "foo") @@ -718,14 +700,14 @@ func TestGlobalFlagsInSubcommands(t *testing.T) { Subcommands: []Command{ { Name: "bar", - Action: func(c *Context) int { + Action: func(c *Context) error { if c.GlobalBool("debug") { subcommandRun = true } if c.GlobalBool("parent") { parentFlag = true } - return 0 + return nil }, }, }, @@ -767,7 +749,7 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } app.Commands = []Command{cmd} - _, err := app.Run(flagSet) + err := app.Run(flagSet) if err != nil { t.Error(err) @@ -808,7 +790,7 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } app.Commands = []Command{cmd} - _, err := app.Run([]string{"command", "foo", "bar", "--help"}) + err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { t.Error(err) } @@ -839,7 +821,7 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { } app.Commands = []Command{cmd} - _, err := app.Run([]string{"command", "foo", "bar", "--help"}) + err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { t.Error(err) } @@ -870,7 +852,7 @@ func TestApp_Run_CommandHelpName(t *testing.T) { } app.Commands = []Command{cmd} - _, err := app.Run([]string{"command", "foo", "bar", "--help"}) + err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { t.Error(err) } @@ -901,7 +883,7 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { } app.Commands = []Command{cmd} - _, err := app.Run([]string{"command", "foo", "--help"}) + err := app.Run([]string{"command", "foo", "--help"}) if err != nil { t.Error(err) } @@ -927,12 +909,12 @@ func TestApp_Run_Help(t *testing.T) { app.Name = "boom" app.Usage = "make an explosive entrance" app.Writer = buf - app.Action = func(c *Context) int { + app.Action = func(c *Context) error { buf.WriteString("boom I say!") - return 0 + return nil } - _, err := app.Run(args) + err := app.Run(args) if err != nil { t.Error(err) } @@ -959,12 +941,12 @@ func TestApp_Run_Version(t *testing.T) { app.Usage = "make an explosive entrance" app.Version = "0.1.0" app.Writer = buf - app.Action = func(c *Context) int { + app.Action = func(c *Context) error { buf.WriteString("boom I say!") - return 0 + return nil } - _, err := app.Run(args) + err := app.Run(args) if err != nil { t.Error(err) } @@ -1029,11 +1011,11 @@ func TestApp_Run_Categories(t *testing.T) { func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() - app.Action = func(c *Context) int { return 0 } - app.Before = func(c *Context) (int, error) { return 1, fmt.Errorf("before error") } - app.After = func(c *Context) (int, error) { return 2, fmt.Errorf("after error") } + app.Action = func(c *Context) error { return nil } + app.Before = func(c *Context) error { return fmt.Errorf("before error") } + app.After = func(c *Context) error { return fmt.Errorf("after error") } - ec, err := app.Run([]string{"foo"}) + err := app.Run([]string{"foo"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } @@ -1044,10 +1026,6 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { if !strings.Contains(err.Error(), "after error") { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } - - if ec != 2 { - t.Errorf("Expected exit code to be %d but got %d", 2, ec) - } } func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { @@ -1060,12 +1038,12 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { }, }, Name: "bar", - Before: func(c *Context) (int, error) { return 1, fmt.Errorf("before error") }, - After: func(c *Context) (int, error) { return 2, fmt.Errorf("after error") }, + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, }, } - ec, err := app.Run([]string{"foo", "bar"}) + err := app.Run([]string{"foo", "bar"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } @@ -1076,10 +1054,6 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { if !strings.Contains(err.Error(), "after error") { t.Errorf("expected text of error from After method, but got none in \"%v\"", err) } - - if ec != 2 { - t.Errorf("Expected exit code to be %d but got %d", 2, ec) - } } func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { @@ -1102,7 +1076,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { }, } - _, err := app.Run([]string{"foo", "--flag=wrong"}) + err := app.Run([]string{"foo", "--flag=wrong"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } @@ -1132,7 +1106,7 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { }, } - _, err := app.Run([]string{"foo", "--flag=wrong", "bar"}) + err := app.Run([]string{"foo", "--flag=wrong", "bar"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } diff --git a/cli.go b/cli.go index 31dc912..f8476d2 100644 --- a/cli.go +++ b/cli.go @@ -19,6 +19,7 @@ package cli import ( + "fmt" "strings" ) @@ -38,3 +39,31 @@ func (m MultiError) Error() string { return strings.Join(errs, "\n") } + +type ExitCoder interface { + ExitCode() int +} + +type ExitError struct { + exitCode int + message string +} + +func NewExitError(message string, exitCode int) *ExitError { + return &ExitError{ + exitCode: exitCode, + message: message, + } +} + +func (ee *ExitError) Error() string { + return ee.message +} + +func (ee *ExitError) String() string { + return fmt.Sprintf("%s exitcode=%v", ee.message, ee.exitCode) +} + +func (ee *ExitError) ExitCode() int { + return ee.exitCode +} diff --git a/command.go b/command.go index 7615c15..1137267 100644 --- a/command.go +++ b/command.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "io/ioutil" + "os" "sort" "strings" ) @@ -63,7 +64,7 @@ func (c Command) FullName() string { type Commands []Command // Invokes the command given the context, parses ctx.Args() to generate command-specific flags -func (c Command) Run(ctx *Context) (ec int, err error) { +func (c Command) Run(ctx *Context) (err error) { if len(c.Subcommands) > 0 { return c.startApp(ctx) } @@ -124,15 +125,16 @@ func (c Command) Run(ctx *Context) (ec int, err error) { if err != nil { if c.OnUsageError != nil { err := c.OnUsageError(ctx, err, false) - if err != nil { - return DefaultErrorExitCode, err + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") } - return DefaultSuccessExitCode, err + return err } else { fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - return DefaultErrorExitCode, err + return err } } @@ -141,47 +143,59 @@ func (c Command) Run(ctx *Context) (ec int, err error) { fmt.Fprintln(ctx.App.Writer, nerr) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - return DefaultErrorExitCode, nerr + return nerr } context := NewContext(ctx.App, set, ctx) if checkCommandCompletions(context, c.Name) { - return DefaultSuccessExitCode, nil + return nil } if checkCommandHelp(context, c.Name) { - return DefaultSuccessExitCode, nil + return nil } if c.After != nil { defer func() { - afterEc, afterErr := c.After(context) + afterErr := c.After(context) if afterErr != nil { + if exitErr, ok := afterErr.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") + } if err != nil { err = NewMultiError(err, afterErr) } else { err = afterErr } - - ec = afterEc } }() } if c.Before != nil { - ec, err = c.Before(context) + err = c.Before(context) if err != nil { fmt.Fprintln(ctx.App.Writer, err) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - return ec, err + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") + } + return err } } context.Command = c - ec = c.Action(context) - return ec, err + err = c.Action(context) + if err != nil { + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + panic("unreachable") + } + } + return err } func (c Command) Names() []string { @@ -204,7 +218,7 @@ func (c Command) HasName(name string) bool { return false } -func (c Command) startApp(ctx *Context) (int, error) { +func (c Command) startApp(ctx *Context) error { app := NewApp() // set the name and usage diff --git a/command_test.go b/command_test.go index 80dc5cd..2687212 100644 --- a/command_test.go +++ b/command_test.go @@ -34,12 +34,12 @@ func TestCommandFlagParsing(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(_ *Context) int { return 0 }, + Action: func(_ *Context) error { return nil }, } command.SkipFlagParsing = c.skipFlagParsing - _, err := command.Run(context) + err := command.Run(context) expect(t, err, c.expectedErr) expect(t, []string(context.Args()), c.testArgs) @@ -51,16 +51,16 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app.Commands = []Command{ Command{ Name: "bar", - Before: func(c *Context) (int, error) { - return 1, fmt.Errorf("before error") + Before: func(c *Context) error { + return fmt.Errorf("before error") }, - After: func(c *Context) (int, error) { - return 1, fmt.Errorf("after error") + After: func(c *Context) error { + return fmt.Errorf("after error") }, }, } - _, err := app.Run([]string{"foo", "bar"}) + err := app.Run([]string{"foo", "bar"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } @@ -90,7 +90,7 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { }, } - _, err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) if err == nil { t.Fatalf("expected to receive error from Run, got none") } diff --git a/flag_test.go b/flag_test.go index a68e543..3dac482 100644 --- a/flag_test.go +++ b/flag_test.go @@ -323,14 +323,14 @@ func TestParseMultiString(t *testing.T) { Flags: []Flag{ StringFlag{Name: "serve, s"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.String("serve") != "10" { t.Errorf("main name not set") } if ctx.String("s") != "10" { t.Errorf("short name not set") } - return 0 + return nil }, }).Run([]string{"run", "-s", "10"}) } @@ -344,11 +344,11 @@ func TestParseDestinationString(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if dest != "10" { t.Errorf("expected destination String 10") } - return 0 + return nil }, } a.Run([]string{"run", "--dest", "10"}) @@ -361,14 +361,14 @@ func TestParseMultiStringFromEnv(t *testing.T) { Flags: []Flag{ StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.String("count") != "20" { t.Errorf("main name not set") } if ctx.String("c") != "20" { t.Errorf("short name not set") } - return 0 + return nil }, }).Run([]string{"run"}) } @@ -380,14 +380,14 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { Flags: []Flag{ StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.String("count") != "20" { t.Errorf("main name not set") } if ctx.String("c") != "20" { t.Errorf("short name not set") } - return 0 + return nil }, }).Run([]string{"run"}) } @@ -397,14 +397,14 @@ func TestParseMultiStringSlice(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "serve, s", Value: &StringSlice{}}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { t.Errorf("short name not set") } - return 0 + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -417,14 +417,14 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, }).Run([]string{"run"}) } @@ -437,14 +437,14 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, }).Run([]string{"run"}) } @@ -454,14 +454,14 @@ func TestParseMultiInt(t *testing.T) { Flags: []Flag{ IntFlag{Name: "serve, s"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Int("serve") != 10 { t.Errorf("main name not set") } if ctx.Int("s") != 10 { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run", "-s", "10"}) @@ -476,11 +476,11 @@ func TestParseDestinationInt(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if dest != 10 { t.Errorf("expected destination Int 10") } - return 0 + return nil }, } a.Run([]string{"run", "--dest", "10"}) @@ -493,14 +493,14 @@ func TestParseMultiIntFromEnv(t *testing.T) { Flags: []Flag{ IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } if ctx.Int("t") != 10 { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -513,14 +513,14 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { Flags: []Flag{ IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { t.Errorf("main name not set") } if ctx.Int("t") != 10 { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -531,14 +531,14 @@ func TestParseMultiIntSlice(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "serve, s", Value: &IntSlice{}}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -551,14 +551,14 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, }).Run([]string{"run"}) } @@ -571,14 +571,14 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, }).Run([]string{"run"}) } @@ -588,14 +588,14 @@ func TestParseMultiFloat64(t *testing.T) { Flags: []Flag{ Float64Flag{Name: "serve, s"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Float64("serve") != 10.2 { t.Errorf("main name not set") } if ctx.Float64("s") != 10.2 { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run", "-s", "10.2"}) @@ -610,11 +610,11 @@ func TestParseDestinationFloat64(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if dest != 10.2 { t.Errorf("expected destination Float64 10.2") } - return 0 + return nil }, } a.Run([]string{"run", "--dest", "10.2"}) @@ -627,14 +627,14 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { Flags: []Flag{ Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } if ctx.Float64("t") != 15.5 { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -647,14 +647,14 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { Flags: []Flag{ Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { t.Errorf("main name not set") } if ctx.Float64("t") != 15.5 { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -665,14 +665,14 @@ func TestParseMultiBool(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "serve, s"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Bool("serve") != true { t.Errorf("main name not set") } if ctx.Bool("s") != true { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run", "--serve"}) @@ -687,11 +687,11 @@ func TestParseDestinationBool(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if dest != true { t.Errorf("expected destination Bool true") } - return 0 + return nil }, } a.Run([]string{"run", "--dest"}) @@ -704,14 +704,14 @@ func TestParseMultiBoolFromEnv(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -724,14 +724,14 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -742,14 +742,14 @@ func TestParseMultiBoolT(t *testing.T) { Flags: []Flag{ BoolTFlag{Name: "serve, s"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.BoolT("serve") != true { t.Errorf("main name not set") } if ctx.BoolT("s") != true { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run", "--serve"}) @@ -764,11 +764,11 @@ func TestParseDestinationBoolT(t *testing.T) { Destination: &dest, }, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if dest != true { t.Errorf("expected destination BoolT true") } - return 0 + return nil }, } a.Run([]string{"run", "--dest"}) @@ -781,14 +781,14 @@ func TestParseMultiBoolTFromEnv(t *testing.T) { Flags: []Flag{ BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -801,14 +801,14 @@ func TestParseMultiBoolTFromEnvCascade(t *testing.T) { Flags: []Flag{ BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -837,14 +837,14 @@ func TestParseGeneric(t *testing.T) { Flags: []Flag{ GenericFlag{Name: "serve, s", Value: &Parser{}}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) { t.Errorf("short name not set") } - return 0 + return nil }, } a.Run([]string{"run", "-s", "10,20"}) @@ -857,14 +857,14 @@ func TestParseGenericFromEnv(t *testing.T) { Flags: []Flag{ GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { 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") } - return 0 + return nil }, } a.Run([]string{"run"}) @@ -877,11 +877,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) { Flags: []Flag{ GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { t.Errorf("value not set from env") } - return 0 + return nil }, } a.Run([]string{"run"}) diff --git a/funcs.go b/funcs.go index f375fd9..94640ea 100644 --- a/funcs.go +++ b/funcs.go @@ -5,14 +5,14 @@ type BashCompleteFunc func(*Context) // 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 -type BeforeFunc func(*Context) (int, error) +type BeforeFunc func(*Context) error // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics -type AfterFunc func(*Context) (int, error) +type AfterFunc func(*Context) error // The action to execute when no subcommands are specified -type ActionFunc func(*Context) int +type ActionFunc func(*Context) error // Execute this function if the proper command cannot be found type CommandNotFoundFunc func(*Context, string) diff --git a/help.go b/help.go index b6a190d..a895e6c 100644 --- a/help.go +++ b/help.go @@ -78,14 +78,14 @@ var helpCommand = Command{ Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *Context) int { + Action: func(c *Context) error { args := c.Args() if args.Present() { ShowCommandHelp(c, args.First()) } else { ShowAppHelp(c) } - return 0 + return nil }, } @@ -94,14 +94,14 @@ var helpSubcommand = Command{ Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *Context) int { + Action: func(c *Context) error { args := c.Args() if args.Present() { ShowCommandHelp(c, args.First()) } else { ShowSubcommandHelp(c) } - return 0 + return nil }, } diff --git a/help_test.go b/help_test.go index 260800d..ee5c25c 100644 --- a/help_test.go +++ b/help_test.go @@ -66,11 +66,11 @@ func Test_Help_Custom_Flags(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "foo, h"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Bool("h") != true { t.Errorf("custom help flag not set") } - return 0 + return nil }, } output := new(bytes.Buffer) @@ -96,11 +96,11 @@ func Test_Version_Custom_Flags(t *testing.T) { Flags: []Flag{ BoolFlag{Name: "foo, v"}, }, - Action: func(ctx *Context) int { + Action: func(ctx *Context) error { if ctx.Bool("v") != true { t.Errorf("custom version flag not set") } - return 0 + return nil }, } output := new(bytes.Buffer) From f3e55a07831afc7b4ccaf13f31b2baa1bc55fac3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 09:13:52 -0400 Subject: [PATCH 119/381] Move error types to errors.go --- cli.go | 50 -------------------------------------------------- errors.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 50 deletions(-) create mode 100644 errors.go diff --git a/cli.go b/cli.go index f8476d2..b742545 100644 --- a/cli.go +++ b/cli.go @@ -17,53 +17,3 @@ // app.Run(os.Args) // } package cli - -import ( - "fmt" - "strings" -) - -type MultiError struct { - Errors []error -} - -func NewMultiError(err ...error) MultiError { - return MultiError{Errors: err} -} - -func (m MultiError) Error() string { - errs := make([]string, len(m.Errors)) - for i, err := range m.Errors { - errs[i] = err.Error() - } - - return strings.Join(errs, "\n") -} - -type ExitCoder interface { - ExitCode() int -} - -type ExitError struct { - exitCode int - message string -} - -func NewExitError(message string, exitCode int) *ExitError { - return &ExitError{ - exitCode: exitCode, - message: message, - } -} - -func (ee *ExitError) Error() string { - return ee.message -} - -func (ee *ExitError) String() string { - return fmt.Sprintf("%s exitcode=%v", ee.message, ee.exitCode) -} - -func (ee *ExitError) ExitCode() int { - return ee.exitCode -} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..0c6b44d --- /dev/null +++ b/errors.go @@ -0,0 +1,51 @@ +package cli + +import ( + "fmt" + "strings" +) + +type MultiError struct { + Errors []error +} + +func NewMultiError(err ...error) MultiError { + return MultiError{Errors: err} +} + +func (m MultiError) Error() string { + errs := make([]string, len(m.Errors)) + for i, err := range m.Errors { + errs[i] = err.Error() + } + + return strings.Join(errs, "\n") +} + +type ExitCoder interface { + ExitCode() int +} + +type ExitError struct { + exitCode int + message string +} + +func NewExitError(message string, exitCode int) *ExitError { + return &ExitError{ + exitCode: exitCode, + message: message, + } +} + +func (ee *ExitError) Error() string { + return ee.message +} + +func (ee *ExitError) String() string { + return fmt.Sprintf("%s exitcode=%v", ee.message, ee.exitCode) +} + +func (ee *ExitError) ExitCode() int { + return ee.exitCode +} From f688d474157f3dfc2c5295bbff2b1bc88a8492fb Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 09:18:42 -0400 Subject: [PATCH 120/381] Encapsulate ExitCoder check into a lil func --- app.go | 49 ++++++------------------------------------------- command.go | 21 ++++----------------- errors.go | 9 +++++++++ 3 files changed, 19 insertions(+), 60 deletions(-) diff --git a/app.go b/app.go index 70442f9..22f6bd6 100644 --- a/app.go +++ b/app.go @@ -157,10 +157,7 @@ func (a *App) Run(arguments []string) (err error) { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) if err != nil { - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) } return err } else { @@ -198,12 +195,7 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { fmt.Fprintf(a.Writer, "%v\n\n", err) ShowAppHelp(context) - - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } - + HandleExitCoder(err) return err } } @@ -219,31 +211,11 @@ func (a *App) Run(arguments []string) (err error) { // Run default Action err = a.Action(context) - - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) return err } -// Another entry point to the cli app, takes care of passing arguments and error handling -func (a *App) RunAndExitOnError() { - err := a.Run(os.Args) - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } - - if err != nil { - os.Exit(DefaultErrorExitCode) - panic("unreachable") - } - - os.Exit(DefaultSuccessExitCode) -} - // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { // append help to commands @@ -295,10 +267,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) return nil } else { fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") @@ -321,10 +290,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { defer func() { afterErr := a.After(context) if afterErr != nil { - if exitErr, ok := afterErr.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -337,10 +303,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.Before != nil { err = a.Before(context) if err != nil { - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) return err } } diff --git a/command.go b/command.go index 1137267..0fe3223 100644 --- a/command.go +++ b/command.go @@ -3,7 +3,6 @@ package cli import ( "fmt" "io/ioutil" - "os" "sort" "strings" ) @@ -125,10 +124,7 @@ func (c Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { err := c.OnUsageError(ctx, err, false) - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) return err } else { fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") @@ -160,10 +156,7 @@ func (c Command) Run(ctx *Context) (err error) { defer func() { afterErr := c.After(context) if afterErr != nil { - if exitErr, ok := afterErr.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -179,10 +172,7 @@ func (c Command) Run(ctx *Context) (err error) { fmt.Fprintln(ctx.App.Writer, err) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) return err } } @@ -190,10 +180,7 @@ func (c Command) Run(ctx *Context) (err error) { context.Command = c err = c.Action(context) if err != nil { - if exitErr, ok := err.(ExitCoder); ok { - os.Exit(exitErr.ExitCode()) - panic("unreachable") - } + HandleExitCoder(err) } return err } diff --git a/errors.go b/errors.go index 0c6b44d..da2d3de 100644 --- a/errors.go +++ b/errors.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "os" "strings" ) @@ -49,3 +50,11 @@ func (ee *ExitError) String() string { func (ee *ExitError) ExitCode() int { return ee.exitCode } + +// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if +// so calls os.Exit with the given exit code. +func HandleExitCoder(err error) { + if exitErr, ok := err.(ExitCoder); ok { + os.Exit(exitErr.ExitCode()) + } +} From 882dd2cc6bf6154b31444b7b49ff1be91f37b4e7 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 09:21:56 -0400 Subject: [PATCH 121/381] Making sure examples in README are valid --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d0c3236..cc09a3f 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,9 @@ func main() { app := cli.NewApp() app.Name = "boom" app.Usage = "make an explosive entrance" - app.Action = func(c *cli.Context) int { + app.Action = func(c *cli.Context) error { fmt.Println("boom! I say!") - return 0 + return nil } app.Run(os.Args) @@ -91,9 +91,9 @@ func main() { app := cli.NewApp() app.Name = "greet" app.Usage = "fight the loneliness!" - app.Action = func(c *cli.Context) int { + app.Action = func(c *cli.Context) error { fmt.Println("Hello friend!") - return 0 + return nil } app.Run(os.Args) @@ -139,8 +139,9 @@ You can lookup arguments by calling the `Args` function on `cli.Context`. ``` go ... -app.Action = func(c *cli.Context) { +app.Action = func(c *cli.Context) error { println("Hello", c.Args()[0]) + return nil } ... ``` @@ -158,7 +159,7 @@ app.Flags = []cli.Flag { Usage: "language for the greeting", }, } -app.Action = func(c *cli.Context) { +app.Action = func(c *cli.Context) error { name := "someone" if c.NArg() > 0 { name = c.Args()[0] @@ -168,6 +169,7 @@ app.Action = func(c *cli.Context) { } else { println("Hello", name) } + return nil } ... ``` @@ -185,7 +187,7 @@ app.Flags = []cli.Flag { Destination: &language, }, } -app.Action = func(c *cli.Context) { +app.Action = func(c *cli.Context) error { name := "someone" if c.NArg() > 0 { name = c.Args()[0] @@ -195,6 +197,7 @@ app.Action = func(c *cli.Context) { } else { println("Hello", name) } + return nil } ... ``` @@ -276,7 +279,7 @@ Here is a more complete sample of a command using YAML support: Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { // Action to run }, Flags: []cli.Flag{ From b2ac0616d1cfe79ae14152a038b1c99e6a64267b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 09:31:43 -0400 Subject: [PATCH 122/381] TRIVIAL Remove trailing whitespace --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1be55fd..53265d0 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ Will result in help output like: --config FILE, -c FILE Load configuration from FILE ``` -Note that only the first placeholder is used. Subsequent back-quoted words will be left as-is. +Note that only the first placeholder is used. Subsequent back-quoted words will be left as-is. #### Alternate Names From d48e22a9dd36cdabce676e518bdc9d182d8b5399 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 09:54:08 -0400 Subject: [PATCH 123/381] Docs around exit error behavior, + handling MultiError in HandleExitCoder --- README.md | 45 ++++++++++++++++++++++++++++++--------------- errors.go | 25 ++++++++++++++++++++----- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 53265d0..777b0a8 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ You can lookup arguments by calling the `Args` function on `cli.Context`. ``` go ... app.Action = func(c *cli.Context) error { - println("Hello", c.Args()[0]) + fmt.Println("Hello", c.Args()[0]) return nil } ... @@ -165,9 +165,9 @@ app.Action = func(c *cli.Context) error { name = c.Args()[0] } if c.String("lang") == "spanish" { - println("Hola", name) + fmt.Println("Hola", name) } else { - println("Hello", name) + fmt.Println("Hello", name) } return nil } @@ -193,9 +193,9 @@ app.Action = func(c *cli.Context) error { name = c.Args()[0] } if language == "spanish" { - println("Hola", name) + fmt.Println("Hola", name) } else { - println("Hello", name) + fmt.Println("Hello", name) } return nil } @@ -323,7 +323,7 @@ app.Commands = []cli.Command{ Aliases: []string{"a"}, Usage: "add a task to the list", Action: func(c *cli.Context) { - println("added task: ", c.Args().First()) + fmt.Println("added task: ", c.Args().First()) }, }, { @@ -331,7 +331,7 @@ app.Commands = []cli.Command{ Aliases: []string{"c"}, Usage: "complete a task on the list", Action: func(c *cli.Context) { - println("completed task: ", c.Args().First()) + fmt.Println("completed task: ", c.Args().First()) }, }, { @@ -343,14 +343,14 @@ app.Commands = []cli.Command{ Name: "add", Usage: "add a new template", Action: func(c *cli.Context) { - println("new task template: ", c.Args().First()) + fmt.Println("new task template: ", c.Args().First()) }, }, { Name: "remove", Usage: "remove an existing template", Action: func(c *cli.Context) { - println("removed task template: ", c.Args().First()) + fmt.Println("removed task template: ", c.Args().First()) }, }, }, @@ -401,20 +401,35 @@ COMMANDS: ### Exit code Calling `App.Run` will not automatically call `os.Exit`, which means that by -default the exit code will "fall through" to being `0`. Proper exit code -propagation is the responsibility of the code that calls `App.Run`, e.g.: +default the exit code will "fall through" to being `0`. An explicit exit code +may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a +`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: -```go +``` go package main import ( "os" + "github.com/codegangsta/cli" ) func main() { - exitCode, _ := cli.NewApp().Run(os.Args) - os.Exit(exitCode) + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.BoolTFlag{ + Name: "ginger-crouton", + Usage: "is it in the soup?", + }, + } + app.Action = func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.NewExitError("it is not in the soup", 86) + } + return nil + } + + app.Run(os.Args) } ``` @@ -436,7 +451,7 @@ app.Commands = []cli.Command{ Aliases: []string{"c"}, Usage: "complete a task on the list", Action: func(c *cli.Context) { - println("completed task: ", c.Args().First()) + fmt.Println("completed task: ", c.Args().First()) }, BashComplete: func(c *cli.Context) { // This will complete if no args are passed diff --git a/errors.go b/errors.go index da2d3de..1a6a8c7 100644 --- a/errors.go +++ b/errors.go @@ -23,15 +23,19 @@ func (m MultiError) Error() string { return strings.Join(errs, "\n") } +// ExitCoder is the interface checked by `App` and `Command` for a custom exit +// code type ExitCoder interface { ExitCode() int } +// ExitError fulfills both the builtin `error` interface and `ExitCoder` type ExitError struct { exitCode int message string } +// NewExitError makes a new *ExitError func NewExitError(message string, exitCode int) *ExitError { return &ExitError{ exitCode: exitCode, @@ -39,22 +43,33 @@ func NewExitError(message string, exitCode int) *ExitError { } } +// Error returns the string message, fulfilling the interface required by +// `error` func (ee *ExitError) Error() string { return ee.message } -func (ee *ExitError) String() string { - return fmt.Sprintf("%s exitcode=%v", ee.message, ee.exitCode) -} - +// ExitCode returns the exit code, fulfilling the interface required by +// `ExitCoder` func (ee *ExitError) ExitCode() int { return ee.exitCode } // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if -// so calls os.Exit with the given exit code. +// so prints the error to stderr (if it is non-empty) and calls os.Exit with the +// given exit code. If the given error is a MultiError, then this func is +// called on all members of the Errors slice. func HandleExitCoder(err error) { if exitErr, ok := err.(ExitCoder); ok { + if err.Error() != "" { + fmt.Fprintln(os.Stderr, err) + } os.Exit(exitErr.ExitCode()) } + + if multiErr, ok := err.(MultiError); ok { + for _, merr := range multiErr.Errors { + HandleExitCoder(merr) + } + } } From d45f7c1fe2bacacbfc6a324dcbbc71be469cb43d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 11:34:01 -0400 Subject: [PATCH 124/381] Allow for legacy and newer Action func signatures --- app.go | 51 +++++++++++++++++++++++++++++++++++++++++++++------ command.go | 8 ++++++-- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/app.go b/app.go index 22f6bd6..d839f39 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "sort" "time" ) @@ -55,7 +56,10 @@ type App struct { // It is run even if Action() panics After AfterFunc // The action to execute when no subcommands are specified - Action ActionFunc + Action interface{} + // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind + // of deprecation period has passed, maybe? + // Execute this function if the proper command cannot be found CommandNotFound CommandNotFoundFunc // Execute this function if an usage error occurs @@ -179,8 +183,7 @@ func (a *App) Run(arguments []string) (err error) { if a.After != nil { defer func() { - afterErr := a.After(context) - if afterErr != nil { + if afterErr := a.After(context); afterErr != nil { if err != nil { err = NewMultiError(err, afterErr) } else { @@ -210,9 +213,11 @@ func (a *App) Run(arguments []string) (err error) { } // Run default Action - err = a.Action(context) - HandleExitCoder(err) + err = HandleAction(a.Action, context) + if err != nil { + HandleExitCoder(err) + } return err } @@ -318,7 +323,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } // Run default Action - return a.Action(context) + err = HandleAction(a.Action, context) + + if err != nil { + HandleExitCoder(err) + } + return err } // Returns the named command on App. Returns nil if the command does not exist @@ -368,3 +378,32 @@ func (a Author) String() string { return fmt.Sprintf("%v %v", a.Name, e) } + +// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an +// ActionFunc, LegacyActionFunc, or some other invalid thing. If it's an +// ActionFunc or LegacyActionFunc, the func is run! +func HandleAction(action interface{}, context *Context) error { + if reflect.TypeOf(action).Kind() != reflect.Func { + panic("given Action must be a func") + } + + vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) + + if len(vals) == 0 { + fmt.Fprintln(os.Stderr, + "DEPRECATED Action signature. Must be `cli.ActionFunc`") + return nil + } + + if len(vals) > 1 { + fmt.Fprintln(os.Stderr, + "ERROR invalid Action signature. Must be `cli.ActionFunc`") + panic("given Action has invalid return signature") + } + + if err, ok := reflect.ValueOf(vals[0]).Interface().(error); ok { + return err + } + + return nil +} diff --git a/command.go b/command.go index 0fe3223..2932d53 100644 --- a/command.go +++ b/command.go @@ -34,7 +34,10 @@ type Command struct { // It is run even if Action() panics After AfterFunc // The function to call when this command is invoked - Action ActionFunc + Action interface{} + // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind + // of deprecation period has passed, maybe? + // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc // List of child commands @@ -178,7 +181,8 @@ func (c Command) Run(ctx *Context) (err error) { } context.Command = c - err = c.Action(context) + err = HandleAction(c.Action, context) + if err != nil { HandleExitCoder(err) } From 3b5133fbb1470779f6b0a81545be1c91d992167a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 11:38:49 -0400 Subject: [PATCH 125/381] Add gfmxr integration for checking examples in README.md now that we support the legacy Action func signature. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index c2b5c8d..9d262b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,10 @@ matrix: allow_failures: - go: tip +before_script: +- go get github.com/meatballhat/gfmxr/... + script: - go vet ./... - go test -v ./... +- gfmxr From 9e8ead512a239b79c58da4d97f9aa5de526a467e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 11:40:46 -0400 Subject: [PATCH 126/381] Making gfmxr assertion stronger --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9d262b4..133722f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,4 @@ before_script: script: - go vet ./... - go test -v ./... -- gfmxr +- gfmxr -c $(grep -c 'package main' README.md) -s README.md From 02924293ff31d413df30e70de754f62d63680055 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 11:51:41 -0400 Subject: [PATCH 127/381] Removing unused vars --- app.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app.go b/app.go index d839f39..b87ab9c 100644 --- a/app.go +++ b/app.go @@ -11,15 +11,6 @@ import ( "time" ) -var ( - // DefaultSuccessExitCode is the default for use with os.Exit intended to - // indicate success - DefaultSuccessExitCode = 0 - // DefaultErrorExitCode is the default for use with os.Exit intended to - // indicate an error - DefaultErrorExitCode = 1 -) - // App is the main structure of a cli application. It is recommended that // an app be created with the cli.NewApp() function type App struct { From 7371138edbce6a2ace08433e37a0a71f13c0491d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 27 Apr 2016 12:23:09 -0400 Subject: [PATCH 128/381] Add back App.RunAndExitOnError with deprecation message --- app.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app.go b/app.go index b87ab9c..fac5fd9 100644 --- a/app.go +++ b/app.go @@ -212,6 +212,17 @@ func (a *App) Run(arguments []string) (err error) { return err } +// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling +func (a *App) RunAndExitOnError() { + fmt.Fprintln(os.Stderr, + "DEPRECATED method `*cli.App.RunAndExitOnError`. "+ + "Use `*cli.App.Run` with an error type that fulfills `cli.ExitCoder`.") + if err := a.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { // append help to commands From 271b56c71b46d26543a729132cb7a0ecae5d2211 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 10:15:04 -0400 Subject: [PATCH 129/381] Cleanups based on feedback in #361 --- CHANGELOG.md | 11 +++++++++++ app.go | 24 ++++++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074bfb2..b03b52c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ ### Added - Support for placeholders in flag usage strings +- Support for custom exit code by returning an error that fulfills +`cli.ExitCoder` to `cli.App.Run`. + +### Deprecated +- +`cli.App.RunAndExitOnError`, which should now be done by returning an error +that fulfills `cli.ExitCoder` to `cli.App.Run`. +- +the legacy signature for `cli.App.Action` of `func(*cli.Context)`, which should +now have a signature of `func(*cli.Context) error`, as defined by +`cli.ActionFunc`. ## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) ### Added diff --git a/app.go b/app.go index fac5fd9..c9830d4 100644 --- a/app.go +++ b/app.go @@ -11,6 +11,15 @@ import ( "time" ) +var ( + errNonFuncAction = NewExitError("ERROR invalid Action type. "+ + "Must be a func of type `cli.ActionFunc`. "+ + "See https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-action-signature", 2) + errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+ + "Must be `cli.ActionFunc`. "+ + "See https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-action-signature", 2) +) + // App is the main structure of a cli application. It is recommended that // an app be created with the cli.NewApp() function type App struct { @@ -215,8 +224,8 @@ func (a *App) Run(arguments []string) (err error) { // DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling func (a *App) RunAndExitOnError() { fmt.Fprintln(os.Stderr, - "DEPRECATED method `*cli.App.RunAndExitOnError`. "+ - "Use `*cli.App.Run` with an error type that fulfills `cli.ExitCoder`.") + "DEPRECATED cli.App.RunAndExitOnError. "+ + "See https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-runandexitonerror") if err := a.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -382,11 +391,12 @@ func (a Author) String() string { } // HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an -// ActionFunc, LegacyActionFunc, or some other invalid thing. If it's an -// ActionFunc or LegacyActionFunc, the func is run! +// ActionFunc, a func with the legacy signature for Action, or some other +// invalid thing. If it's an ActionFunc or a func with the legacy signature for +// Action, the func is run! func HandleAction(action interface{}, context *Context) error { if reflect.TypeOf(action).Kind() != reflect.Func { - panic("given Action must be a func") + return errNonFuncAction } vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) @@ -398,9 +408,7 @@ func HandleAction(action interface{}, context *Context) error { } if len(vals) > 1 { - fmt.Fprintln(os.Stderr, - "ERROR invalid Action signature. Must be `cli.ActionFunc`") - panic("given Action has invalid return signature") + return errInvalidActionSignature } if err, ok := reflect.ValueOf(vals[0]).Interface().(error); ok { From b453bf5940c0b5e469050dfe17b144a605f6d199 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 11:03:10 -0400 Subject: [PATCH 130/381] Clarifying errors returned from HandleAction + tests --- app.go | 27 ++++++++++++++------ app_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/app.go b/app.go index c9830d4..0e78695 100644 --- a/app.go +++ b/app.go @@ -12,12 +12,14 @@ import ( ) var ( + appActionDeprecationURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-action-signature" + errNonFuncAction = NewExitError("ERROR invalid Action type. "+ "Must be a func of type `cli.ActionFunc`. "+ - "See https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-action-signature", 2) + fmt.Sprintf("See %s", appActionDeprecationURL), 2) errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+ "Must be `cli.ActionFunc`. "+ - "See https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-action-signature", 2) + fmt.Sprintf("See %s", appActionDeprecationURL), 2) ) // App is the main structure of a cli application. It is recommended that @@ -394,7 +396,18 @@ func (a Author) String() string { // ActionFunc, a func with the legacy signature for Action, or some other // invalid thing. If it's an ActionFunc or a func with the legacy signature for // Action, the func is run! -func HandleAction(action interface{}, context *Context) error { +func HandleAction(action interface{}, context *Context) (err error) { + defer func() { + if r := recover(); r != nil { + switch r.(type) { + case error: + err = r.(error) + default: + err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2) + } + } + }() + if reflect.TypeOf(action).Kind() != reflect.Func { return errNonFuncAction } @@ -404,16 +417,16 @@ func HandleAction(action interface{}, context *Context) error { if len(vals) == 0 { fmt.Fprintln(os.Stderr, "DEPRECATED Action signature. Must be `cli.ActionFunc`") - return nil + return err } if len(vals) > 1 { return errInvalidActionSignature } - if err, ok := reflect.ValueOf(vals[0]).Interface().(error); ok { - return err + if retErr, ok := reflect.ValueOf(vals[0]).Interface().(error); ok { + return retErr } - return nil + return err } diff --git a/app_test.go b/app_test.go index 0241ad3..f947728 100644 --- a/app_test.go +++ b/app_test.go @@ -1115,3 +1115,75 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { t.Errorf("Expect an intercepted error, but got \"%v\"", err) } } + +func TestHandleAction_WithNonFuncAction(t *testing.T) { + app := NewApp() + app.Action = 42 + err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) + + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + exitErr, ok := err.(*ExitError) + + if !ok { + t.Fatalf("expected to receive a *ExitError") + } + + if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { + t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) + } + + if exitErr.ExitCode() != 2 { + t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) + } +} + +func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { + app := NewApp() + app.Action = func() string { return "" } + err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) + + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + exitErr, ok := err.(*ExitError) + + if !ok { + t.Fatalf("expected to receive a *ExitError") + } + + if !strings.HasPrefix(exitErr.Error(), "ERROR unknown Action error") { + t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) + } + + if exitErr.ExitCode() != 2 { + t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) + } +} + +func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { + app := NewApp() + app.Action = func(_ *Context) (int, error) { return 0, nil } + err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) + + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + exitErr, ok := err.(*ExitError) + + if !ok { + t.Fatalf("expected to receive a *ExitError") + } + + if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action signature") { + t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) + } + + if exitErr.ExitCode() != 2 { + t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) + } +} From c3a637061608c0680770d6465c6bf1118a331322 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 11:07:53 -0400 Subject: [PATCH 131/381] Moving remaining details from #361 description to CHANGELOG.md --- CHANGELOG.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b03b52c..4830a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,17 +6,28 @@ ### Added - Support for placeholders in flag usage strings -- Support for custom exit code by returning an error that fulfills -`cli.ExitCoder` to `cli.App.Run`. + +### Changed +- The `App.Action` and `Command.Action` now prefer a return signature of +`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil +`error` is returned, there may be two outcomes: + - If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called + automatically + - Else the error is bubbled up and returned from `App.Run` +- Specifying an `Action` with the legacy return signature of +`func(*cli.Context)` will produce a deprecation message to stderr +- Specifying an `Action` that is not a `func` type will produce a non-zero exit +from `App.Run` +- Specifying an `Action` func that has an invalid (input) signature will +produce a non-zero exit from `App.Run` ### Deprecated - `cli.App.RunAndExitOnError`, which should now be done by returning an error that fulfills `cli.ExitCoder` to `cli.App.Run`. -- -the legacy signature for `cli.App.Action` of `func(*cli.Context)`, which should -now have a signature of `func(*cli.Context) error`, as defined by -`cli.ActionFunc`. +- the legacy signature for +`cli.App.Action` of `func(*cli.Context)`, which should now have a signature of +`func(*cli.Context) error`, as defined by `cli.ActionFunc`. ## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) ### Added From 7651aa05a63f72d59f84211ce7382aad3cdb7139 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 11:09:33 -0400 Subject: [PATCH 132/381] TRIVIAL specify *return* signature in deprecation message --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4830a80..1bf2212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,8 @@ produce a non-zero exit from `App.Run` `cli.App.RunAndExitOnError`, which should now be done by returning an error that fulfills `cli.ExitCoder` to `cli.App.Run`. - the legacy signature for -`cli.App.Action` of `func(*cli.Context)`, which should now have a signature of -`func(*cli.Context) error`, as defined by `cli.ActionFunc`. +`cli.App.Action` of `func(*cli.Context)`, which should now have a return +signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) ### Added From bd0f13d022deb9ba6af02b7e2181cc319787b56a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 13:03:25 -0400 Subject: [PATCH 133/381] Add Context.GlobalFloat64 plus tests Closes #362 --- context.go | 9 +++++++++ context_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/context.go b/context.go index b66d278..c542a67 100644 --- a/context.go +++ b/context.go @@ -79,6 +79,15 @@ func (c *Context) GlobalInt(name string) int { return 0 } +// Looks up the value of a global float64 flag, returns float64(0) if no float64 +// flag exists +func (c *Context) GlobalFloat64(name string) float64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupFloat64(name, fs) + } + return float64(0) +} + // 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 { if fs := lookupGlobalFlagSet(name, c); fs != nil { diff --git a/context_test.go b/context_test.go index b8ab37d..e821235 100644 --- a/context_test.go +++ b/context_test.go @@ -9,14 +9,18 @@ import ( func TestNewContext(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") + set.Float64("myflag64", float64(17), "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Int("myflag", 42, "doc") + globalSet.Float64("myflag64", float64(47), "doc") globalCtx := NewContext(nil, globalSet, nil) command := Command{Name: "mycommand"} c := NewContext(nil, set, globalCtx) c.Command = command expect(t, c.Int("myflag"), 12) + expect(t, c.Float64("myflag64"), float64(17)) expect(t, c.GlobalInt("myflag"), 42) + expect(t, c.GlobalFloat64("myflag64"), float64(47)) expect(t, c.Command.Name, "mycommand") } @@ -27,6 +31,29 @@ func TestContext_Int(t *testing.T) { expect(t, c.Int("myflag"), 12) } +func TestContext_GlobalInt(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.GlobalInt("myflag"), 12) + expect(t, c.GlobalInt("nope"), 0) +} + +func TestContext_Float64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Float64("myflag", float64(17), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Float64("myflag"), float64(17)) +} + +func TestContext_GlobalFloat64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Float64("myflag", float64(17), "doc") + c := NewContext(nil, set, nil) + expect(t, c.GlobalFloat64("myflag"), float64(17)) + expect(t, c.GlobalFloat64("nope"), float64(0)) +} + func TestContext_Duration(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Duration("myflag", time.Duration(12*time.Second), "doc") From b9bbb35c5beba31c0ce7836d754f16aa06673b75 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 13:04:52 -0400 Subject: [PATCH 134/381] Add change to Fixed section of Unreleased in CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074bfb2..300467b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ ### Added - Support for placeholders in flag usage strings +### Fixed +- Added missing `*cli.Context.GlobalFloat64` method + ## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) ### Added - Codebeat badge From 2c0e13ecf812f302a2459b0c2a2b1693c23820b4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 16:05:14 -0400 Subject: [PATCH 135/381] Revising/adding tests that assert order of operations inside App.Run --- app_test.go | 215 ++++++++++++++++++++++++++++++++++-------------- context_test.go | 54 ++++++++++++ helpers_test.go | 13 ++- 3 files changed, 220 insertions(+), 62 deletions(-) diff --git a/app_test.go b/app_test.go index ebf26c7..417bf98 100644 --- a/app_test.go +++ b/app_test.go @@ -13,6 +13,10 @@ import ( "testing" ) +type opCounts struct { + Total, BashComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int +} + func ExampleApp_Run() { // set args for examples sake os.Args = []string{"greet", "--name", "Jeremy"} @@ -439,14 +443,15 @@ func TestApp_SetStdout(t *testing.T) { } func TestApp_BeforeFunc(t *testing.T) { - beforeRun, subcommandRun := false, false + counts := &opCounts{} beforeError := fmt.Errorf("fail") var err error app := NewApp() app.Before = func(c *Context) error { - beforeRun = true + counts.Total++ + counts.Before = counts.Total s := c.String("opt") if s == "fail" { return beforeError @@ -459,7 +464,8 @@ func TestApp_BeforeFunc(t *testing.T) { Command{ Name: "sub", Action: func(c *Context) { - subcommandRun = true + counts.Total++ + counts.SubCommand = counts.Total }, }, } @@ -475,16 +481,16 @@ func TestApp_BeforeFunc(t *testing.T) { t.Fatalf("Run error: %s", err) } - if beforeRun == false { + if counts.Before != 1 { t.Errorf("Before() not executed when expected") } - if subcommandRun == false { + if counts.SubCommand != 2 { t.Errorf("Subcommand not executed when expected") } // reset - beforeRun, subcommandRun = false, false + counts = &opCounts{} // run with the Before() func failing err = app.Run([]string{"command", "--opt", "fail", "sub"}) @@ -494,25 +500,26 @@ func TestApp_BeforeFunc(t *testing.T) { t.Errorf("Run error expected, but not received") } - if beforeRun == false { + if counts.Before != 1 { t.Errorf("Before() not executed when expected") } - if subcommandRun == true { + if counts.SubCommand != 0 { t.Errorf("Subcommand executed when NOT expected") } } func TestApp_AfterFunc(t *testing.T) { - afterRun, subcommandRun := false, false + counts := &opCounts{} afterError := fmt.Errorf("fail") var err error app := NewApp() app.After = func(c *Context) error { - afterRun = true + counts.Total++ + counts.After = counts.Total s := c.String("opt") if s == "fail" { return afterError @@ -525,7 +532,8 @@ func TestApp_AfterFunc(t *testing.T) { Command{ Name: "sub", Action: func(c *Context) { - subcommandRun = true + counts.Total++ + counts.SubCommand = counts.Total }, }, } @@ -541,16 +549,16 @@ func TestApp_AfterFunc(t *testing.T) { t.Fatalf("Run error: %s", err) } - if afterRun == false { + if counts.After != 2 { t.Errorf("After() not executed when expected") } - if subcommandRun == false { + if counts.SubCommand != 1 { t.Errorf("Subcommand not executed when expected") } // reset - afterRun, subcommandRun = false, false + counts = &opCounts{} // run with the Before() func failing err = app.Run([]string{"command", "--opt", "fail", "sub"}) @@ -560,11 +568,11 @@ func TestApp_AfterFunc(t *testing.T) { t.Errorf("Run error expected, but not received") } - if afterRun == false { + if counts.After != 2 { t.Errorf("After() not executed when expected") } - if subcommandRun == false { + if counts.SubCommand != 1 { t.Errorf("Subcommand not executed when expected") } } @@ -605,7 +613,7 @@ func TestAppHelpPrinter(t *testing.T) { } } -func TestAppVersionPrinter(t *testing.T) { +func TestApp_VersionPrinter(t *testing.T) { oldPrinter := VersionPrinter defer func() { VersionPrinter = oldPrinter @@ -625,81 +633,168 @@ func TestAppVersionPrinter(t *testing.T) { } } -func TestAppCommandNotFound(t *testing.T) { - beforeRun, subcommandRun := false, false +func TestApp_CommandNotFound(t *testing.T) { + counts := &opCounts{} app := NewApp() app.CommandNotFound = func(c *Context, command string) { - beforeRun = true + counts.Total++ + counts.CommandNotFound = counts.Total } app.Commands = []Command{ Command{ Name: "bar", Action: func(c *Context) { - subcommandRun = true + counts.Total++ + counts.SubCommand = counts.Total }, }, } app.Run([]string{"command", "foo"}) - expect(t, beforeRun, true) - expect(t, subcommandRun, false) + expect(t, counts.CommandNotFound, 1) + expect(t, counts.SubCommand, 0) + expect(t, counts.Total, 1) } -func TestGlobalFlag(t *testing.T) { - var globalFlag string - var globalFlagSet bool +func TestApp_OrderOfOperations(t *testing.T) { + counts := &opCounts{} + + resetCounts := func() { counts = &opCounts{} } + app := NewApp() - app.Flags = []Flag{ - StringFlag{Name: "global, g", Usage: "global"}, + app.EnableBashCompletion = true + app.BashComplete = func(c *Context) { + counts.Total++ + counts.BashComplete = counts.Total } - app.Action = func(c *Context) { - globalFlag = c.GlobalString("global") - globalFlagSet = c.GlobalIsSet("global") + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + counts.Total++ + counts.OnUsageError = counts.Total + return errors.New("hay OnUsageError") } - app.Run([]string{"command", "-g", "foo"}) - expect(t, globalFlag, "foo") - expect(t, globalFlagSet, true) -} + beforeNoError := func(c *Context) error { + counts.Total++ + counts.Before = counts.Total + return nil + } -func TestGlobalFlagsInSubcommands(t *testing.T) { - subcommandRun := false - parentFlag := false - app := NewApp() + beforeError := func(c *Context) error { + counts.Total++ + counts.Before = counts.Total + return errors.New("hay Before") + } - app.Flags = []Flag{ - BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, + app.Before = beforeNoError + app.CommandNotFound = func(c *Context, command string) { + counts.Total++ + counts.CommandNotFound = counts.Total + } + + afterNoError := func(c *Context) error { + counts.Total++ + counts.After = counts.Total + return nil } + afterError := func(c *Context) error { + counts.Total++ + counts.After = counts.Total + return errors.New("hay After") + } + + app.After = afterNoError app.Commands = []Command{ Command{ - Name: "foo", - Flags: []Flag{ - BoolFlag{Name: "parent, p", Usage: "Parent flag"}, - }, - Subcommands: []Command{ - { - Name: "bar", - Action: func(c *Context) { - if c.GlobalBool("debug") { - subcommandRun = true - } - if c.GlobalBool("parent") { - parentFlag = true - } - }, - }, + Name: "bar", + Action: func(c *Context) { + counts.Total++ + counts.SubCommand = counts.Total }, }, } - app.Run([]string{"command", "-d", "foo", "-p", "bar"}) + app.Action = func(c *Context) { + counts.Total++ + counts.Action = counts.Total + } + + _ = app.Run([]string{"command", "--nope"}) + expect(t, counts.OnUsageError, 1) + expect(t, counts.Total, 1) + + resetCounts() - expect(t, subcommandRun, true) - expect(t, parentFlag, true) + _ = app.Run([]string{"command", "--generate-bash-completion"}) + expect(t, counts.BashComplete, 1) + expect(t, counts.Total, 1) + + resetCounts() + + oldOnUsageError := app.OnUsageError + app.OnUsageError = nil + _ = app.Run([]string{"command", "--nope"}) + expect(t, counts.Total, 0) + app.OnUsageError = oldOnUsageError + + resetCounts() + + _ = app.Run([]string{"command", "foo"}) + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.CommandNotFound, 0) + expect(t, counts.Action, 2) + expect(t, counts.After, 3) + expect(t, counts.Total, 3) + + resetCounts() + + app.Before = beforeError + _ = app.Run([]string{"command", "bar"}) + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.After, 2) + expect(t, counts.Total, 2) + app.Before = beforeNoError + + resetCounts() + + app.After = nil + _ = app.Run([]string{"command", "bar"}) + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.SubCommand, 2) + expect(t, counts.Total, 2) + app.After = afterNoError + + resetCounts() + + app.After = afterError + err := app.Run([]string{"command", "bar"}) + if err == nil { + t.Fatalf("expected a non-nil error") + } + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.SubCommand, 2) + expect(t, counts.After, 3) + expect(t, counts.Total, 3) + app.After = afterNoError + + resetCounts() + + oldCommands := app.Commands + app.Commands = nil + _ = app.Run([]string{"command"}) + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.Action, 2) + expect(t, counts.After, 3) + expect(t, counts.Total, 3) + app.Commands = oldCommands } func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { diff --git a/context_test.go b/context_test.go index e821235..20647b8 100644 --- a/context_test.go +++ b/context_test.go @@ -146,3 +146,57 @@ func TestContext_NumFlags(t *testing.T) { globalSet.Parse([]string{"--myflagGlobal"}) expect(t, c.NumFlags(), 2) } + +func TestContext_GlobalFlag(t *testing.T) { + var globalFlag string + var globalFlagSet bool + app := NewApp() + app.Flags = []Flag{ + StringFlag{Name: "global, g", Usage: "global"}, + } + app.Action = func(c *Context) { + globalFlag = c.GlobalString("global") + globalFlagSet = c.GlobalIsSet("global") + } + app.Run([]string{"command", "-g", "foo"}) + expect(t, globalFlag, "foo") + expect(t, globalFlagSet, true) + +} + +func TestContext_GlobalFlagsInSubcommands(t *testing.T) { + subcommandRun := false + parentFlag := false + app := NewApp() + + app.Flags = []Flag{ + BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, + } + + app.Commands = []Command{ + Command{ + Name: "foo", + Flags: []Flag{ + BoolFlag{Name: "parent, p", Usage: "Parent flag"}, + }, + Subcommands: []Command{ + { + Name: "bar", + Action: func(c *Context) { + if c.GlobalBool("debug") { + subcommandRun = true + } + if c.GlobalBool("parent") { + parentFlag = true + } + }, + }, + }, + }, + } + + app.Run([]string{"command", "-d", "foo", "-p", "bar"}) + + expect(t, subcommandRun, true) + expect(t, parentFlag, true) +} diff --git a/helpers_test.go b/helpers_test.go index b1b7339..109ea7a 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -1,14 +1,23 @@ package cli import ( + "os" "reflect" + "runtime" + "strings" "testing" ) -/* Test Helpers */ +var ( + wd, _ = os.Getwd() +) + func expect(t *testing.T, a interface{}, b interface{}) { + _, fn, line, _ := runtime.Caller(1) + fn = strings.Replace(fn, wd+"/", "", -1) + if !reflect.DeepEqual(a, b) { - t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } From 4cae17cfe1ef51b3c2abaab464de3b08f6122855 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 17:15:16 -0400 Subject: [PATCH 136/381] Ensure MultiError returned when both Before and After funcs given --- app.go | 18 ++++++++++-------- app_test.go | 27 ++++++++++++++++++++++++++- context_test.go | 3 ++- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index 0e78695..2bdbf4c 100644 --- a/app.go +++ b/app.go @@ -196,11 +196,12 @@ func (a *App) Run(arguments []string) (err error) { } if a.Before != nil { - err = a.Before(context) - if err != nil { - fmt.Fprintf(a.Writer, "%v\n\n", err) + beforeErr := a.Before(context) + if beforeErr != nil { + fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) ShowAppHelp(context) - HandleExitCoder(err) + HandleExitCoder(beforeErr) + err = beforeErr return err } } @@ -286,7 +287,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) HandleExitCoder(err) - return nil + return err } else { fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") ShowSubcommandHelp(context) @@ -319,9 +320,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if a.Before != nil { - err = a.Before(context) - if err != nil { - HandleExitCoder(err) + beforeErr := a.Before(context) + if beforeErr != nil { + HandleExitCoder(beforeErr) + err = beforeErr return err } } diff --git a/app_test.go b/app_test.go index a176de3..bf2887e 100644 --- a/app_test.go +++ b/app_test.go @@ -522,6 +522,30 @@ func TestApp_BeforeFunc(t *testing.T) { if counts.SubCommand != 0 { t.Errorf("Subcommand executed when NOT expected") } + + // reset + counts = &opCounts{} + + afterError := errors.New("fail again") + app.After = func(_ *Context) error { + return afterError + } + + // run with the Before() func failing, wrapped by After() + err = app.Run([]string{"command", "--opt", "fail", "sub"}) + + // should be the same error produced by the Before func + if _, ok := err.(MultiError); !ok { + t.Errorf("MultiError expected, but not received") + } + + if counts.Before != 1 { + t.Errorf("Before() not executed when expected") + } + + if counts.SubCommand != 0 { + t.Errorf("Subcommand executed when NOT expected") + } } func TestApp_AfterFunc(t *testing.T) { @@ -735,9 +759,10 @@ func TestApp_OrderOfOperations(t *testing.T) { }, } - app.Action = func(c *Context) { + app.Action = func(c *Context) error { counts.Total++ counts.Action = counts.Total + return nil } _ = app.Run([]string{"command", "--nope"}) diff --git a/context_test.go b/context_test.go index 836306c..cbad304 100644 --- a/context_test.go +++ b/context_test.go @@ -154,9 +154,10 @@ func TestContext_GlobalFlag(t *testing.T) { app.Flags = []Flag{ StringFlag{Name: "global, g", Usage: "global"}, } - app.Action = func(c *Context) { + app.Action = func(c *Context) error { globalFlag = c.GlobalString("global") globalFlagSet = c.GlobalIsSet("global") + return nil } app.Run([]string{"command", "-g", "foo"}) expect(t, globalFlag, "foo") From 06c01a4bba6e9fd20b999096951b83469586d0c4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 29 Apr 2016 03:01:57 -0400 Subject: [PATCH 137/381] A few tweaks based on feedback in #361 --- app.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 2bdbf4c..4097a58 100644 --- a/app.go +++ b/app.go @@ -14,11 +14,13 @@ import ( var ( appActionDeprecationURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-action-signature" + contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." + errNonFuncAction = NewExitError("ERROR invalid Action type. "+ - "Must be a func of type `cli.ActionFunc`. "+ + fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+ fmt.Sprintf("See %s", appActionDeprecationURL), 2) errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+ - "Must be `cli.ActionFunc`. "+ + fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+ fmt.Sprintf("See %s", appActionDeprecationURL), 2) ) @@ -419,7 +421,7 @@ func HandleAction(action interface{}, context *Context) (err error) { if len(vals) == 0 { fmt.Fprintln(os.Stderr, "DEPRECATED Action signature. Must be `cli.ActionFunc`") - return err + return nil } if len(vals) > 1 { From 36a5323a47c4c22c23f035cf8aa1eadd48c56715 Mon Sep 17 00:00:00 2001 From: rob boll Date: Fri, 29 Apr 2016 12:33:59 -0400 Subject: [PATCH 138/381] altsrc: allow nested defaults in yaml files Previously, defaults specified as nested keys in a yaml file would not be recognized, i.e. `top: \n bottom: key` would not be accessible using the name `top.bottom`, but `top.bottom: key` would. These changes support using nested keys by traversing the configuration tree if the key name uses '.' as a delimiter. --- altsrc/flag_test.go | 2 +- altsrc/map_input_source.go | 100 ++++++++++++++++++++++++++- altsrc/yaml_command_test.go | 132 ++++++++++++++++++++++++++++++++++++ altsrc/yaml_file_loader.go | 2 +- 4 files changed, 232 insertions(+), 4 deletions(-) diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index ac4d1f5..4e25be6 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -296,7 +296,7 @@ func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { } func runTest(t *testing.T, test testApplyInputSource) *cli.Context { - inputSource := &MapInputSource{valueMap: map[string]interface{}{test.FlagName: test.MapValue}} + inputSource := &MapInputSource{valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}} set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError) c := cli.NewContext(nil, set, nil) if test.EnvVarName != "" && test.EnvVarValue != "" { diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index f1670fb..19f87af 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -3,6 +3,7 @@ package altsrc import ( "fmt" "reflect" + "strings" "time" "github.com/codegangsta/cli" @@ -11,7 +12,31 @@ import ( // MapInputSource implements InputSourceContext to return // data from the map that is loaded. type MapInputSource struct { - valueMap map[string]interface{} + valueMap map[interface{}]interface{} +} + +// nestedVal checks if the name has '.' delimiters. +// If so, it tries to traverse the tree by the '.' delimited sections to find +// a nested value for the key. +func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool) { + if sections := strings.Split(name, "."); len(sections) > 1 { + node := tree + for _, section := range sections[:len(sections)-1] { + if child, ok := node[section]; !ok { + return nil, false + } else { + if ctype, ok := child.(map[interface{}]interface{}); !ok { + return nil, false + } else { + node = ctype + } + } + } + if val, ok := node[sections[len(sections)-1]]; ok { + return val, true + } + } + return nil, false } // Int returns an int from the map if it exists otherwise returns 0 @@ -22,7 +47,14 @@ func (fsm *MapInputSource) Int(name string) (int, error) { if !isType { return 0, incorrectTypeForFlagError(name, "int", otherGenericValue) } - + return otherValue, nil + } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(int) + if !isType { + return 0, incorrectTypeForFlagError(name, "int", nestedGenericValue) + } return otherValue, nil } @@ -39,6 +71,14 @@ func (fsm *MapInputSource) Duration(name string) (time.Duration, error) { } return otherValue, nil } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(time.Duration) + if !isType { + return 0, incorrectTypeForFlagError(name, "duration", nestedGenericValue) + } + return otherValue, nil + } return 0, nil } @@ -53,6 +93,14 @@ func (fsm *MapInputSource) Float64(name string) (float64, error) { } return otherValue, nil } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(float64) + if !isType { + return 0, incorrectTypeForFlagError(name, "float64", nestedGenericValue) + } + return otherValue, nil + } return 0, nil } @@ -67,6 +115,14 @@ func (fsm *MapInputSource) String(name string) (string, error) { } return otherValue, nil } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(string) + if !isType { + return "", incorrectTypeForFlagError(name, "string", nestedGenericValue) + } + return otherValue, nil + } return "", nil } @@ -81,6 +137,14 @@ func (fsm *MapInputSource) StringSlice(name string) ([]string, error) { } return otherValue, nil } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.([]string) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue) + } + return otherValue, nil + } return nil, nil } @@ -95,6 +159,14 @@ func (fsm *MapInputSource) IntSlice(name string) ([]int, error) { } return otherValue, nil } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.([]int) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue) + } + return otherValue, nil + } return nil, nil } @@ -109,6 +181,14 @@ func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) { } return otherValue, nil } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(cli.Generic) + if !isType { + return nil, incorrectTypeForFlagError(name, "cli.Generic", nestedGenericValue) + } + return otherValue, nil + } return nil, nil } @@ -123,6 +203,14 @@ func (fsm *MapInputSource) Bool(name string) (bool, error) { } return otherValue, nil } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(bool) + if !isType { + return false, incorrectTypeForFlagError(name, "bool", nestedGenericValue) + } + return otherValue, nil + } return false, nil } @@ -137,6 +225,14 @@ func (fsm *MapInputSource) BoolT(name string) (bool, error) { } return otherValue, nil } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(bool) + if !isType { + return true, incorrectTypeForFlagError(name, "bool", nestedGenericValue) + } + return otherValue, nil + } return true, nil } diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 275bc64..29ead8d 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -76,6 +76,40 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { expect(t, err, nil) } +func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte(`top: + test: 15`), 0666) + defer os.Remove("current.yaml") + + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("top.test") + expect(t, val, 10) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { app := cli.NewApp() set := flag.NewFlagSet("test", 0) @@ -107,6 +141,38 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { expect(t, err, nil) } +func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte(`top: + test: 15`), 0666) + defer os.Remove("current.yaml") + + test := []string{"test-cmd", "--load", "current.yaml", "--top.test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("top.test") + expect(t, val, 7) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { app := cli.NewApp() set := flag.NewFlagSet("test", 0) @@ -138,6 +204,38 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { expect(t, err, nil) } +func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte(`top: + test: 15`), 0666) + defer os.Remove("current.yaml") + + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("top.test") + expect(t, val, 15) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) { app := cli.NewApp() set := flag.NewFlagSet("test", 0) @@ -170,3 +268,37 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T expect(t, err, nil) } + +func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte(`top: + test: 15`), 0666) + defer os.Remove("current.yaml") + + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + val := c.Int("top.test") + expect(t, val, 11) + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 4fb0965..01797ad 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -24,7 +24,7 @@ type yamlSourceContext struct { // NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath. func NewYamlSourceFromFile(file string) (InputSourceContext, error) { ysc := &yamlSourceContext{FilePath: file} - var results map[string]interface{} + var results map[interface{}]interface{} err := readCommandYaml(ysc.FilePath, &results) if err != nil { return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error()) From f72d40510730a83b18d501db0b6e2ec289d5529d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=8C=AF=E5=A8=81?= Date: Sat, 30 Apr 2016 10:42:07 +0800 Subject: [PATCH 139/381] Change Extras to Metadata --- app.go | 2 +- command.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 503f838..93bbb04 100644 --- a/app.go +++ b/app.go @@ -63,7 +63,7 @@ type App struct { // Writer writer to write output to Writer io.Writer // Other custom info - Extras map[string]interface{} + Metadata map[string]interface{} } // Tries to find out when this binary was compiled. diff --git a/command.go b/command.go index 0ccffa2..9e713df 100644 --- a/command.go +++ b/command.go @@ -197,7 +197,7 @@ func (c Command) HasName(name string) bool { func (c Command) startApp(ctx *Context) error { app := NewApp() - app.Extras = ctx.App.Extras + app.Metadata = ctx.App.Metadata // set the name and usage app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) if c.HelpName == "" { From e2161d7f64270bd1ce2bfc7b6fabdfd41fd542e8 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2016 09:54:44 -0400 Subject: [PATCH 140/381] Minor changelog update bits --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ade961f..434c88c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ ## [Unreleased] ### Added +- This file! - Support for placeholders in flag usage strings +- `App.Metadata` map for arbitrary data/state management ### Changed - The `App.Action` and `Command.Action` now prefer a return signature of From e059dc81880375ca5efca91549d2361a87df35ae Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2016 11:46:47 -0400 Subject: [PATCH 141/381] Implement *Context.GlobalSet + relevant CHANGELOG entry --- CHANGELOG.md | 2 ++ context.go | 20 ++++++++++++++++++++ context_test.go | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 434c88c..a4d88ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - This file! - Support for placeholders in flag usage strings - `App.Metadata` map for arbitrary data/state management +- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after +parsing. ### Changed - The `App.Action` and `Command.Action` now prefer a return signature of diff --git a/context.go b/context.go index 93c000c..45013ac 100644 --- a/context.go +++ b/context.go @@ -146,6 +146,11 @@ func (c *Context) Set(name, value string) error { return c.flagSet.Set(name, value) } +// GlobalSet sets a context flag to a value on the global flagset +func (c *Context) GlobalSet(name, value string) error { + return globalContext(c).flagSet.Set(name, value) +} + // Determines if the flag was actually set func (c *Context) IsSet(name string) bool { if c.setFlags == nil { @@ -252,6 +257,21 @@ func (a Args) Swap(from, to int) error { return nil } +func globalContext(ctx *Context) *Context { + if ctx == nil { + return nil + } + + for { + if ctx.parentContext == nil { + return ctx + } + ctx = ctx.parentContext + } + + return nil +} + func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { if ctx.parentContext != nil { ctx = ctx.parentContext diff --git a/context_test.go b/context_test.go index 20970b2..4c23271 100644 --- a/context_test.go +++ b/context_test.go @@ -211,3 +211,22 @@ func TestContext_Set(t *testing.T) { c.Set("int", "1") expect(t, c.Int("int"), 1) } + +func TestContext_GlobalSet(t *testing.T) { + gSet := flag.NewFlagSet("test", 0) + gSet.Int("int", 5, "an int") + + set := flag.NewFlagSet("sub", 0) + set.Int("int", 3, "an int") + + pc := NewContext(nil, gSet, nil) + c := NewContext(nil, set, pc) + + c.Set("int", "1") + expect(t, c.Int("int"), 1) + expect(t, c.GlobalInt("int"), 5) + + c.GlobalSet("int", "1") + expect(t, c.Int("int"), 1) + expect(t, c.GlobalInt("int"), 1) +} From f3b589e89239925ddf57fda31d049dd31ce3f495 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2016 12:22:32 -0400 Subject: [PATCH 142/381] Remove unreachable code --- context.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/context.go b/context.go index 45013ac..ef3d2fc 100644 --- a/context.go +++ b/context.go @@ -268,8 +268,6 @@ func globalContext(ctx *Context) *Context { } ctx = ctx.parentContext } - - return nil } func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { From 896d2fd3c174b3bbd2c1f8587b98ef84d7cb8a29 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2016 13:27:11 -0400 Subject: [PATCH 143/381] Add a note to CHANGELOG about dot-delimited YAML key lookup --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4d88ae..50f4140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - `App.Metadata` map for arbitrary data/state management - `Set` and `GlobalSet` methods on `*cli.Context` for altering values after parsing. +- Support for nested lookup of dot-delimited keys in structures loaded from +YAML. ### Changed - The `App.Action` and `Command.Action` now prefer a return signature of From a9afed5b1522421b2d5f0c93b4b1d13349fd095d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2016 13:48:08 -0400 Subject: [PATCH 144/381] Prepping v1.15.0 release --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50f4140..5760983 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## [Unreleased] +## [1.15.0] - 2016-04-30 ### Added - This file! - Support for placeholders in flag usage strings @@ -250,7 +251,8 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/codegangsta/cli/compare/v1.14.0...HEAD +[Unreleased]: https://github.com/codegangsta/cli/compare/v1.15.0...HEAD +[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0 [1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 [1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 [1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0 From 99431669d05b813fd8ec3d922c0b440cf9c36705 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Sun, 15 Mar 2015 03:11:52 -0700 Subject: [PATCH 145/381] New ``Hide`` variable for all Flags This is a way to provide hidden flags for app, command and subcommands For example: --generate-bash-completion global flag shouldn't be printed along with other flags as it might generally confuse people into thinking that 'generate' in-fact would generate a bash completion file for them to be used along with their app. Also in general one would want to hide some flags for their apps. --- flag.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ help.go | 30 +++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/flag.go b/flag.go index 42f6a61..d18df0a 100644 --- a/flag.go +++ b/flag.go @@ -13,6 +13,7 @@ import ( // This flag enables bash-completion for all commands and subcommands var BashCompletionFlag = BoolFlag{ Name: "generate-bash-completion", + Hide: true, } // This flag prints the version for the application @@ -37,6 +38,7 @@ type Flag interface { // Apply Flag settings to the given flag set Apply(*flag.FlagSet) GetName() string + isNotHidden() bool } func flagSet(name string, flags []Flag) *flag.FlagSet { @@ -68,6 +70,7 @@ type GenericFlag struct { Value Generic Usage string EnvVar string + Hide bool } // String returns the string representation of the generic flag to display the @@ -112,6 +115,10 @@ func (f GenericFlag) GetName() string { return f.Name } +func (f GenericFlag) isNotHidden() bool { + return !f.Hide +} + // StringSlice is an opaque type for []string to satisfy flag.Value type StringSlice []string @@ -138,6 +145,7 @@ type StringSliceFlag struct { Value *StringSlice Usage string EnvVar string + Hide bool } // String returns the usage @@ -177,6 +185,10 @@ func (f StringSliceFlag) GetName() string { return f.Name } +func (f StringSliceFlag) isNotHidden() bool { + return !f.Hide +} + // StringSlice is an opaque type for []int to satisfy flag.Value type IntSlice []int @@ -208,6 +220,7 @@ type IntSliceFlag struct { Value *IntSlice Usage string EnvVar string + Hide bool } // String returns the usage @@ -250,12 +263,17 @@ func (f IntSliceFlag) GetName() string { return f.Name } +func (f IntSliceFlag) isNotHidden() bool { + return !f.Hide +} + // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string Usage string EnvVar string Destination *bool + Hide bool } // String returns a readable representation of this value (for usage defaults) @@ -293,6 +311,10 @@ func (f BoolFlag) GetName() string { return f.Name } +func (f BoolFlag) isNotHidden() bool { + return !f.Hide +} + // BoolTFlag this represents a boolean flag that is true by default, but can // still be set to false by --some-flag=false type BoolTFlag struct { @@ -300,6 +322,7 @@ type BoolTFlag struct { Usage string EnvVar string Destination *bool + Hide bool } // String returns a readable representation of this value (for usage defaults) @@ -337,6 +360,10 @@ func (f BoolTFlag) GetName() string { return f.Name } +func (f BoolTFlag) isNotHidden() bool { + return !f.Hide +} + // StringFlag represents a flag that takes as string value type StringFlag struct { Name string @@ -344,6 +371,7 @@ type StringFlag struct { Usage string EnvVar string Destination *string + Hide bool } // String returns the usage @@ -385,6 +413,10 @@ func (f StringFlag) GetName() string { return f.Name } +func (f StringFlag) isNotHidden() bool { + return !f.Hide +} + // IntFlag is a flag that takes an integer // Errors if the value provided cannot be parsed type IntFlag struct { @@ -393,6 +425,7 @@ type IntFlag struct { Usage string EnvVar string Destination *int + Hide bool } // String returns the usage @@ -429,6 +462,10 @@ func (f IntFlag) GetName() string { return f.Name } +func (f IntFlag) isNotHidden() bool { + return !f.Hide +} + // DurationFlag is a flag that takes a duration specified in Go's duration // format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { @@ -437,6 +474,7 @@ type DurationFlag struct { Usage string EnvVar string Destination *time.Duration + Hide bool } // String returns a readable representation of this value (for usage defaults) @@ -473,6 +511,10 @@ func (f DurationFlag) GetName() string { return f.Name } +func (f DurationFlag) isNotHidden() bool { + return !f.Hide +} + // Float64Flag is a flag that takes an float value // Errors if the value provided cannot be parsed type Float64Flag struct { @@ -481,6 +523,7 @@ type Float64Flag struct { Usage string EnvVar string Destination *float64 + Hide bool } // String returns the usage @@ -516,6 +559,10 @@ func (f Float64Flag) GetName() string { return f.Name } +func (f Float64Flag) isNotHidden() bool { + return !f.Hide +} + func prefixFor(name string) (prefix string) { if len(name) == 1 { prefix = "-" diff --git a/help.go b/help.go index a895e6c..507fbe5 100644 --- a/help.go +++ b/help.go @@ -114,7 +114,15 @@ var HelpPrinter helpPrinter = printHelp var VersionPrinter = printVersion func ShowAppHelp(c *Context) { - HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) + // Make a copy of c.App context + app := *c.App + app.Flags = make([]Flag, 0) + for _, flag := range c.App.Flags { + if flag.isNotHidden() { + app.Flags = append(app.Flags, flag) + } + } + HelpPrinter(c.App.Writer, AppHelpTemplate, app) } // Prints the list of subcommands as the default app completion method @@ -130,13 +138,29 @@ func DefaultAppComplete(c *Context) { func ShowCommandHelp(ctx *Context, command string) { // show the subcommand help for a command with subcommands if command == "" { - HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) + // Make a copy of c.App context + app := *c.App + app.Flags = make([]Flag, 0) + for _, flag := range c.App.Flags { + if flag.isNotHidden() { + app.Flags = append(app.Flags, flag) + } + } + HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, app) return } for _, c := range ctx.App.Commands { if c.HasName(command) { - HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) + // Make a copy of command context + c0 := c + c0.Flags = make([]Flag, 0) + for _, flag := range c.Flags { + if flag.isNotHidden() { + c0.Flags = append(c0.Flags, flag) + } + } + HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c0) return } } From fed78b8bab633ee554cfa229e9975ae731d40f5d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 1 May 2016 08:36:17 -0400 Subject: [PATCH 146/381] Rework of hidden flag impl plus some Action func corrections --- README.md | 16 ++++++--- altsrc/yaml_command_test.go | 12 ++++--- app.go | 5 +++ cli.go | 2 +- command.go | 5 +++ flag.go | 66 +++++++++++-------------------------- help.go | 48 +++++++-------------------- 7 files changed, 62 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 777b0a8..5db1b6f 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,7 @@ Here is a more complete sample of a command using YAML support: Description: "testing", Action: func(c *cli.Context) error { // Action to run + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "test"}), @@ -322,16 +323,18 @@ app.Commands = []cli.Command{ Name: "add", Aliases: []string{"a"}, Usage: "add a task to the list", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Println("added task: ", c.Args().First()) + return nil }, }, { Name: "complete", Aliases: []string{"c"}, Usage: "complete a task on the list", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Println("completed task: ", c.Args().First()) + return nil }, }, { @@ -342,15 +345,17 @@ app.Commands = []cli.Command{ { Name: "add", Usage: "add a new template", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Println("new task template: ", c.Args().First()) + return nil }, }, { Name: "remove", Usage: "remove an existing template", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Println("removed task template: ", c.Args().First()) + return nil }, }, }, @@ -450,8 +455,9 @@ app.Commands = []cli.Command{ Name: "complete", Aliases: []string{"c"}, Usage: "complete a task on the list", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { fmt.Println("completed task: ", c.Args().First()) + return nil }, BashComplete: func(c *cli.Context) { // This will complete if no args are passed diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 90d7823..519bd81 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -97,9 +97,10 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { val := c.Int("top.test") expect(t, val, 10) + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), @@ -161,9 +162,10 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { val := c.Int("top.test") expect(t, val, 7) + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "top.test"}), @@ -225,9 +227,10 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { val := c.Int("top.test") expect(t, val, 15) + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), @@ -294,9 +297,10 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *tes Aliases: []string{"tc"}, Usage: "this is for testing", Description: "testing", - Action: func(c *cli.Context) { + Action: func(c *cli.Context) error { val := c.Int("top.test") expect(t, val, 11) + return nil }, Flags: []cli.Flag{ NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), diff --git a/app.go b/app.go index 6917fbc..2b7c9c6 100644 --- a/app.go +++ b/app.go @@ -366,6 +366,11 @@ func (a *App) Categories() CommandCategories { return a.categories } +// VisibleFlags returns a slice of the Flags with Hidden=false +func (a *App) VisibleFlags() []Flag { + return visibleFlags(a.Flags) +} + func (a *App) hasFlag(flag Flag) bool { for _, f := range a.Flags { if flag == f { diff --git a/cli.go b/cli.go index b742545..f0440c5 100644 --- a/cli.go +++ b/cli.go @@ -10,7 +10,7 @@ // app := cli.NewApp() // app.Name = "greet" // app.Usage = "say a greeting" -// app.Action = func(c *cli.Context) { +// app.Action = func(c *cli.Context) error { // println("Greetings") // } // diff --git a/command.go b/command.go index 7f30932..9ca7e51 100644 --- a/command.go +++ b/command.go @@ -269,3 +269,8 @@ func (c Command) startApp(ctx *Context) error { return app.RunAsSubcommand(ctx) } + +// VisibleFlags returns a slice of the Flags with Hidden=false +func (c Command) VisibleFlags() []Flag { + return visibleFlags(c.Flags) +} diff --git a/flag.go b/flag.go index d18df0a..3c5c1b3 100644 --- a/flag.go +++ b/flag.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "os" + "reflect" "runtime" "strconv" "strings" @@ -12,8 +13,8 @@ import ( // This flag enables bash-completion for all commands and subcommands var BashCompletionFlag = BoolFlag{ - Name: "generate-bash-completion", - Hide: true, + Name: "generate-bash-completion", + Hidden: true, } // This flag prints the version for the application @@ -38,7 +39,6 @@ type Flag interface { // Apply Flag settings to the given flag set Apply(*flag.FlagSet) GetName() string - isNotHidden() bool } func flagSet(name string, flags []Flag) *flag.FlagSet { @@ -70,7 +70,7 @@ type GenericFlag struct { Value Generic Usage string EnvVar string - Hide bool + Hidden bool } // String returns the string representation of the generic flag to display the @@ -115,10 +115,6 @@ func (f GenericFlag) GetName() string { return f.Name } -func (f GenericFlag) isNotHidden() bool { - return !f.Hide -} - // StringSlice is an opaque type for []string to satisfy flag.Value type StringSlice []string @@ -145,7 +141,7 @@ type StringSliceFlag struct { Value *StringSlice Usage string EnvVar string - Hide bool + Hidden bool } // String returns the usage @@ -185,10 +181,6 @@ func (f StringSliceFlag) GetName() string { return f.Name } -func (f StringSliceFlag) isNotHidden() bool { - return !f.Hide -} - // StringSlice is an opaque type for []int to satisfy flag.Value type IntSlice []int @@ -220,7 +212,7 @@ type IntSliceFlag struct { Value *IntSlice Usage string EnvVar string - Hide bool + Hidden bool } // String returns the usage @@ -263,17 +255,13 @@ func (f IntSliceFlag) GetName() string { return f.Name } -func (f IntSliceFlag) isNotHidden() bool { - return !f.Hide -} - // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string Usage string EnvVar string Destination *bool - Hide bool + Hidden bool } // String returns a readable representation of this value (for usage defaults) @@ -311,10 +299,6 @@ func (f BoolFlag) GetName() string { return f.Name } -func (f BoolFlag) isNotHidden() bool { - return !f.Hide -} - // BoolTFlag this represents a boolean flag that is true by default, but can // still be set to false by --some-flag=false type BoolTFlag struct { @@ -322,7 +306,7 @@ type BoolTFlag struct { Usage string EnvVar string Destination *bool - Hide bool + Hidden bool } // String returns a readable representation of this value (for usage defaults) @@ -360,10 +344,6 @@ func (f BoolTFlag) GetName() string { return f.Name } -func (f BoolTFlag) isNotHidden() bool { - return !f.Hide -} - // StringFlag represents a flag that takes as string value type StringFlag struct { Name string @@ -371,7 +351,7 @@ type StringFlag struct { Usage string EnvVar string Destination *string - Hide bool + Hidden bool } // String returns the usage @@ -413,10 +393,6 @@ func (f StringFlag) GetName() string { return f.Name } -func (f StringFlag) isNotHidden() bool { - return !f.Hide -} - // IntFlag is a flag that takes an integer // Errors if the value provided cannot be parsed type IntFlag struct { @@ -425,7 +401,7 @@ type IntFlag struct { Usage string EnvVar string Destination *int - Hide bool + Hidden bool } // String returns the usage @@ -462,10 +438,6 @@ func (f IntFlag) GetName() string { return f.Name } -func (f IntFlag) isNotHidden() bool { - return !f.Hide -} - // DurationFlag is a flag that takes a duration specified in Go's duration // format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { @@ -474,7 +446,7 @@ type DurationFlag struct { Usage string EnvVar string Destination *time.Duration - Hide bool + Hidden bool } // String returns a readable representation of this value (for usage defaults) @@ -511,10 +483,6 @@ func (f DurationFlag) GetName() string { return f.Name } -func (f DurationFlag) isNotHidden() bool { - return !f.Hide -} - // Float64Flag is a flag that takes an float value // Errors if the value provided cannot be parsed type Float64Flag struct { @@ -523,7 +491,7 @@ type Float64Flag struct { Usage string EnvVar string Destination *float64 - Hide bool + Hidden bool } // String returns the usage @@ -559,8 +527,14 @@ func (f Float64Flag) GetName() string { return f.Name } -func (f Float64Flag) isNotHidden() bool { - return !f.Hide +func visibleFlags(fl []Flag) []Flag { + visible := []Flag{} + for _, flag := range fl { + if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { + visible = append(visible, flag) + } + } + return visible } func prefixFor(name string) (prefix string) { diff --git a/help.go b/help.go index 507fbe5..45e8603 100644 --- a/help.go +++ b/help.go @@ -15,7 +15,7 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} {{if .Version}}{{if not .HideVersion}} VERSION: {{.Version}} @@ -26,9 +26,9 @@ AUTHOR(S): COMMANDS:{{range .Categories}}{{if .Name}} {{.Name}}{{ ":" }}{{end}}{{range .Commands}} {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} -{{end}}{{end}}{{if .Flags}} +{{end}}{{end}}{{if .VisibleFlags}} GLOBAL OPTIONS: - {{range .Flags}}{{.}} + {{range .VisibleFlags}}{{.}} {{end}}{{end}}{{if .Copyright }} COPYRIGHT: {{.Copyright}} @@ -42,16 +42,16 @@ var CommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} + {{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{if .Flags}} + {{.Description}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .Flags}}{{.}} + {{range .VisibleFlags}}{{.}} {{end}}{{ end }} ` @@ -62,14 +62,14 @@ var SubcommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} COMMANDS:{{range .Categories}}{{if .Name}} {{.Name}}{{ ":" }}{{end}}{{range .Commands}} {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} -{{end}}{{if .Flags}} +{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .Flags}}{{.}} + {{range .VisibleFlags}}{{.}} {{end}}{{end}} ` @@ -114,15 +114,7 @@ var HelpPrinter helpPrinter = printHelp var VersionPrinter = printVersion func ShowAppHelp(c *Context) { - // Make a copy of c.App context - app := *c.App - app.Flags = make([]Flag, 0) - for _, flag := range c.App.Flags { - if flag.isNotHidden() { - app.Flags = append(app.Flags, flag) - } - } - HelpPrinter(c.App.Writer, AppHelpTemplate, app) + HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) } // Prints the list of subcommands as the default app completion method @@ -138,29 +130,13 @@ func DefaultAppComplete(c *Context) { func ShowCommandHelp(ctx *Context, command string) { // show the subcommand help for a command with subcommands if command == "" { - // Make a copy of c.App context - app := *c.App - app.Flags = make([]Flag, 0) - for _, flag := range c.App.Flags { - if flag.isNotHidden() { - app.Flags = append(app.Flags, flag) - } - } - HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, app) + HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) return } for _, c := range ctx.App.Commands { if c.HasName(command) { - // Make a copy of command context - c0 := c - c0.Flags = make([]Flag, 0) - for _, flag := range c.Flags { - if flag.isNotHidden() { - c0.Flags = append(c0.Flags, flag) - } - } - HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c0) + HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) return } } From 95845551505a09fe45cf0681455d3e8313920c78 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 1 May 2016 08:44:01 -0400 Subject: [PATCH 147/381] Include details of hidden flag impl in CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5760983..7c865e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ **ATTN**: This project uses [semantic versioning](http://semver.org/). ## [Unreleased] +### Added +- `Hidden` field on all flag struct types to omit from generated help text + +### Changed +- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from +generated help text via the `Hidden` field ## [1.15.0] - 2016-04-30 ### Added From 007295e509868d8dfbe52659c97ef04c059ed52b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 1 May 2016 09:09:54 -0400 Subject: [PATCH 148/381] Cleaning up the recently-introduced deprecations to ensure the intent, responsibility, and migration path are all more clear. --- app.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index 6917fbc..6d0690e 100644 --- a/app.go +++ b/app.go @@ -12,7 +12,9 @@ import ( ) var ( - appActionDeprecationURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-action-signature" + changeLogURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md" + appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) + runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." @@ -230,9 +232,9 @@ func (a *App) Run(arguments []string) (err error) { // DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling func (a *App) RunAndExitOnError() { - fmt.Fprintln(os.Stderr, - "DEPRECATED cli.App.RunAndExitOnError. "+ - "See https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-runandexitonerror") + fmt.Fprintf(os.Stderr, + "DEPRECATED cli.App.RunAndExitOnError. %s See %s\n", + contactSysadmin, runAndExitOnErrorDeprecationURL) if err := a.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -421,8 +423,9 @@ func HandleAction(action interface{}, context *Context) (err error) { vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) if len(vals) == 0 { - fmt.Fprintln(os.Stderr, - "DEPRECATED Action signature. Must be `cli.ActionFunc`") + fmt.Fprintf(os.Stderr, + "DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n", + contactSysadmin, appActionDeprecationURL) return nil } From b738841df82b9a299ee3ebcd3e80a0b49836fa90 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 1 May 2016 10:06:12 -0400 Subject: [PATCH 149/381] Add some explicit docs about help text customization --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/README.md b/README.md index 5db1b6f..064961d 100644 --- a/README.md +++ b/README.md @@ -496,6 +496,69 @@ Alternatively, you can just document that users should source the generic `autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set to the name of their program (as above). +### Generated Help Text Customization + +All of the help text generation may be customized, and at multiple levels. The +templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and +`SubcommandHelpTemplate` which may be reassigned or augmented, and full override +is possible by assigning a compatible func to the `cli.HelpPrinter` variable, +e.g.: + +``` go +package main + +import ( + "fmt" + "io" + "os" + + "github.com/codegangsta/cli" +) + +func main() { + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s + +WEBSITE: http://awesometown.example.com + +SUPPORT: support@awesometown.example.com + +`, cli.AppHelpTemplate) + + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command +[command options]{{end}} {{if +.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if len .Authors}} +AUTHOR(S): + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t" +}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +GLOBAL OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}}{{if .Version}} +VERSION: + {{.Version}} + {{end}} +` + + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } + + cli.NewApp().Run(os.Args) +} +``` + ## 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. From f90241a6a37a14ae9d5fe678b6effc2bae462254 Mon Sep 17 00:00:00 2001 From: Paul Makepeace Date: Sun, 1 May 2016 20:58:59 -0700 Subject: [PATCH 150/381] Assert type against actual return val's interface. Exit code example produces now correctly, https://github.com/codegangsta/cli#exit-code ``` $ ./ec --ginger-crouton=false it is not in the soup $ echo $? 86 $ ``` --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index a5b0aa3..696c05e 100644 --- a/app.go +++ b/app.go @@ -438,7 +438,7 @@ func HandleAction(action interface{}, context *Context) (err error) { return errInvalidActionSignature } - if retErr, ok := reflect.ValueOf(vals[0]).Interface().(error); ok { + if retErr, ok := vals[0].Interface().(error); ok { return retErr } From a90e2e4ff1f110ff75b7a12d43df6328bf4cc134 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Mon, 2 May 2016 17:06:14 +0200 Subject: [PATCH 151/381] Fix #376 NewExitError not working, reflect vals[0] cast to Interface() was missing --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index a5b0aa3..76ab2f2 100644 --- a/app.go +++ b/app.go @@ -438,7 +438,7 @@ func HandleAction(action interface{}, context *Context) (err error) { return errInvalidActionSignature } - if retErr, ok := reflect.ValueOf(vals[0]).Interface().(error); ok { + if retErr, ok := reflect.ValueOf(vals[0].Interface()).Interface().(error); ok { return retErr } From 4b4c07bd4fda8289a8c3d12d8feca4b2c9912868 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 11:25:37 -0400 Subject: [PATCH 152/381] Ensure HandleAction/HandleExitCoder work correctly with *ExitError Closes #376 --- app.go | 16 +++++--------- errors.go | 12 ++++++++-- errors_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 errors_test.go diff --git a/app.go b/app.go index 76ab2f2..89c741b 100644 --- a/app.go +++ b/app.go @@ -168,9 +168,7 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) - if err != nil { - HandleExitCoder(err) - } + HandleExitCoder(err) return err } else { fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") @@ -224,9 +222,7 @@ func (a *App) Run(arguments []string) (err error) { // Run default Action err = HandleAction(a.Action, context) - if err != nil { - HandleExitCoder(err) - } + HandleExitCoder(err) return err } @@ -237,7 +233,7 @@ func (a *App) RunAndExitOnError() { contactSysadmin, runAndExitOnErrorDeprecationURL) if err := a.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) - os.Exit(1) + OsExiter(1) } } @@ -346,9 +342,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // Run default Action err = HandleAction(a.Action, context) - if err != nil { - HandleExitCoder(err) - } + HandleExitCoder(err) return err } @@ -438,7 +432,7 @@ func HandleAction(action interface{}, context *Context) (err error) { return errInvalidActionSignature } - if retErr, ok := reflect.ValueOf(vals[0].Interface()).Interface().(error); ok { + if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok { return retErr } diff --git a/errors.go b/errors.go index 1a6a8c7..5f1e83b 100644 --- a/errors.go +++ b/errors.go @@ -6,6 +6,8 @@ import ( "strings" ) +var OsExiter = os.Exit + type MultiError struct { Errors []error } @@ -26,6 +28,7 @@ func (m MultiError) Error() string { // ExitCoder is the interface checked by `App` and `Command` for a custom exit // code type ExitCoder interface { + error ExitCode() int } @@ -56,15 +59,20 @@ func (ee *ExitError) ExitCode() int { } // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if -// so prints the error to stderr (if it is non-empty) and calls os.Exit with the +// so prints the error to stderr (if it is non-empty) and calls OsExiter with the // given exit code. If the given error is a MultiError, then this func is // called on all members of the Errors slice. func HandleExitCoder(err error) { + if err == nil { + return + } + if exitErr, ok := err.(ExitCoder); ok { if err.Error() != "" { fmt.Fprintln(os.Stderr, err) } - os.Exit(exitErr.ExitCode()) + OsExiter(exitErr.ExitCode()) + return } if multiErr, ok := err.(MultiError); ok { diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 0000000..6863105 --- /dev/null +++ b/errors_test.go @@ -0,0 +1,60 @@ +package cli + +import ( + "errors" + "os" + "testing" +) + +func TestHandleExitCoder_nil(t *testing.T) { + exitCode := 0 + called := false + + OsExiter = func(rc int) { + exitCode = rc + called = true + } + + defer func() { OsExiter = os.Exit }() + + HandleExitCoder(nil) + + expect(t, exitCode, 0) + expect(t, called, false) +} + +func TestHandleExitCoder_ExitCoder(t *testing.T) { + exitCode := 0 + called := false + + OsExiter = func(rc int) { + exitCode = rc + called = true + } + + defer func() { OsExiter = os.Exit }() + + HandleExitCoder(NewExitError("galactic perimiter breach", 9)) + + expect(t, exitCode, 9) + expect(t, called, true) +} + +func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { + exitCode := 0 + called := false + + OsExiter = func(rc int) { + exitCode = rc + called = true + } + + defer func() { OsExiter = os.Exit }() + + exitErr := NewExitError("galactic perimiter breach", 9) + err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) + HandleExitCoder(err) + + expect(t, exitCode, 9) + expect(t, called, true) +} From fe67cb0f3ddab023e6ceec62c0d12ed0a414a83d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 11:41:01 -0400 Subject: [PATCH 153/381] Add note about error handling fix, prep 1.16.0 section --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c865e9..39581e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ **ATTN**: This project uses [semantic versioning](http://semver.org/). ## [Unreleased] + +## [1.16.0] - 2016-05-02 ### Added - `Hidden` field on all flag struct types to omit from generated help text @@ -10,6 +12,9 @@ - `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from generated help text via the `Hidden` field +### Fixed +- handling of error values in `HandleAction` and `HandleExitCoder` + ## [1.15.0] - 2016-04-30 ### Added - This file! @@ -257,7 +262,8 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/codegangsta/cli/compare/v1.15.0...HEAD +[Unreleased]: https://github.com/codegangsta/cli/compare/v1.16.0...HEAD +[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0 [1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0 [1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 [1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 From 5c36fa18d949f5b9501668a58d85abe72ac1952d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 11:51:54 -0400 Subject: [PATCH 154/381] Change refs from `cli.go` to cli since the days of this being a single-file library are long gone. --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 064961d..4c61af9 100644 --- a/README.md +++ b/README.md @@ -3,21 +3,21 @@ [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) -# cli.go +# cli -`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. +cli is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. ## Overview Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. -**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive! +**This is where cli comes into play.** cli 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, simply run: ``` $ go get github.com/codegangsta/cli ``` @@ -29,7 +29,7 @@ export PATH=$PATH:$GOPATH/bin ## Getting Started -One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. +One of the philosophies behind cli is that an API should be playful and full of discovery. So a cli app can be as little as one line of code in `main()`. ``` go package main @@ -113,7 +113,7 @@ $ greet Hello friend! ``` -`cli.go` also generates neat help text: +cli also generates neat help text: ``` $ greet help From 22773b14c1d7ddda94888c7797405237f547f1db Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 13:05:21 -0400 Subject: [PATCH 155/381] Allow for pluggable flag-level help text formatting by defining `cli.DefaultFlagStringFunc` with a default value that uses `withEnvHint`, conditionally running a given flag's `FormatValueHelp` if present. Closes #257 --- CHANGELOG.md | 6 +++++ flag.go | 69 +++++++++++++++++++++++++++++++++++----------------- flag_test.go | 37 +++++++++++++--------------- funcs.go | 4 +++ 4 files changed, 74 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39581e9..efdc548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ **ATTN**: This project uses [semantic versioning](http://semver.org/). ## [Unreleased] +### Added +- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` + +### Changed +- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer +quoted in help text output. ## [1.16.0] - 2016-05-02 ### Added diff --git a/flag.go b/flag.go index 3c5c1b3..d21e018 100644 --- a/flag.go +++ b/flag.go @@ -31,6 +31,8 @@ var HelpFlag = BoolFlag{ Usage: "show help", } +var DefaultFlagStringFunc FlagStringFunc = flagStringer + // Flag is a common interface related to parsing flags in cli. // For more advanced flag parsing techniques, it is recommended that // this interface be implemented. @@ -77,8 +79,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 { - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name, placeholder), f.FormatValueHelp(), usage)) + return DefaultFlagStringFunc(f) } func (f GenericFlag) FormatValueHelp() string { @@ -146,10 +147,7 @@ type StringSliceFlag struct { // String returns the usage func (f StringSliceFlag) String() string { - firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") - pref := prefixFor(firstName) - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name, placeholder), pref+firstName+" option "+pref+firstName+" option", usage)) + return DefaultFlagStringFunc(f) } // Apply populates the flag given the flag set and environment @@ -181,6 +179,12 @@ func (f StringSliceFlag) GetName() string { return f.Name } +func (f StringSliceFlag) FormatValueHelp() string { + firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") + pref := prefixFor(firstName) + return fmt.Sprintf("[%s%s option %s%s option]", pref, firstName, pref, firstName) +} + // StringSlice is an opaque type for []int to satisfy flag.Value type IntSlice []int @@ -217,10 +221,7 @@ type IntSliceFlag struct { // String returns the usage func (f IntSliceFlag) String() string { - firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") - pref := prefixFor(firstName) - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name, placeholder), pref+firstName+" option "+pref+firstName+" option", usage)) + return DefaultFlagStringFunc(f) } // Apply populates the flag given the flag set and environment @@ -255,6 +256,12 @@ func (f IntSliceFlag) GetName() string { return f.Name } +func (f IntSliceFlag) FormatValueHelp() string { + firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") + pref := prefixFor(firstName) + return fmt.Sprintf("[%s%s option %s%s option]", pref, firstName, pref, firstName) +} + // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string @@ -266,8 +273,7 @@ type BoolFlag struct { // String returns a readable representation of this value (for usage defaults) func (f BoolFlag) String() string { - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name, placeholder), usage)) + return DefaultFlagStringFunc(f) } // Apply populates the flag given the flag set and environment @@ -311,8 +317,7 @@ type BoolTFlag struct { // String returns a readable representation of this value (for usage defaults) func (f BoolTFlag) String() string { - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name, placeholder), usage)) + return DefaultFlagStringFunc(f) } // Apply populates the flag given the flag set and environment @@ -356,8 +361,7 @@ type StringFlag struct { // String returns the usage func (f StringFlag) String() string { - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name, placeholder), f.FormatValueHelp(), usage)) + return DefaultFlagStringFunc(f) } func (f StringFlag) FormatValueHelp() string { @@ -406,8 +410,7 @@ type IntFlag struct { // String returns the usage func (f IntFlag) String() string { - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage)) + return DefaultFlagStringFunc(f) } // Apply populates the flag given the flag set and environment @@ -451,8 +454,7 @@ type DurationFlag struct { // String returns a readable representation of this value (for usage defaults) func (f DurationFlag) String() string { - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage)) + return DefaultFlagStringFunc(f) } // Apply populates the flag given the flag set and environment @@ -496,8 +498,7 @@ type Float64Flag struct { // String returns the usage func (f Float64Flag) String() string { - placeholder, usage := unquoteUsage(f.Usage) - return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage)) + return DefaultFlagStringFunc(f) } // Apply populates the flag given the flag set and environment @@ -595,3 +596,27 @@ func withEnvHint(envVar, str string) string { } return str + envText } + +func flagStringer(f Flag) string { + fv := reflect.ValueOf(f) + placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) + + defaultValueString := "" + if val := fv.FieldByName("Value"); val.IsValid() { + defaultValueString = fmt.Sprintf("%v", val.Interface()) + } + + formatFunc := fv.MethodByName("FormatValueHelp") + if formatFunc.IsValid() { + defaultValueString = formatFunc.Call([]reflect.Value{})[0].String() + } + + if len(defaultValueString) > 0 { + defaultValueString = " " + defaultValueString + } + + return withEnvHint(fv.FieldByName("EnvVar").String(), + fmt.Sprintf("%s%v\t%v", + prefixedNames(fv.FieldByName("Name").String(), placeholder), + defaultValueString, usage)) +} diff --git a/flag_test.go b/flag_test.go index 48d920a..a49cac7 100644 --- a/flag_test.go +++ b/flag_test.go @@ -7,6 +7,7 @@ import ( "runtime" "strings" "testing" + "time" ) var boolFlagTests = []struct { @@ -18,13 +19,12 @@ var boolFlagTests = []struct { } func TestBoolFlagHelpOutput(t *testing.T) { - for _, test := range boolFlagTests { flag := BoolFlag{Name: test.name} output := flag.String() if output != test.expected { - t.Errorf("%s does not match %s", output, test.expected) + t.Errorf("%q does not match %q", output, test.expected) } } } @@ -35,11 +35,11 @@ var stringFlagTests = []struct { value string expected string }{ - {"help", "", "", "--help \t"}, - {"h", "", "", "-h \t"}, - {"h", "", "", "-h \t"}, + {"help", "", "", "--help\t"}, + {"h", "", "", "-h\t"}, + {"h", "", "", "-h\t"}, {"test", "", "Something", "--test \"Something\"\t"}, - {"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE \tLoad configuration from FILE"}, + {"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, } func TestStringFlagHelpOutput(t *testing.T) { @@ -49,7 +49,7 @@ func TestStringFlagHelpOutput(t *testing.T) { output := flag.String() if output != test.expected { - t.Errorf("%s does not match %s", output, test.expected) + t.Errorf("%q does not match %q", output, test.expected) } } } @@ -131,8 +131,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) { @@ -168,18 +168,17 @@ var durationFlagTests = []struct { name string expected string }{ - {"help", "--help \"0\"\t"}, - {"h", "-h \"0\"\t"}, + {"help", "--help 1s\t"}, + {"h", "-h 1s\t"}, } func TestDurationFlagHelpOutput(t *testing.T) { - for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name} + flag := DurationFlag{Name: test.name, Value: 1 * time.Second} output := flag.String() if output != test.expected { - t.Errorf("%s does not match %s", output, test.expected) + t.Errorf("%q does not match %q", output, test.expected) } } } @@ -249,18 +248,17 @@ var float64FlagTests = []struct { name string expected string }{ - {"help", "--help \"0\"\t"}, - {"h", "-h \"0\"\t"}, + {"help", "--help 0.1\t"}, + {"h", "-h 0.1\t"}, } func TestFloat64FlagHelpOutput(t *testing.T) { - for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name} + flag := Float64Flag{Name: test.name, Value: float64(0.1)} output := flag.String() if output != test.expected { - t.Errorf("%s does not match %s", output, test.expected) + t.Errorf("%q does not match %q", output, test.expected) } } } @@ -292,7 +290,6 @@ var genericFlagTests = []struct { } func TestGenericFlagHelpOutput(t *testing.T) { - for _, test := range genericFlagTests { flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} output := flag.String() diff --git a/funcs.go b/funcs.go index 94640ea..6b2a846 100644 --- a/funcs.go +++ b/funcs.go @@ -22,3 +22,7 @@ type CommandNotFoundFunc func(*Context, string) // original error messages. If this function is not set, the "Incorrect usage" // is displayed and the execution is interrupted. type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error + +// FlagStringFunc is used by the help generation to display a flag, which is +// expected to be a single line. +type FlagStringFunc func(Flag) string From 23af5dd643c494dcc6cf2170734ef40fd87c0674 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 13:07:57 -0400 Subject: [PATCH 156/381] Rename flag stringer func bits for clarity, consistency --- flag.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/flag.go b/flag.go index d21e018..d00a984 100644 --- a/flag.go +++ b/flag.go @@ -31,7 +31,7 @@ var HelpFlag = BoolFlag{ Usage: "show help", } -var DefaultFlagStringFunc FlagStringFunc = flagStringer +var FlagStringer FlagStringFunc = stringifyFlag // Flag is a common interface related to parsing flags in cli. // For more advanced flag parsing techniques, it is recommended that @@ -79,7 +79,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 DefaultFlagStringFunc(f) + return FlagStringer(f) } func (f GenericFlag) FormatValueHelp() string { @@ -147,7 +147,7 @@ type StringSliceFlag struct { // String returns the usage func (f StringSliceFlag) String() string { - return DefaultFlagStringFunc(f) + return FlagStringer(f) } // Apply populates the flag given the flag set and environment @@ -221,7 +221,7 @@ type IntSliceFlag struct { // String returns the usage func (f IntSliceFlag) String() string { - return DefaultFlagStringFunc(f) + return FlagStringer(f) } // Apply populates the flag given the flag set and environment @@ -273,7 +273,7 @@ type BoolFlag struct { // String returns a readable representation of this value (for usage defaults) func (f BoolFlag) String() string { - return DefaultFlagStringFunc(f) + return FlagStringer(f) } // Apply populates the flag given the flag set and environment @@ -317,7 +317,7 @@ type BoolTFlag struct { // String returns a readable representation of this value (for usage defaults) func (f BoolTFlag) String() string { - return DefaultFlagStringFunc(f) + return FlagStringer(f) } // Apply populates the flag given the flag set and environment @@ -361,7 +361,7 @@ type StringFlag struct { // String returns the usage func (f StringFlag) String() string { - return DefaultFlagStringFunc(f) + return FlagStringer(f) } func (f StringFlag) FormatValueHelp() string { @@ -410,7 +410,7 @@ type IntFlag struct { // String returns the usage func (f IntFlag) String() string { - return DefaultFlagStringFunc(f) + return FlagStringer(f) } // Apply populates the flag given the flag set and environment @@ -454,7 +454,7 @@ type DurationFlag struct { // String returns a readable representation of this value (for usage defaults) func (f DurationFlag) String() string { - return DefaultFlagStringFunc(f) + return FlagStringer(f) } // Apply populates the flag given the flag set and environment @@ -498,7 +498,7 @@ type Float64Flag struct { // String returns the usage func (f Float64Flag) String() string { - return DefaultFlagStringFunc(f) + return FlagStringer(f) } // Apply populates the flag given the flag set and environment @@ -597,7 +597,7 @@ func withEnvHint(envVar, str string) string { return str + envText } -func flagStringer(f Flag) string { +func stringifyFlag(f Flag) string { fv := reflect.ValueOf(f) placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) From 69a8e25f3d7390cf45d81b130200b0a9448d3ca9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 19:42:08 -0400 Subject: [PATCH 157/381] Make flag usage rendering more consistent; show default values --- flag.go | 128 ++++++++++++++++++++++++++++++++++----------------- flag_test.go | 57 +++++++++++------------ 2 files changed, 114 insertions(+), 71 deletions(-) diff --git a/flag.go b/flag.go index d00a984..4535b5d 100644 --- a/flag.go +++ b/flag.go @@ -11,6 +11,8 @@ import ( "time" ) +const defaultPlaceholder = "value" + // This flag enables bash-completion for all commands and subcommands var BashCompletionFlag = BoolFlag{ Name: "generate-bash-completion", @@ -82,17 +84,6 @@ func (f GenericFlag) String() string { return FlagStringer(f) } -func (f GenericFlag) FormatValueHelp() string { - if f.Value == nil { - return "" - } - s := f.Value.String() - if len(s) == 0 { - return "" - } - return fmt.Sprintf("\"%s\"", s) -} - // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f GenericFlag) Apply(set *flag.FlagSet) { @@ -179,12 +170,6 @@ func (f StringSliceFlag) GetName() string { return f.Name } -func (f StringSliceFlag) FormatValueHelp() string { - firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") - pref := prefixFor(firstName) - return fmt.Sprintf("[%s%s option %s%s option]", pref, firstName, pref, firstName) -} - // StringSlice is an opaque type for []int to satisfy flag.Value type IntSlice []int @@ -256,12 +241,6 @@ func (f IntSliceFlag) GetName() string { return f.Name } -func (f IntSliceFlag) FormatValueHelp() string { - firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") - pref := prefixFor(firstName) - return fmt.Sprintf("[%s%s option %s%s option]", pref, firstName, pref, firstName) -} - // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string @@ -364,14 +343,6 @@ func (f StringFlag) String() string { return FlagStringer(f) } -func (f StringFlag) FormatValueHelp() string { - s := f.Value - if len(s) == 0 { - return "" - } - return fmt.Sprintf("\"%s\"", s) -} - // Apply populates the flag given the flag set and environment func (f StringFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { @@ -599,24 +570,99 @@ func withEnvHint(envVar, str string) string { func stringifyFlag(f Flag) string { fv := reflect.ValueOf(f) + + switch f.(type) { + case IntSliceFlag: + return withEnvHint(fv.FieldByName("EnvVar").String(), + stringifyIntSliceFlag(f.(IntSliceFlag))) + case StringSliceFlag: + return withEnvHint(fv.FieldByName("EnvVar").String(), + stringifyStringSliceFlag(f.(StringSliceFlag))) + } + placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) + needsPlaceholder := false defaultValueString := "" - if val := fv.FieldByName("Value"); val.IsValid() { - defaultValueString = fmt.Sprintf("%v", val.Interface()) + val := fv.FieldByName("Value") + + valString := "" + if val.IsValid() { + needsPlaceholder = true + if val.Kind() == reflect.String && val.String() != "" { + valString = fmt.Sprintf("%q", val.String()) + } else { + valString = fmt.Sprintf("%v", val.Interface()) + } } - formatFunc := fv.MethodByName("FormatValueHelp") - if formatFunc.IsValid() { - defaultValueString = formatFunc.Call([]reflect.Value{})[0].String() + if valString != "" { + defaultValueString = valString } - if len(defaultValueString) > 0 { - defaultValueString = " " + defaultValueString + if needsPlaceholder && placeholder == "" { + placeholder = defaultPlaceholder } + formattedDefaultValueString := "" + if defaultValueString != "" { + formattedDefaultValueString = fmt.Sprintf(" (default: %v)", defaultValueString) + } + + usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, formattedDefaultValueString)) + return withEnvHint(fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s%v\t%v", - prefixedNames(fv.FieldByName("Name").String(), placeholder), - defaultValueString, usage)) + fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) +} + +func stringifyIntSliceFlag(f IntSliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + } + } + + return stringifySliceFlag(f.Usage, f.Name, defaultVals) +} + +func stringifyStringSliceFlag(f StringSliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, s := range f.Value.Value() { + if len(s) > 0 { + defaultVals = append(defaultVals, fmt.Sprintf("%q", s)) + } + } + } + + return stringifySliceFlag(f.Usage, f.Name, defaultVals) +} + +func stringifySliceFlag(usage, name string, defaultVals []string) string { + placeholder, usage := unquoteUsage(usage) + if placeholder == "" { + placeholder = defaultPlaceholder + } + + nameParts := []string{} + for _, part := range strings.Split(name, ",") { + nameParts = append(nameParts, strings.TrimSpace(part)) + } + + defaultVal := "" + if len(defaultVals) > 0 { + defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) + } + + usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) + + if len(nameParts) < 2 { + return fmt.Sprintf("%s%s %s\t%s", prefixFor(nameParts[0]), nameParts[0], + placeholder, usageWithDefault) + } + + return fmt.Sprintf("%s%s %s, %s%s %s\t%s", prefixFor(nameParts[0]), nameParts[0], + placeholder, prefixFor(nameParts[1]), nameParts[1], + placeholder, usageWithDefault) } diff --git a/flag_test.go b/flag_test.go index a49cac7..e0df23b 100644 --- a/flag_test.go +++ b/flag_test.go @@ -35,15 +35,15 @@ var stringFlagTests = []struct { value string expected string }{ - {"help", "", "", "--help\t"}, - {"h", "", "", "-h\t"}, - {"h", "", "", "-h\t"}, - {"test", "", "Something", "--test \"Something\"\t"}, + {"foo", "", "", "--foo value\t"}, + {"f", "", "", "-f value\t"}, + {"f", "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"}, + {"test", "", "Something", "--test value\t(default: \"Something\")"}, {"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, + {"config,c", "Load configuration from `CONFIG`", "config.json", "--config CONFIG, -c CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, } func TestStringFlagHelpOutput(t *testing.T) { - for _, test := range stringFlagTests { flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} output := flag.String() @@ -76,30 +76,29 @@ var stringSliceFlagTests = []struct { value *StringSlice expected string }{ - {"help", func() *StringSlice { + {"foo", func() *StringSlice { s := &StringSlice{} s.Set("") return s - }(), "--help [--help option --help option]\t"}, - {"h", func() *StringSlice { + }(), "--foo value\t"}, + {"f", func() *StringSlice { s := &StringSlice{} s.Set("") return s - }(), "-h [-h option -h option]\t"}, - {"h", func() *StringSlice { + }(), "-f value\t"}, + {"f", func() *StringSlice { s := &StringSlice{} - s.Set("") + s.Set("Lipstick") return s - }(), "-h [-h option -h option]\t"}, + }(), "-f value\t(default: \"Lipstick\")"}, {"test", func() *StringSlice { s := &StringSlice{} s.Set("Something") return s - }(), "--test [--test option --test option]\t"}, + }(), "--test value\t(default: \"Something\")"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { - for _, test := range stringSliceFlagTests { flag := StringSliceFlag{Name: test.name, Value: test.value} output := flag.String() @@ -131,14 +130,13 @@ var intFlagTests = []struct { name string expected string }{ - {"help", "--help 0\t"}, - {"h", "-h 0\t"}, + {"hats", "--hats value\t(default: 9)"}, + {"H", "-H value\t(default: 9)"}, } func TestIntFlagHelpOutput(t *testing.T) { - for _, test := range intFlagTests { - flag := IntFlag{Name: test.name} + flag := IntFlag{Name: test.name, Value: 9} output := flag.String() if output != test.expected { @@ -168,8 +166,8 @@ var durationFlagTests = []struct { name string expected string }{ - {"help", "--help 1s\t"}, - {"h", "-h 1s\t"}, + {"hooting", "--hooting value\t(default: 1s)"}, + {"H", "-H value\t(default: 1s)"}, } func TestDurationFlagHelpOutput(t *testing.T) { @@ -205,18 +203,17 @@ var intSliceFlagTests = []struct { value *IntSlice expected string }{ - {"help", &IntSlice{}, "--help [--help option --help option]\t"}, - {"h", &IntSlice{}, "-h [-h option -h option]\t"}, - {"h", &IntSlice{}, "-h [-h option -h option]\t"}, - {"test", func() *IntSlice { + {"heads", &IntSlice{}, "--heads value\t"}, + {"H", &IntSlice{}, "-H value\t"}, + {"H, heads", func() *IntSlice { i := &IntSlice{} i.Set("9") + i.Set("3") return i - }(), "--test [--test option --test option]\t"}, + }(), "-H value, --heads value\t(default: 9, 3)"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { - for _, test := range intSliceFlagTests { flag := IntSliceFlag{Name: test.name, Value: test.value} output := flag.String() @@ -248,8 +245,8 @@ var float64FlagTests = []struct { name string expected string }{ - {"help", "--help 0.1\t"}, - {"h", "-h 0.1\t"}, + {"hooting", "--hooting value\t(default: 0.1)"}, + {"H", "-H value\t(default: 0.1)"}, } func TestFloat64FlagHelpOutput(t *testing.T) { @@ -285,8 +282,8 @@ var genericFlagTests = []struct { value Generic expected string }{ - {"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"}, - {"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"}, + {"toads", &Parser{"abc", "def"}, "--toads value\ttest flag (default: abc,def)"}, + {"t", &Parser{"abc", "def"}, "-t value\ttest flag (default: abc,def)"}, } func TestGenericFlagHelpOutput(t *testing.T) { From 7de151883c333cde1739a25ae112c89e959328d4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 19:48:35 -0400 Subject: [PATCH 158/381] Added more notes about usage formatting changes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efdc548..d7ea0a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ ### Changed - `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer quoted in help text output. +- All flag types now include `(default: {value})` strings following usage when a +default value can be (reasonably) detected. +- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent +with non-slice flag types ## [1.16.0] - 2016-05-02 ### Added From 6089d723a81a9970f806ac7e198c6dcc1210596e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 19:52:39 -0400 Subject: [PATCH 159/381] Use existing `prefixedNames` func --- flag.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/flag.go b/flag.go index 4535b5d..8f124d3 100644 --- a/flag.go +++ b/flag.go @@ -645,24 +645,11 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { placeholder = defaultPlaceholder } - nameParts := []string{} - for _, part := range strings.Split(name, ",") { - nameParts = append(nameParts, strings.TrimSpace(part)) - } - defaultVal := "" if len(defaultVals) > 0 { defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - - if len(nameParts) < 2 { - return fmt.Sprintf("%s%s %s\t%s", prefixFor(nameParts[0]), nameParts[0], - placeholder, usageWithDefault) - } - - return fmt.Sprintf("%s%s %s, %s%s %s\t%s", prefixFor(nameParts[0]), nameParts[0], - placeholder, prefixFor(nameParts[1]), nameParts[1], - placeholder, usageWithDefault) + return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) } From cd92adcb7512ec11789a2b3bed9028367da1b175 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 19:58:16 -0400 Subject: [PATCH 160/381] Further simplifying default flag stringer func --- flag.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/flag.go b/flag.go index 8f124d3..3b6a2e1 100644 --- a/flag.go +++ b/flag.go @@ -581,35 +581,29 @@ func stringifyFlag(f Flag) string { } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) - needsPlaceholder := false + needsPlaceholder := false defaultValueString := "" val := fv.FieldByName("Value") - valString := "" if val.IsValid() { needsPlaceholder = true + defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) + if val.Kind() == reflect.String && val.String() != "" { - valString = fmt.Sprintf("%q", val.String()) - } else { - valString = fmt.Sprintf("%v", val.Interface()) + defaultValueString = fmt.Sprintf(" (default: %q)", val.String()) } } - if valString != "" { - defaultValueString = valString + if defaultValueString == " (default: )" { + defaultValueString = "" } if needsPlaceholder && placeholder == "" { placeholder = defaultPlaceholder } - formattedDefaultValueString := "" - if defaultValueString != "" { - formattedDefaultValueString = fmt.Sprintf(" (default: %v)", defaultValueString) - } - - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, formattedDefaultValueString)) + usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) return withEnvHint(fv.FieldByName("EnvVar").String(), fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) From b14dcdb7b97e38d247bd6389cc2f64c8f253d216 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 2 May 2016 21:20:23 -0400 Subject: [PATCH 161/381] TRIVIAL the letter "a" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c61af9..06f7b84 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # cli -cli is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. +cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. ## Overview From f397b1618ce783d09e35960ab83b3ad192649526 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 3 May 2016 05:51:26 -0400 Subject: [PATCH 162/381] Adding test for Command.Hidden handling in help text --- help_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/help_test.go b/help_test.go index ee5c25c..c6f2e57 100644 --- a/help_test.go +++ b/help_test.go @@ -2,6 +2,7 @@ package cli import ( "bytes" + "strings" "testing" ) @@ -110,3 +111,35 @@ func Test_Version_Custom_Flags(t *testing.T) { t.Errorf("unexpected output: %s", output.String()) } } + +func TestShowAppHelp_HiddenCommand(t *testing.T) { + app := &App{ + Commands: []Command{ + Command{ + Name: "frobbly", + Action: func(ctx *Context) error { + return nil + }, + }, + Command{ + Name: "secretfrob", + Hidden: true, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"app", "--help"}) + + if strings.Contains(output.String(), "secretfrob") { + t.Fatalf("expected output to exclude \"secretfrob\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "frobbly") { + t.Fatalf("expected output to include \"frobbly\"; got: %q", output.String()) + } +} From cc481d6b0ea0e659faecc03f48bd0352b5502b4b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 3 May 2016 06:54:05 -0400 Subject: [PATCH 163/381] Adjust command hiding to use similar convention as hidden flags plus breaking out "setup" portion of `App.Run` into its own method, cleaning up some bits of the help templates, and allowing for runtime opt-in of displaying template errors to stderr. --- app.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++--- app_test.go | 4 ++-- category.go | 11 +++++++++++ errors_test.go | 4 ++-- help.go | 26 ++++++++++++++----------- help_test.go | 4 ++-- 6 files changed, 80 insertions(+), 20 deletions(-) diff --git a/app.go b/app.go index 89c741b..7ad070b 100644 --- a/app.go +++ b/app.go @@ -84,6 +84,8 @@ type App struct { Writer io.Writer // Other custom info Metadata map[string]interface{} + + didSetup bool } // Tries to find out when this binary was compiled. @@ -111,8 +113,16 @@ 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) (err error) { +// Setup runs initialization code to ensure all data structures are ready for +// `Run` or inspection prior to `Run`. It is internally called by `Run`, but +// will return early if setup has already happened. +func (a *App) Setup() { + if a.didSetup { + return + } + + a.didSetup = true + if a.Author != "" || a.Email != "" { a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) } @@ -148,6 +158,11 @@ func (a *App) Run(arguments []string) (err error) { if !a.HideVersion { a.appendFlag(VersionFlag) } +} + +// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination +func (a *App) Run(arguments []string) (err error) { + a.Setup() // parse flags set := flagSet(a.Name, a.Flags) @@ -357,11 +372,41 @@ func (a *App) Command(name string) *Command { return nil } -// Returnes the array containing all the categories with the commands they contain +// Categories returns a slice containing all the categories with the commands they contain func (a *App) Categories() CommandCategories { return a.categories } +// VisibleCategories returns a slice of categories and commands that are +// Hidden=false +func (a *App) VisibleCategories() []*CommandCategory { + ret := []*CommandCategory{} + for _, category := range a.categories { + if visible := func() *CommandCategory { + for _, command := range category.Commands { + if !command.Hidden { + return category + } + } + return nil + }(); visible != nil { + ret = append(ret, visible) + } + } + return ret +} + +// VisibleCommands returns a slice of the Commands with Hidden=false +func (a *App) VisibleCommands() []Command { + ret := []Command{} + for _, command := range a.Commands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} + // VisibleFlags returns a slice of the Flags with Hidden=false func (a *App) VisibleFlags() []Flag { return visibleFlags(a.Flags) diff --git a/app_test.go b/app_test.go index bf2887e..b08abb2 100644 --- a/app_test.go +++ b/app_test.go @@ -1124,8 +1124,8 @@ func TestApp_Run_Categories(t *testing.T) { output := buf.String() t.Logf("output: %q\n", buf.Bytes()) - if !strings.Contains(output, "1:\n command1") { - t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) + if !strings.Contains(output, "1:\n command1") { + t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) } } diff --git a/category.go b/category.go index 7dbf218..5c3d4b5 100644 --- a/category.go +++ b/category.go @@ -28,3 +28,14 @@ func (c CommandCategories) AddCommand(category string, command Command) CommandC } return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) } + +// VisibleCommands returns a slice of the Commands with Hidden=false +func (c *CommandCategory) VisibleCommands() []Command { + ret := []Command{} + for _, command := range c.Commands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} diff --git a/errors_test.go b/errors_test.go index 6863105..8f5f284 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - HandleExitCoder(NewExitError("galactic perimiter breach", 9)) + HandleExitCoder(NewExitError("galactic perimeter breach", 9)) expect(t, exitCode, 9) expect(t, called, true) @@ -51,7 +51,7 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - exitErr := NewExitError("galactic perimiter breach", 9) + exitErr := NewExitError("galactic perimeter breach", 9) err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) HandleExitCoder(err) diff --git a/help.go b/help.go index 3f47efd..666791f 100644 --- a/help.go +++ b/help.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "io" + "os" "strings" "text/tabwriter" "text/template" @@ -21,15 +22,15 @@ VERSION: {{.Version}} {{end}}{{end}}{{if len .Authors}} AUTHOR(S): - {{range .Authors}}{{ . }}{{end}} - {{end}}{{if .Commands}} -COMMANDS:{{range .Categories}}{{if .Name}} - {{.Name}}{{ ":" }}{{end}}{{range .Commands}} - {{if not .Hidden}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}{{end}} + {{range .Authors}}{{.}}{{end}} + {{end}}{{if .VisibleCommands}} +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}} {{end}}{{end}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright }} + {{end}}{{end}}{{if .Copyright}} COPYRIGHT: {{.Copyright}} {{end}} @@ -52,7 +53,7 @@ DESCRIPTION: OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{ end }} + {{end}}{{end}} ` // The text template for the subcommand help topic. @@ -64,9 +65,9 @@ var SubcommandHelpTemplate = `NAME: USAGE: {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} -COMMANDS:{{range .Categories}}{{if .Name}} - {{.Name}}{{ ":" }}{{end}}{{range .Commands}} - {{if not .Hidden}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}{{end}} +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}} {{end}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} @@ -191,7 +192,10 @@ func printHelp(out io.Writer, templ string, data interface{}) { err := t.Execute(w, data) if err != nil { // If the writer is closed, t.Execute will fail, and there's nothing - // we can do to recover. We could send this to os.Stderr if we need. + // we can do to recover. + if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { + fmt.Fprintf(os.Stderr, "CLI TEMPLATE ERROR: %#v\n", err) + } return } w.Flush() diff --git a/help_test.go b/help_test.go index c6f2e57..db0cb21 100644 --- a/help_test.go +++ b/help_test.go @@ -136,10 +136,10 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) { app.Run([]string{"app", "--help"}) if strings.Contains(output.String(), "secretfrob") { - t.Fatalf("expected output to exclude \"secretfrob\"; got: %q", output.String()) + t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) } if !strings.Contains(output.String(), "frobbly") { - t.Fatalf("expected output to include \"frobbly\"; got: %q", output.String()) + t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) } } From 11ad2b36c828c166547b33345d724667da2ae93a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 4 May 2016 19:41:33 -0400 Subject: [PATCH 164/381] Assert output in some README examples --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 06f7b84..2ac96fd 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ func main() { This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: + ``` go package main @@ -77,6 +80,9 @@ Being a programmer can be a lonely job. Thankfully by the power of automation th Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it: + ``` go package main @@ -210,6 +216,7 @@ Sometimes it's useful to specify a flag's value within the usage string itself. indicated with back quotes. For example this: + ```go cli.StringFlag{ Name: "config, c", @@ -504,6 +511,9 @@ templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and is possible by assigning a compatible func to the `cli.HelpPrinter` variable, e.g.: + ``` go package main From 2a256d4c5397fb0e91ab71cc73787698d13023e0 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 5 May 2016 10:26:53 -0400 Subject: [PATCH 165/381] Provide a variable for writing output with a default of os.Stderr --- app.go | 6 +++--- errors.go | 6 +++++- flag.go | 2 +- help.go | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index 89c741b..28bf78b 100644 --- a/app.go +++ b/app.go @@ -228,11 +228,11 @@ func (a *App) Run(arguments []string) (err error) { // DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling func (a *App) RunAndExitOnError() { - fmt.Fprintf(os.Stderr, + fmt.Fprintf(OutWriter, "DEPRECATED cli.App.RunAndExitOnError. %s See %s\n", contactSysadmin, runAndExitOnErrorDeprecationURL) if err := a.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(OutWriter, err) OsExiter(1) } } @@ -422,7 +422,7 @@ func HandleAction(action interface{}, context *Context) (err error) { vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) if len(vals) == 0 { - fmt.Fprintf(os.Stderr, + fmt.Fprintf(OutWriter, "DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n", contactSysadmin, appActionDeprecationURL) return nil diff --git a/errors.go b/errors.go index 5f1e83b..c03e676 100644 --- a/errors.go +++ b/errors.go @@ -8,6 +8,10 @@ import ( var OsExiter = os.Exit +// OutWriter is used to write output to the user. This can be anything +// implementing the io.Writer interface and defaults to os.Stderr. +var OutWriter = os.Stderr + type MultiError struct { Errors []error } @@ -69,7 +73,7 @@ func HandleExitCoder(err error) { if exitErr, ok := err.(ExitCoder); ok { if err.Error() != "" { - fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(OutWriter, err) } OsExiter(exitErr.ExitCode()) return diff --git a/flag.go b/flag.go index 3b6a2e1..7778a2d 100644 --- a/flag.go +++ b/flag.go @@ -220,7 +220,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { s = strings.TrimSpace(s) err := newVal.Set(s) if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) + fmt.Fprintf(OutWriter, err.Error()) } } f.Value = newVal diff --git a/help.go b/help.go index 45e8603..79f2e85 100644 --- a/help.go +++ b/help.go @@ -188,7 +188,7 @@ func printHelp(out io.Writer, templ string, data interface{}) { err := t.Execute(w, data) if err != nil { // If the writer is closed, t.Execute will fail, and there's nothing - // we can do to recover. We could send this to os.Stderr if we need. + // we can do to recover. We could send this to OutWriter if we need. return } w.Flush() From 6f0b442222239d0c6c6a999742e6d56b58494d7e Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 6 May 2016 12:14:26 -0400 Subject: [PATCH 166/381] Update to ErrWriter and make available on app --- app.go | 18 +++++++++++++++--- errors.go | 7 ++++--- flag.go | 2 +- help.go | 2 +- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app.go b/app.go index 28bf78b..4d18d24 100644 --- a/app.go +++ b/app.go @@ -82,6 +82,8 @@ type App struct { Email string // Writer writer to write output to Writer io.Writer + // ErrWriter writes error output + ErrWriter io.Writer // Other custom info Metadata map[string]interface{} } @@ -228,11 +230,11 @@ func (a *App) Run(arguments []string) (err error) { // DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling func (a *App) RunAndExitOnError() { - fmt.Fprintf(OutWriter, + fmt.Fprintf(a.errWriter(), "DEPRECATED cli.App.RunAndExitOnError. %s See %s\n", contactSysadmin, runAndExitOnErrorDeprecationURL) if err := a.Run(os.Args); err != nil { - fmt.Fprintln(OutWriter, err) + fmt.Fprintln(a.errWriter(), err) OsExiter(1) } } @@ -377,6 +379,16 @@ func (a *App) hasFlag(flag Flag) bool { return false } +func (a *App) errWriter() io.Writer { + + // When the app ErrWriter is nil use the package level one. + if a.ErrWriter == nil { + return ErrWriter + } + + return a.ErrWriter +} + func (a *App) appendFlag(flag Flag) { if !a.hasFlag(flag) { a.Flags = append(a.Flags, flag) @@ -422,7 +434,7 @@ func HandleAction(action interface{}, context *Context) (err error) { vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) if len(vals) == 0 { - fmt.Fprintf(OutWriter, + fmt.Fprintf(ErrWriter, "DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n", contactSysadmin, appActionDeprecationURL) return nil diff --git a/errors.go b/errors.go index c03e676..db46a83 100644 --- a/errors.go +++ b/errors.go @@ -2,15 +2,16 @@ package cli import ( "fmt" + "io" "os" "strings" ) var OsExiter = os.Exit -// OutWriter is used to write output to the user. This can be anything +// ErrWriter is used to write errors to the user. This can be anything // implementing the io.Writer interface and defaults to os.Stderr. -var OutWriter = os.Stderr +var ErrWriter io.Writer = os.Stderr type MultiError struct { Errors []error @@ -73,7 +74,7 @@ func HandleExitCoder(err error) { if exitErr, ok := err.(ExitCoder); ok { if err.Error() != "" { - fmt.Fprintln(OutWriter, err) + fmt.Fprintln(ErrWriter, err) } OsExiter(exitErr.ExitCode()) return diff --git a/flag.go b/flag.go index 7778a2d..8354de0 100644 --- a/flag.go +++ b/flag.go @@ -220,7 +220,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { s = strings.TrimSpace(s) err := newVal.Set(s) if err != nil { - fmt.Fprintf(OutWriter, err.Error()) + fmt.Fprintf(ErrWriter, err.Error()) } } f.Value = newVal diff --git a/help.go b/help.go index 79f2e85..f4ea7a3 100644 --- a/help.go +++ b/help.go @@ -188,7 +188,7 @@ func printHelp(out io.Writer, templ string, data interface{}) { err := t.Execute(w, data) if err != nil { // If the writer is closed, t.Execute will fail, and there's nothing - // we can do to recover. We could send this to OutWriter if we need. + // we can do to recover. We could send this to ErrWriter if we need. return } w.Flush() From cd230f3a88267fff46df1f1763978494c08c8b29 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 6 May 2016 12:19:01 -0400 Subject: [PATCH 167/381] Update travis config for Go versions - Added Go 1.6 testing - Updated 1.5.x and 1.4.x to latest point releases --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 133722f..1707c50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,9 @@ go: - 1.1.2 - 1.2.2 - 1.3.3 -- 1.4.2 -- 1.5.1 +- 1.4.3 +- 1.5.4 +- 1.6.2 - tip matrix: From e9970b7b13333aba175bb7cb4c3b3ca07e3fba34 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 6 May 2016 12:24:51 -0400 Subject: [PATCH 168/381] Adding Go Report Card badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2ac96fd..c1709ce 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) +[![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli) # cli From 0a3c5e751657e5ab9ff65bedefb07a29cea6a5b9 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 6 May 2016 12:30:30 -0400 Subject: [PATCH 169/381] Letting Travis CI select the patch version of Go 1.4 to use The last release of Go 1.4 when installed on Travis CI does not have go vet installed. Letting Travis CI select the patch version instead. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1707c50..76f38a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ go: - 1.1.2 - 1.2.2 - 1.3.3 -- 1.4.3 +- 1.4 - 1.5.4 - 1.6.2 - tip From e3ace79a91d4333dc0f5cdc5bf5e1a3e58dddd21 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 7 May 2016 16:06:28 -0700 Subject: [PATCH 170/381] Add GlobalBoolT Fixes #206 --- CHANGELOG.md | 1 + context.go | 8 ++++++++ context_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ea0a6..3d07131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## [Unreleased] ### Added - Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` +- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool` ### Changed - `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer diff --git a/context.go b/context.go index ef3d2fc..aad2812 100644 --- a/context.go +++ b/context.go @@ -104,6 +104,14 @@ func (c *Context) GlobalBool(name string) bool { return false } +// Looks up the value of a global bool flag, returns true if no bool flag exists +func (c *Context) GlobalBoolT(name string) bool { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupBoolT(name, fs) + } + return false +} + // Looks up the value of a global string flag, returns "" if no string flag exists func (c *Context) GlobalString(name string) string { if fs := lookupGlobalFlagSet(name, c); fs != nil { diff --git a/context_test.go b/context_test.go index 4c23271..7ba2ebd 100644 --- a/context_test.go +++ b/context_test.go @@ -82,6 +82,30 @@ func TestContext_BoolT(t *testing.T) { expect(t, c.BoolT("myflag"), true) } +func TestContext_GlobalBool(t *testing.T) { + set := flag.NewFlagSet("test", 0) + + globalSet := flag.NewFlagSet("test-global", 0) + globalSet.Bool("myflag", false, "doc") + globalCtx := NewContext(nil, globalSet, nil) + + c := NewContext(nil, set, globalCtx) + expect(t, c.GlobalBool("myflag"), false) + expect(t, c.GlobalBool("nope"), false) +} + +func TestContext_GlobalBoolT(t *testing.T) { + set := flag.NewFlagSet("test", 0) + + globalSet := flag.NewFlagSet("test-global", 0) + globalSet.Bool("myflag", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + + c := NewContext(nil, set, globalCtx) + expect(t, c.GlobalBoolT("myflag"), true) + expect(t, c.GlobalBoolT("nope"), false) +} + func TestContext_Args(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") From 592f1d97e5a73a1f446de8675c01c9dcbfc3f6a5 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 7 May 2016 17:22:09 -0700 Subject: [PATCH 171/381] Exit non-zero if a unknown subcommand is given Currently it just prints the help message and exits 0. We do this by modifying the helpCommand and helpSubcommand cli.Commands to return an error if they are called with an unknown subcommand. This propogates up to the app which exits with 3 and prints the error. Thanks to @danslimmon for the initial approach! Fixes #276 --- CHANGELOG.md | 10 ++++++--- help.go | 32 ++++++++++++++--------------- help_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ea0a6..d4de0b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,15 @@ ### Changed - `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer -quoted in help text output. + quoted in help text output. - All flag types now include `(default: {value})` strings following usage when a -default value can be (reasonably) detected. + default value can be (reasonably) detected. - `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent -with non-slice flag types + with non-slice flag types +- Apps now exit with a code of 3 if an unknown subcommand is specified + (previously they printed "No help topic for...", but still exited 0. This + makes it easier to script around apps built using `cli` since they can trust + that a 0 exit code indicated a successful execution. ## [1.16.0] - 2016-05-02 ### Added diff --git a/help.go b/help.go index 45e8603..259e452 100644 --- a/help.go +++ b/help.go @@ -81,10 +81,10 @@ var helpCommand = Command{ Action: func(c *Context) error { args := c.Args() if args.Present() { - ShowCommandHelp(c, args.First()) - } else { - ShowAppHelp(c) + return ShowCommandHelp(c, args.First()) } + + ShowAppHelp(c) return nil }, } @@ -97,11 +97,10 @@ var helpSubcommand = Command{ Action: func(c *Context) error { args := c.Args() if args.Present() { - ShowCommandHelp(c, args.First()) - } else { - ShowSubcommandHelp(c) + return ShowCommandHelp(c, args.First()) } - return nil + + return ShowSubcommandHelp(c) }, } @@ -127,30 +126,31 @@ func DefaultAppComplete(c *Context) { } // Prints help for the given command -func ShowCommandHelp(ctx *Context, command string) { +func ShowCommandHelp(ctx *Context, command string) error { // show the subcommand help for a command with subcommands if command == "" { HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) - return + return nil } for _, c := range ctx.App.Commands { if c.HasName(command) { HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) - return + return nil } } - if ctx.App.CommandNotFound != nil { - ctx.App.CommandNotFound(ctx, command) - } else { - fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command) + if ctx.App.CommandNotFound == nil { + return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3) } + + ctx.App.CommandNotFound(ctx, command) + return nil } // Prints help for the given subcommand -func ShowSubcommandHelp(c *Context) { - ShowCommandHelp(c, c.Command.Name) +func ShowSubcommandHelp(c *Context) error { + return ShowCommandHelp(c, c.Command.Name) } // Prints the version number of the App diff --git a/help_test.go b/help_test.go index ee5c25c..0fabdba 100644 --- a/help_test.go +++ b/help_test.go @@ -2,6 +2,8 @@ package cli import ( "bytes" + "flag" + "strings" "testing" ) @@ -110,3 +112,59 @@ func Test_Version_Custom_Flags(t *testing.T) { t.Errorf("unexpected output: %s", output.String()) } } + +func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { + app := NewApp() + + set := flag.NewFlagSet("test", 0) + set.Parse([]string{"foo"}) + + c := NewContext(app, set, nil) + + err := helpCommand.Action.(func(*Context) error)(c) + + if err == nil { + t.Fatalf("expected error from helpCommand.Action(), but got nil") + } + + exitErr, ok := err.(*ExitError) + if !ok { + t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + } + + if !strings.HasPrefix(exitErr.Error(), "No help topic for") { + t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error()) + } + + if exitErr.exitCode != 3 { + t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode) + } +} + +func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { + app := NewApp() + + set := flag.NewFlagSet("test", 0) + set.Parse([]string{"foo"}) + + c := NewContext(app, set, nil) + + err := helpSubcommand.Action.(func(*Context) error)(c) + + if err == nil { + t.Fatalf("expected error from helpCommand.Action(), but got nil") + } + + exitErr, ok := err.(*ExitError) + if !ok { + t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + } + + if !strings.HasPrefix(exitErr.Error(), "No help topic for") { + t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error()) + } + + if exitErr.exitCode != 3 { + t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode) + } +} From dfa9a87bee6396f67a8d0b09e5ae47b19b985494 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2016 23:54:12 -0400 Subject: [PATCH 172/381] Add tests for App.VisibleCategories & App.VisibleCommands --- app_test.go | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/app_test.go b/app_test.go index b08abb2..4f054a4 100644 --- a/app_test.go +++ b/app_test.go @@ -304,6 +304,48 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { expect(t, args[2], "notAFlagAtAll") } +func TestApp_VisibleCommands(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + Command{ + Name: "frob", + HelpName: "foo frob", + Action: func(_ *Context) error { return nil }, + }, + Command{ + Name: "frib", + HelpName: "foo frib", + Hidden: true, + Action: func(_ *Context) error { return nil }, + }, + } + + app.Setup() + expected := []Command{ + app.Commands[0], + app.Commands[2], // help + } + actual := app.VisibleCommands() + expect(t, len(expected), len(actual)) + for i, actualCommand := range actual { + expectedCommand := expected[i] + + if expectedCommand.Action != nil { + // comparing func addresses is OK! + expect(t, fmt.Sprintf("%p", expectedCommand.Action), fmt.Sprintf("%p", actualCommand.Action)) + } + + // nil out funcs, as they cannot be compared + // (https://github.com/golang/go/issues/8554) + expectedCommand.Action = nil + actualCommand.Action = nil + + if !reflect.DeepEqual(expectedCommand, actualCommand) { + t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand) + } + } +} + func TestApp_Float64Flag(t *testing.T) { var meters float64 @@ -1129,6 +1171,109 @@ func TestApp_Run_Categories(t *testing.T) { } } +func TestApp_VisibleCategories(t *testing.T) { + app := NewApp() + app.Name = "visible-categories" + app.Commands = []Command{ + Command{ + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + Command{ + Name: "command2", + Category: "2", + HelpName: "foo command2", + }, + Command{ + Name: "command3", + Category: "3", + HelpName: "foo command3", + }, + } + + expected := []*CommandCategory{ + &CommandCategory{ + Name: "2", + Commands: []Command{ + app.Commands[1], + }, + }, + &CommandCategory{ + Name: "3", + Commands: []Command{ + app.Commands[2], + }, + }, + } + + app.Setup() + expect(t, expected, app.VisibleCategories()) + + app = NewApp() + app.Name = "visible-categories" + app.Commands = []Command{ + Command{ + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + Command{ + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + }, + Command{ + Name: "command3", + Category: "3", + HelpName: "foo command3", + }, + } + + expected = []*CommandCategory{ + &CommandCategory{ + Name: "3", + Commands: []Command{ + app.Commands[2], + }, + }, + } + + app.Setup() + expect(t, expected, app.VisibleCategories()) + + app = NewApp() + app.Name = "visible-categories" + app.Commands = []Command{ + Command{ + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + Command{ + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + }, + Command{ + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, + }, + } + + expected = []*CommandCategory{} + + app.Setup() + expect(t, expected, app.VisibleCategories()) +} + func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Action = func(c *Context) error { return nil } From 28eb7b2cc4bf1b4d266fe55fe7d94f958170cdf8 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 8 May 2016 21:03:02 -0700 Subject: [PATCH 173/381] Added Hidden command support to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ea0a6..9834228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ## [Unreleased] ### Added - Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` +- Support for hiding commands by setting `Hidden: true` -- this will hide the + commands in help output ### Changed - `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer From b9f33fbe6deac41734fe1768ab3d5fa73fc743e6 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 9 May 2016 08:41:01 -0400 Subject: [PATCH 174/381] Add a vet/test runner script with coverage on by default --- .gitignore | 1 + .travis.yml | 9 +++---- CHANGELOG.md | 1 + runtests | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100755 runtests diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7823778 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.coverprofile diff --git a/.travis.yml b/.travis.yml index 76f38a4..4f7bdd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: go + sudo: false go: @@ -8,16 +9,14 @@ go: - 1.4 - 1.5.4 - 1.6.2 -- tip +- master matrix: allow_failures: - - go: tip + - go: master before_script: - go get github.com/meatballhat/gfmxr/... script: -- go vet ./... -- go test -v ./... -- gfmxr -c $(grep -c 'package main' README.md) -s README.md +- ./runtests diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d3d9c1..0403912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - `context.GlobalBoolT` was added as an analogue to `context.GlobalBool` - Support for hiding commands by setting `Hidden: true` -- this will hide the commands in help output +- `./runtests` test runner with coverage tracking by default ### Changed - `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer diff --git a/runtests b/runtests new file mode 100755 index 0000000..fe8b013 --- /dev/null +++ b/runtests @@ -0,0 +1,70 @@ +#!/usr/bin/env python +from __future__ import print_function + +import os +import sys +import tempfile + +from subprocess import check_call + + +PACKAGE_NAME = os.environ.get( + 'CLI_PACKAGE_NAME', 'github.com/codegangsta/cli' +) + + +def main(): + _run('go vet ./...'.split()) + + coverprofiles = [] + for subpackage in ['', 'altsrc']: + coverprofile = 'cli.coverprofile' + if subpackage != '': + coverprofile = '{}.coverprofile'.format(subpackage) + + coverprofiles.append(coverprofile) + + _run('go test -v'.split() + [ + '-coverprofile={}'.format(coverprofile), + '{}/{}'.format(PACKAGE_NAME, subpackage) + ]) + + combined = _combine_coverprofiles(coverprofiles) + _run('go tool cover -func={}'.format(combined.name).split()) + combined.close() + + _run(['gfmxr', '-c', str(_gfmxr_count()), '-s', 'README.md']) + return 0 + + +def _run(command): + print('runtests: {}'.format(' '.join(command)), file=sys.stderr) + check_call(command) + + +def _gfmxr_count(): + with open('README.md') as infile: + lines = infile.read().splitlines() + return len(filter(_is_go_runnable, lines)) + + +def _is_go_runnable(line): + return line.startswith('package main') + + +def _combine_coverprofiles(coverprofiles): + combined = tempfile.NamedTemporaryFile(suffix='.coverprofile') + combined.write('mode: set\n') + + for coverprofile in coverprofiles: + with open(coverprofile, 'r') as infile: + for line in infile.readlines(): + if not line.startswith('mode: '): + combined.write(line) + + combined.flush() + return combined + + +if __name__ == '__main__': + sys.exit(main()) From 2df2fa514dddba4672c3b0b585cb72b98d1a8032 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 9 May 2016 08:49:38 -0400 Subject: [PATCH 175/381] Skip coverage bits on < go1.2 --- runtests | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/runtests b/runtests index fe8b013..923a80f 100755 --- a/runtests +++ b/runtests @@ -5,7 +5,7 @@ import os import sys import tempfile -from subprocess import check_call +from subprocess import check_call, check_output PACKAGE_NAME = os.environ.get( @@ -16,22 +16,25 @@ PACKAGE_NAME = os.environ.get( def main(): _run('go vet ./...'.split()) - coverprofiles = [] - for subpackage in ['', 'altsrc']: - coverprofile = 'cli.coverprofile' - if subpackage != '': - coverprofile = '{}.coverprofile'.format(subpackage) - - coverprofiles.append(coverprofile) - - _run('go test -v'.split() + [ - '-coverprofile={}'.format(coverprofile), - '{}/{}'.format(PACKAGE_NAME, subpackage) - ]) - - combined = _combine_coverprofiles(coverprofiles) - _run('go tool cover -func={}'.format(combined.name).split()) - combined.close() + if check_output('go version'.split()).split()[2] >= 'go1.2': + coverprofiles = [] + for subpackage in ['', 'altsrc']: + coverprofile = 'cli.coverprofile' + if subpackage != '': + coverprofile = '{}.coverprofile'.format(subpackage) + + coverprofiles.append(coverprofile) + + _run('go test -v'.split() + [ + '-coverprofile={}'.format(coverprofile), + '{}/{}'.format(PACKAGE_NAME, subpackage) + ]) + + combined = _combine_coverprofiles(coverprofiles) + _run('go tool cover -func={}'.format(combined.name).split()) + combined.close() + else: + _run('go test -v ./...'.split()) _run(['gfmxr', '-c', str(_gfmxr_count()), '-s', 'README.md']) return 0 From d94fdb3e84260d33551531fec6722f863016fc2f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 9 May 2016 08:58:20 -0400 Subject: [PATCH 176/381] Break runtests back into steps for more granular CI feedback --- .travis.yml | 4 +++- runtests | 54 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f7bdd7..b117165 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,6 @@ before_script: - go get github.com/meatballhat/gfmxr/... script: -- ./runtests +- ./runtests vet +- ./runtests test +- ./runtests gfmxr diff --git a/runtests b/runtests index 923a80f..c295f0c 100755 --- a/runtests +++ b/runtests @@ -13,31 +13,49 @@ PACKAGE_NAME = os.environ.get( ) -def main(): - _run('go vet ./...'.split()) +def main(sysargs=sys.argv[:]): + target = 'test' + if len(sysargs) > 1: + target = sysargs[1] - if check_output('go version'.split()).split()[2] >= 'go1.2': - coverprofiles = [] - for subpackage in ['', 'altsrc']: - coverprofile = 'cli.coverprofile' - if subpackage != '': - coverprofile = '{}.coverprofile'.format(subpackage) + { + 'vet': _vet, + 'test': _test, + 'gfmxr': _gfmxr + }[target]() - coverprofiles.append(coverprofile) + return 0 - _run('go test -v'.split() + [ - '-coverprofile={}'.format(coverprofile), - '{}/{}'.format(PACKAGE_NAME, subpackage) - ]) - combined = _combine_coverprofiles(coverprofiles) - _run('go tool cover -func={}'.format(combined.name).split()) - combined.close() - else: +def _test(): + if check_output('go version'.split()).split()[2] < 'go1.2': _run('go test -v ./...'.split()) + return + + coverprofiles = [] + for subpackage in ['', 'altsrc']: + coverprofile = 'cli.coverprofile' + if subpackage != '': + coverprofile = '{}.coverprofile'.format(subpackage) + + coverprofiles.append(coverprofile) + + _run('go test -v'.split() + [ + '-coverprofile={}'.format(coverprofile), + '{}/{}'.format(PACKAGE_NAME, subpackage) + ]) + + combined = _combine_coverprofiles(coverprofiles) + _run('go tool cover -func={}'.format(combined.name).split()) + combined.close() + +def _gfmxr(): _run(['gfmxr', '-c', str(_gfmxr_count()), '-s', 'README.md']) - return 0 + + +def _vet(): + _run('go vet ./...'.split()) def _run(command): From 5fa09b8e23737d63a6b7c51cde75248743067145 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 9 May 2016 09:00:22 -0400 Subject: [PATCH 177/381] Strip trailing slash for cleanliness --- runtests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtests b/runtests index c295f0c..162e23e 100755 --- a/runtests +++ b/runtests @@ -42,7 +42,7 @@ def _test(): _run('go test -v'.split() + [ '-coverprofile={}'.format(coverprofile), - '{}/{}'.format(PACKAGE_NAME, subpackage) + ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') ]) combined = _combine_coverprofiles(coverprofiles) From 2f4ec312644a4d92160042be8db056fe67475b7c Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 9 May 2016 09:40:09 -0400 Subject: [PATCH 178/381] Fixed spelling and gofmt issues --- app.go | 2 +- app_test.go | 52 ++++++++++++++++++++++++------------------------- command_test.go | 4 ++-- context_test.go | 2 +- help_test.go | 4 ++-- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/app.go b/app.go index b4ad8dd..2e6b771 100644 --- a/app.go +++ b/app.go @@ -51,7 +51,7 @@ type App struct { HideHelp bool // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool - // Populate on app startup, only gettable throught method Categories() + // Populate on app startup, only gettable through method Categories() categories CommandCategories // An action to execute when the bash-completion flag is set BashComplete BashCompleteFunc diff --git a/app_test.go b/app_test.go index 4f054a4..42c852e 100644 --- a/app_test.go +++ b/app_test.go @@ -33,7 +33,7 @@ func ExampleApp_Run() { app.UsageText = "app [first_arg] [second_arg]" app.Author = "Harrison" app.Email = "harrison@lolwut.com" - app.Authors = []Author{Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} app.Run(os.Args) // Output: // Hello Jeremy @@ -307,12 +307,12 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { func TestApp_VisibleCommands(t *testing.T) { app := NewApp() app.Commands = []Command{ - Command{ + { Name: "frob", HelpName: "foo frob", Action: func(_ *Context) error { return nil }, }, - Command{ + { Name: "frib", HelpName: "foo frib", Hidden: true, @@ -517,7 +517,7 @@ func TestApp_BeforeFunc(t *testing.T) { } app.Commands = []Command{ - Command{ + { Name: "sub", Action: func(c *Context) error { counts.Total++ @@ -609,7 +609,7 @@ func TestApp_AfterFunc(t *testing.T) { } app.Commands = []Command{ - Command{ + { Name: "sub", Action: func(c *Context) error { counts.Total++ @@ -724,7 +724,7 @@ func TestApp_CommandNotFound(t *testing.T) { } app.Commands = []Command{ - Command{ + { Name: "bar", Action: func(c *Context) error { counts.Total++ @@ -791,7 +791,7 @@ func TestApp_OrderOfOperations(t *testing.T) { app.After = afterNoError app.Commands = []Command{ - Command{ + { Name: "bar", Action: func(c *Context) error { counts.Total++ @@ -1126,15 +1126,15 @@ func TestApp_Run_Categories(t *testing.T) { app := NewApp() app.Name = "categories" app.Commands = []Command{ - Command{ + { Name: "command1", Category: "1", }, - Command{ + { Name: "command2", Category: "1", }, - Command{ + { Name: "command3", Category: "2", }, @@ -1175,18 +1175,18 @@ func TestApp_VisibleCategories(t *testing.T) { app := NewApp() app.Name = "visible-categories" app.Commands = []Command{ - Command{ + { Name: "command1", Category: "1", HelpName: "foo command1", Hidden: true, }, - Command{ + { Name: "command2", Category: "2", HelpName: "foo command2", }, - Command{ + { Name: "command3", Category: "3", HelpName: "foo command3", @@ -1194,13 +1194,13 @@ func TestApp_VisibleCategories(t *testing.T) { } expected := []*CommandCategory{ - &CommandCategory{ + { Name: "2", Commands: []Command{ app.Commands[1], }, }, - &CommandCategory{ + { Name: "3", Commands: []Command{ app.Commands[2], @@ -1214,19 +1214,19 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" app.Commands = []Command{ - Command{ + { Name: "command1", Category: "1", HelpName: "foo command1", Hidden: true, }, - Command{ + { Name: "command2", Category: "2", HelpName: "foo command2", Hidden: true, }, - Command{ + { Name: "command3", Category: "3", HelpName: "foo command3", @@ -1234,7 +1234,7 @@ func TestApp_VisibleCategories(t *testing.T) { } expected = []*CommandCategory{ - &CommandCategory{ + { Name: "3", Commands: []Command{ app.Commands[2], @@ -1248,19 +1248,19 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" app.Commands = []Command{ - Command{ + { Name: "command1", Category: "1", HelpName: "foo command1", Hidden: true, }, - Command{ + { Name: "command2", Category: "2", HelpName: "foo command2", Hidden: true, }, - Command{ + { Name: "command3", Category: "3", HelpName: "foo command3", @@ -1296,9 +1296,9 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Commands = []Command{ - Command{ + { Subcommands: []Command{ - Command{ + { Name: "sub", }, }, @@ -1336,7 +1336,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { return errors.New("intercepted: " + err.Error()) } app.Commands = []Command{ - Command{ + { Name: "bar", }, } @@ -1366,7 +1366,7 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { return errors.New("intercepted: " + err.Error()) } app.Commands = []Command{ - Command{ + { Name: "bar", }, } diff --git a/command_test.go b/command_test.go index 2687212..6608254 100644 --- a/command_test.go +++ b/command_test.go @@ -49,7 +49,7 @@ func TestCommandFlagParsing(t *testing.T) { func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Commands = []Command{ - Command{ + { Name: "bar", Before: func(c *Context) error { return fmt.Errorf("before error") @@ -76,7 +76,7 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() app.Commands = []Command{ - Command{ + { Name: "bar", Flags: []Flag{ IntFlag{Name: "flag"}, diff --git a/context_test.go b/context_test.go index 7ba2ebd..28d4884 100644 --- a/context_test.go +++ b/context_test.go @@ -199,7 +199,7 @@ func TestContext_GlobalFlagsInSubcommands(t *testing.T) { } app.Commands = []Command{ - Command{ + { Name: "foo", Flags: []Flag{ BoolFlag{Name: "parent, p", Usage: "Parent flag"}, diff --git a/help_test.go b/help_test.go index c5372e4..54836d7 100644 --- a/help_test.go +++ b/help_test.go @@ -172,13 +172,13 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ Commands: []Command{ - Command{ + { Name: "frobbly", Action: func(ctx *Context) error { return nil }, }, - Command{ + { Name: "secretfrob", Hidden: true, Action: func(ctx *Context) error { From 2f110bd745262bfaa4db2248e431a45b5a838c44 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 9 May 2016 10:12:59 -0400 Subject: [PATCH 179/381] Cleaned up exported func comments per golint --- app.go | 11 +++++--- category.go | 3 ++ command.go | 8 ++++-- context.go | 79 +++++++++++++++++++++++++++++++---------------------- errors.go | 4 +++ flag.go | 21 ++++++++++---- funcs.go | 16 +++++------ help.go | 24 +++++++++------- 8 files changed, 103 insertions(+), 63 deletions(-) diff --git a/app.go b/app.go index 2e6b771..6d1dd7b 100644 --- a/app.go +++ b/app.go @@ -100,7 +100,8 @@ func compileTime() time.Time { return info.ModTime() } -// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. +// NewApp creates a new cli Application with some reasonable defaults for Name, +// Usage, Version and Action. func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), @@ -162,7 +163,8 @@ func (a *App) Setup() { } } -// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination +// Run is the entry point to the cli app. Parses the arguments slice and routes +// to the proper flag/args combination func (a *App) Run(arguments []string) (err error) { a.Setup() @@ -254,7 +256,8 @@ func (a *App) RunAndExitOnError() { } } -// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags +// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to +// generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { // append help to commands if len(a.Commands) > 0 { @@ -363,7 +366,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { return err } -// Returns the named command on App. Returns nil if the command does not exist +// Command returns the named command on App. Returns nil if the command does not exist func (a *App) Command(name string) *Command { for _, c := range a.Commands { if c.HasName(name) { diff --git a/category.go b/category.go index 5c3d4b5..1a60550 100644 --- a/category.go +++ b/category.go @@ -1,7 +1,9 @@ package cli +// CommandCategories is a slice of *CommandCategory. type CommandCategories []*CommandCategory +// CommandCategory is a category containing commands. type CommandCategory struct { Name string Commands Commands @@ -19,6 +21,7 @@ func (c CommandCategories) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +// AddCommand adds a command to a category. func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { for _, commandCategory := range c { if commandCategory.Name == category { diff --git a/command.go b/command.go index 09a9464..d40ca85 100644 --- a/command.go +++ b/command.go @@ -56,7 +56,7 @@ type Command struct { commandNamePath []string } -// Returns the full name of the command. +// FullName returns the full name of the command. // For subcommands this ensures that parent commands are part of the command path func (c Command) FullName() string { if c.commandNamePath == nil { @@ -65,9 +65,10 @@ func (c Command) FullName() string { return strings.Join(c.commandNamePath, " ") } +// Commands is a slice of Command type Commands []Command -// Invokes the command given the context, parses ctx.Args() to generate command-specific flags +// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags func (c Command) Run(ctx *Context) (err error) { if len(c.Subcommands) > 0 { return c.startApp(ctx) @@ -191,6 +192,7 @@ func (c Command) Run(ctx *Context) (err error) { return err } +// Names returns the names including short names and aliases. func (c Command) Names() []string { names := []string{c.Name} @@ -201,7 +203,7 @@ func (c Command) Names() []string { return append(names, c.Aliases...) } -// Returns true if Command.Name or Command.ShortName matches given name +// HasName returns true if Command.Name or Command.ShortName matches given name func (c Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { diff --git a/context.go b/context.go index aad2812..c342463 100644 --- a/context.go +++ b/context.go @@ -21,57 +21,62 @@ type Context struct { parentContext *Context } -// Creates a new context. For use in when invoking an App or Command action. +// NewContext creates a new context. For use in when invoking an App or Command action. func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { return &Context{App: app, flagSet: set, parentContext: parentCtx} } -// Looks up the value of a local int flag, returns 0 if no int flag exists +// Int looks up the value of a local int flag, returns 0 if no int flag exists 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 +// Duration 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 +// Float64 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) } -// Looks up the value of a local bool flag, returns false if no bool flag exists +// Bool looks up the value of a local bool flag, returns false if no bool flag exists func (c *Context) Bool(name string) bool { return lookupBool(name, c.flagSet) } -// Looks up the value of a local boolT flag, returns false if no bool flag exists +// BoolT looks up the value of a local boolT flag, returns false if no bool flag exists func (c *Context) BoolT(name string) bool { return lookupBoolT(name, c.flagSet) } -// Looks up the value of a local string flag, returns "" if no string flag exists +// String looks up the value of a local string flag, returns "" if no string flag exists func (c *Context) String(name string) string { return lookupString(name, c.flagSet) } -// Looks up the value of a local string slice flag, returns nil if no string slice flag exists +// StringSlice looks up the value of a local string slice flag, returns nil if no +// string slice flag exists func (c *Context) StringSlice(name string) []string { return lookupStringSlice(name, c.flagSet) } -// Looks up the value of a local int slice flag, returns nil if no int slice flag exists +// IntSlice looks up the value of a local int slice flag, returns nil if no int +// slice flag exists func (c *Context) IntSlice(name string) []int { return lookupIntSlice(name, c.flagSet) } -// Looks up the value of a local generic flag, returns nil if no generic flag exists +// Generic looks up the value of a local generic flag, returns nil if no generic +// flag exists func (c *Context) Generic(name string) interface{} { return lookupGeneric(name, c.flagSet) } -// Looks up the value of a global int flag, returns 0 if no int flag exists +// GlobalInt looks up the value of a global int flag, returns 0 if no int flag exists func (c *Context) GlobalInt(name string) int { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupInt(name, fs) @@ -79,8 +84,8 @@ func (c *Context) GlobalInt(name string) int { return 0 } -// Looks up the value of a global float64 flag, returns float64(0) if no float64 -// flag exists +// GlobalFloat64 looks up the value of a global float64 flag, returns float64(0) +// if no float64 flag exists func (c *Context) GlobalFloat64(name string) float64 { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupFloat64(name, fs) @@ -88,7 +93,8 @@ func (c *Context) GlobalFloat64(name string) float64 { return float64(0) } -// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists +// GlobalDuration 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 { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupDuration(name, fs) @@ -96,7 +102,8 @@ func (c *Context) GlobalDuration(name string) time.Duration { return 0 } -// Looks up the value of a global bool flag, returns false if no bool flag exists +// GlobalBool looks up the value of a global bool flag, returns false if no bool +// flag exists func (c *Context) GlobalBool(name string) bool { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupBool(name, fs) @@ -104,7 +111,8 @@ func (c *Context) GlobalBool(name string) bool { return false } -// Looks up the value of a global bool flag, returns true if no bool flag exists +// GlobalBoolT looks up the value of a global bool flag, returns true if no bool +// flag exists func (c *Context) GlobalBoolT(name string) bool { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupBoolT(name, fs) @@ -112,7 +120,8 @@ func (c *Context) GlobalBoolT(name string) bool { return false } -// Looks up the value of a global string flag, returns "" if no string flag exists +// GlobalString looks up the value of a global string flag, returns "" if no +// string flag exists func (c *Context) GlobalString(name string) string { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupString(name, fs) @@ -120,7 +129,8 @@ func (c *Context) GlobalString(name string) string { return "" } -// Looks up the value of a global string slice flag, returns nil if no string slice flag exists +// GlobalStringSlice looks up the value of a global string slice flag, returns +// nil if no string slice flag exists func (c *Context) GlobalStringSlice(name string) []string { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupStringSlice(name, fs) @@ -128,7 +138,8 @@ func (c *Context) GlobalStringSlice(name string) []string { return nil } -// Looks up the value of a global int slice flag, returns nil if no int slice flag exists +// GlobalIntSlice looks up the value of a global int slice flag, returns nil if +// no int slice flag exists func (c *Context) GlobalIntSlice(name string) []int { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupIntSlice(name, fs) @@ -136,7 +147,8 @@ func (c *Context) GlobalIntSlice(name string) []int { return nil } -// Looks up the value of a global generic flag, returns nil if no generic flag exists +// GlobalGeneric looks up the value of a global generic flag, returns nil if no +// generic flag exists func (c *Context) GlobalGeneric(name string) interface{} { if fs := lookupGlobalFlagSet(name, c); fs != nil { return lookupGeneric(name, fs) @@ -144,7 +156,7 @@ func (c *Context) GlobalGeneric(name string) interface{} { return nil } -// Returns the number of flags set +// NumFlags returns the number of flags set func (c *Context) NumFlags() int { return c.flagSet.NFlag() } @@ -159,7 +171,7 @@ func (c *Context) GlobalSet(name, value string) error { return globalContext(c).flagSet.Set(name, value) } -// Determines if the flag was actually set +// IsSet determines if the flag was actually set func (c *Context) IsSet(name string) bool { if c.setFlags == nil { c.setFlags = make(map[string]bool) @@ -170,7 +182,7 @@ func (c *Context) IsSet(name string) bool { return c.setFlags[name] == true } -// Determines if the global flag was actually set +// GlobalIsSet 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) @@ -187,7 +199,7 @@ func (c *Context) GlobalIsSet(name string) bool { return c.globalSetFlags[name] } -// Returns a slice of flag names used in this context. +// FlagNames 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] @@ -199,7 +211,7 @@ func (c *Context) FlagNames() (names []string) { return } -// Returns a slice of global flag names used by the app. +// GlobalFlagNames 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] @@ -211,25 +223,26 @@ func (c *Context) GlobalFlagNames() (names []string) { return } -// Returns the parent context, if any +// Parent returns the parent context, if any func (c *Context) Parent() *Context { return c.parentContext } +// Args contains apps console arguments type Args []string -// Returns the command line arguments associated with the context. +// Args returns the command line arguments associated with the context. func (c *Context) Args() Args { args := Args(c.flagSet.Args()) return args } -// Returns the number of the command line arguments. +// NArg returns the number of the command line arguments. func (c *Context) NArg() int { return len(c.Args()) } -// Returns the nth argument, or else a blank string +// Get returns the nth argument, or else a blank string func (a Args) Get(n int) string { if len(a) > n { return a[n] @@ -237,12 +250,12 @@ func (a Args) Get(n int) string { return "" } -// Returns the first argument, or else a blank string +// First returns the first argument, or else a blank string func (a Args) First() string { return a.Get(0) } -// Return the rest of the arguments (not the first one) +// Tail returns the rest of the arguments (not the first one) // or else an empty string slice func (a Args) Tail() []string { if len(a) >= 2 { @@ -251,12 +264,12 @@ func (a Args) Tail() []string { return []string{} } -// Checks if there are any arguments present +// Present checks if there are any arguments present func (a Args) Present() bool { return len(a) != 0 } -// Swaps arguments at the given indexes +// Swap swaps arguments at the given indexes func (a Args) Swap(from, to int) error { if from >= len(a) || to >= len(a) { return errors.New("index out of range") diff --git a/errors.go b/errors.go index db46a83..ea551be 100644 --- a/errors.go +++ b/errors.go @@ -7,20 +7,24 @@ import ( "strings" ) +// OsExiter is the function used when the app exits. If not set defaults to os.Exit. var OsExiter = os.Exit // ErrWriter is used to write errors to the user. This can be anything // implementing the io.Writer interface and defaults to os.Stderr. var ErrWriter io.Writer = os.Stderr +// MultiError is an error that wraps multiple errors. type MultiError struct { Errors []error } +// NewMultiError creates a new MultiError. Pass in one or more errors. func NewMultiError(err ...error) MultiError { return MultiError{Errors: err} } +// Error implents the error interface. func (m MultiError) Error() string { errs := make([]string, len(m.Errors)) for i, err := range m.Errors { diff --git a/flag.go b/flag.go index 8354de0..4c3d174 100644 --- a/flag.go +++ b/flag.go @@ -13,19 +13,19 @@ import ( const defaultPlaceholder = "value" -// This flag enables bash-completion for all commands and subcommands +// BashCompletionFlag enables bash-completion for all commands and subcommands var BashCompletionFlag = BoolFlag{ Name: "generate-bash-completion", Hidden: true, } -// This flag prints the version for the application +// VersionFlag prints the version for the application var VersionFlag = BoolFlag{ Name: "version, v", Usage: "print the version", } -// This flag prints the help for all commands and subcommands +// HelpFlag 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{ @@ -33,6 +33,8 @@ var HelpFlag = BoolFlag{ Usage: "show help", } +// FlagStringer converts a flag definition to a string. This is used by help +// to display a flag. var FlagStringer FlagStringFunc = stringifyFlag // Flag is a common interface related to parsing flags in cli. @@ -103,6 +105,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of a flag. func (f GenericFlag) GetName() string { return f.Name } @@ -126,7 +129,7 @@ func (f *StringSlice) Value() []string { return *f } -// StringSlice is a string flag that can be specified multiple times on the +// StringSliceFlag is a string flag that can be specified multiple times on the // command-line type StringSliceFlag struct { Name string @@ -166,11 +169,12 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of a flag. func (f StringSliceFlag) GetName() string { return f.Name } -// StringSlice is an opaque type for []int to satisfy flag.Value +// IntSlice is an opaque type for []int to satisfy flag.Value type IntSlice []int // Set parses the value into an integer and appends it to the list of values @@ -237,6 +241,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of the flag. func (f IntSliceFlag) GetName() string { return f.Name } @@ -280,6 +285,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of the flag. func (f BoolFlag) GetName() string { return f.Name } @@ -324,6 +330,7 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of the flag. func (f BoolTFlag) GetName() string { return f.Name } @@ -364,6 +371,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of the flag. func (f StringFlag) GetName() string { return f.Name } @@ -408,6 +416,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of the flag. func (f IntFlag) GetName() string { return f.Name } @@ -452,6 +461,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of the flag. func (f DurationFlag) GetName() string { return f.Name } @@ -495,6 +505,7 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { }) } +// GetName returns the name of the flag. func (f Float64Flag) GetName() string { return f.Name } diff --git a/funcs.go b/funcs.go index 6b2a846..cba5e6c 100644 --- a/funcs.go +++ b/funcs.go @@ -1,23 +1,23 @@ package cli -// An action to execute when the bash-completion flag is set +// BashCompleteFunc is an action to execute when the bash-completion flag is set type BashCompleteFunc func(*Context) -// 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 +// BeforeFunc is 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 type BeforeFunc func(*Context) error -// An action to execute after any subcommands are run, but after the subcommand has finished -// It is run even if Action() panics +// AfterFunc is an action to execute after any subcommands are run, but after the +// subcommand has finished it is run even if Action() panics type AfterFunc func(*Context) error -// The action to execute when no subcommands are specified +// ActionFunc is the action to execute when no subcommands are specified type ActionFunc func(*Context) error -// Execute this function if the proper command cannot be found +// CommandNotFoundFunc is executed if the proper command cannot be found type CommandNotFoundFunc func(*Context, string) -// Execute this function if an usage error occurs. This is useful for displaying +// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying // customized usage error messages. This function is able to replace the // original error messages. If this function is not set, the "Incorrect usage" // is displayed and the execution is interrupted. diff --git a/help.go b/help.go index 6591e8b..801d2b1 100644 --- a/help.go +++ b/help.go @@ -9,7 +9,7 @@ import ( "text/template" ) -// The text template for the Default help topic. +// AppHelpTemplate is the text template for the Default help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var AppHelpTemplate = `NAME: @@ -36,7 +36,7 @@ COPYRIGHT: {{end}} ` -// The text template for the command help topic. +// CommandHelpTemplate is the text template for the command help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var CommandHelpTemplate = `NAME: @@ -56,7 +56,7 @@ OPTIONS: {{end}}{{end}} ` -// The text template for the subcommand help topic. +// SubcommandHelpTemplate is the text template for the subcommand help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var SubcommandHelpTemplate = `NAME: @@ -108,16 +108,20 @@ var helpSubcommand = Command{ // Prints help for the App or Command type helpPrinter func(w io.Writer, templ string, data interface{}) +// HelpPrinter is a function that writes the help output. If not set a default +// is used. The function signature is: +// func(w io.Writer, templ string, data interface{}) var HelpPrinter helpPrinter = printHelp -// Prints version for the App +// VersionPrinter prints the version for the App var VersionPrinter = printVersion +// ShowAppHelp is an action that displays the help. func ShowAppHelp(c *Context) { HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) } -// Prints the list of subcommands as the default app completion method +// DefaultAppComplete prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { for _, command := range c.App.Commands { if command.Hidden { @@ -129,7 +133,7 @@ func DefaultAppComplete(c *Context) { } } -// Prints help for the given command +// ShowCommandHelp prints help for the given command func ShowCommandHelp(ctx *Context, command string) error { // show the subcommand help for a command with subcommands if command == "" { @@ -152,12 +156,12 @@ func ShowCommandHelp(ctx *Context, command string) error { return nil } -// Prints help for the given subcommand +// ShowSubcommandHelp prints help for the given subcommand func ShowSubcommandHelp(c *Context) error { return ShowCommandHelp(c, c.Command.Name) } -// Prints the version number of the App +// ShowVersion prints the version number of the App func ShowVersion(c *Context) { VersionPrinter(c) } @@ -166,7 +170,7 @@ func printVersion(c *Context) { fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) } -// Prints the lists of commands within a given context +// ShowCompletions prints the lists of commands within a given context func ShowCompletions(c *Context) { a := c.App if a != nil && a.BashComplete != nil { @@ -174,7 +178,7 @@ func ShowCompletions(c *Context) { } } -// Prints the custom completions for a given command +// ShowCommandCompletions prints the custom completions for a given command func ShowCommandCompletions(ctx *Context, command string) { c := ctx.App.Command(command) if c != nil && c.BashComplete != nil { From 07ce8bf79caaf9b29b7b96daae8b6462da41ca2b Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 9 May 2016 10:15:05 -0400 Subject: [PATCH 180/381] Cleaned up else per golint When an if ends in a return the else is not required. golint detects these conditions and found these. --- app.go | 14 ++++++-------- command.go | 9 ++++----- flag.go | 3 +-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/app.go b/app.go index 6d1dd7b..7c9b958 100644 --- a/app.go +++ b/app.go @@ -189,11 +189,10 @@ func (a *App) Run(arguments []string) (err error) { err := a.OnUsageError(context, err, false) HandleExitCoder(err) return err - } else { - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") - ShowAppHelp(context) - return err } + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + ShowAppHelp(context) + return err } if !a.HideHelp && checkHelp(context) { @@ -310,11 +309,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { err = a.OnUsageError(context, err, true) HandleExitCoder(err) return err - } else { - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") - ShowSubcommandHelp(context) - return err } + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + ShowSubcommandHelp(context) + return err } if len(a.Commands) > 0 { diff --git a/command.go b/command.go index d40ca85..8950cca 100644 --- a/command.go +++ b/command.go @@ -132,12 +132,11 @@ func (c Command) Run(ctx *Context) (err error) { err := c.OnUsageError(ctx, err, false) HandleExitCoder(err) return err - } else { - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return err } + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err } nerr := normalizeFlags(c.Flags, set) diff --git a/flag.go b/flag.go index 4c3d174..1e8112e 100644 --- a/flag.go +++ b/flag.go @@ -182,9 +182,8 @@ func (f *IntSlice) Set(value string) error { tmp, err := strconv.Atoi(value) if err != nil { return err - } else { - *f = append(*f, tmp) } + *f = append(*f, tmp) return nil } From 2fcbd9d7298be44cf622c4906cd1318ba6ff785f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 9 May 2016 10:52:31 -0400 Subject: [PATCH 181/381] Preparing for v1.17.0 release --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d3d9c1..f623e59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ **ATTN**: This project uses [semantic versioning](http://semver.org/). ## [Unreleased] + +## [1.17.0] - 2016-05-09 ### Added - Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` - `context.GlobalBoolT` was added as an analogue to `context.GlobalBool` @@ -20,6 +22,8 @@ (previously they printed "No help topic for...", but still exited 0. This makes it easier to script around apps built using `cli` since they can trust that a 0 exit code indicated a successful execution. +- cleanups based on [Go Report Card + feedback](https://goreportcard.com/report/github.com/codegangsta/cli) ## [1.16.0] - 2016-05-02 ### Added @@ -279,7 +283,8 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/codegangsta/cli/compare/v1.16.0...HEAD +[Unreleased]: https://github.com/codegangsta/cli/compare/v1.17.0...HEAD +[1.17.0]: https://github.com/codegangsta/cli/compare/v1.16.0...v1.17.0 [1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0 [1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0 [1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 From 33f5de5f18e5bc0703d867bcc356a24853ef2e6a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 10 May 2016 08:09:27 -0400 Subject: [PATCH 182/381] Move the `./runtests` changelog entry to up `Unreleased` --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7416fe5..c974c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ **ATTN**: This project uses [semantic versioning](http://semver.org/). ## [Unreleased] +### Added +- `./runtests` test runner with coverage tracking by default ## [1.17.0] - 2016-05-09 ### Added @@ -10,7 +12,6 @@ - `context.GlobalBoolT` was added as an analogue to `context.GlobalBool` - Support for hiding commands by setting `Hidden: true` -- this will hide the commands in help output -- `./runtests` test runner with coverage tracking by default ### Changed - `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer From 139815765414d09bc7e7de2c9abb8bd9398a5130 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 10 May 2016 08:16:33 -0400 Subject: [PATCH 183/381] Use argparse in runtests like Zeus intended --- runtests | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/runtests b/runtests index 162e23e..feacff3 100755 --- a/runtests +++ b/runtests @@ -1,6 +1,7 @@ #!/usr/bin/env python from __future__ import print_function +import argparse import os import sys import tempfile @@ -14,16 +15,19 @@ PACKAGE_NAME = os.environ.get( def main(sysargs=sys.argv[:]): - target = 'test' - if len(sysargs) > 1: - target = sysargs[1] - - { + targets = { 'vet': _vet, 'test': _test, 'gfmxr': _gfmxr - }[target]() + } + + parser = argparse.ArgumentParser() + parser.add_argument( + 'target', nargs='?', choices=tuple(targets.keys()), default='test' + ) + args = parser.parse_args(sysargs[1:]) + targets[args.target]() return 0 From b9d96954ca2d0794de7a1e5c8580e83ad43a12aa Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 10 May 2016 13:41:43 -0400 Subject: [PATCH 184/381] Fix command alias printing in help text Closes #405 --- CHANGELOG.md | 3 +++ help.go | 4 +-- help_test.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c974c72..87a3ed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### Added - `./runtests` test runner with coverage tracking by default +### Fixed +- Printing of command aliases in help text + ## [1.17.0] - 2016-05-09 ### Added - Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` diff --git a/help.go b/help.go index 801d2b1..a9e7327 100644 --- a/help.go +++ b/help.go @@ -26,7 +26,7 @@ AUTHOR(S): {{end}}{{if .VisibleCommands}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{end}}{{range .VisibleCommands}} - {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} {{end}}{{end}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range .VisibleFlags}}{{.}} @@ -67,7 +67,7 @@ USAGE: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{end}}{{range .VisibleCommands}} - {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} {{end}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} diff --git a/help_test.go b/help_test.go index 54836d7..6ed525b 100644 --- a/help_test.go +++ b/help_test.go @@ -169,6 +169,76 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { } } +func TestShowAppHelp_CommandAliases(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Aliases: []string{"fr", "frob"}, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "--help"}) + + if !strings.Contains(output.String(), "frobbly, fr, frob") { + t.Errorf("expected output to include all command aliases; got: %q", output.String()) + } +} + +func TestShowCommandHelp_CommandAliases(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Aliases: []string{"fr", "frob", "bork"}, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "help", "fr"}) + + if !strings.Contains(output.String(), "frobbly") { + t.Errorf("expected output to include command name; got: %q", output.String()) + } + + if strings.Contains(output.String(), "bork") { + t.Errorf("expected output to exclude command aliases; got: %q", output.String()) + } +} + +func TestShowSubcommandHelp_CommandAliases(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Aliases: []string{"fr", "frob", "bork"}, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "help"}) + + if !strings.Contains(output.String(), "frobbly, fr, frob, bork") { + t.Errorf("expected output to include command name; got: %q", output.String()) + } +} + func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ Commands: []Command{ From 6f3bb94eae46a2a81011105648c056ffa471e659 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 10 May 2016 13:51:54 -0400 Subject: [PATCH 185/381] Correct assertion text --- help_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help_test.go b/help_test.go index 6ed525b..a664865 100644 --- a/help_test.go +++ b/help_test.go @@ -235,7 +235,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) { app.Run([]string{"foo", "help"}) if !strings.Contains(output.String(), "frobbly, fr, frob, bork") { - t.Errorf("expected output to include command name; got: %q", output.String()) + t.Errorf("expected output to include all command aliases; got: %q", output.String()) } } From 50b384d9044c17276f2369d333d765a8b01c3bea Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 10 May 2016 14:40:56 -0400 Subject: [PATCH 186/381] Separate badges for package coverage --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c1709ce..409b338 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) [![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) [![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli) +[![top level coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) / +[![altsrc coverage](http://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc) + # cli From 92897b9bf6639511e0830b4fc5e9c1c5e796fc34 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Tue, 10 May 2016 20:42:38 -0700 Subject: [PATCH 187/381] Updating coverage badges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 409b338..bffd052 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) [![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli) -[![top level coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) / -[![altsrc coverage](http://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc) +[![top level coverage](https://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) / +[![altsrc coverage](https://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc) # cli From 1beb5f66cd28d7d39c93bace3a5550ab8e454dc3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 16 May 2016 10:43:05 -0400 Subject: [PATCH 188/381] Add some docs about the `v2` branch --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index bffd052..0f9aa33 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,27 @@ Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands c export PATH=$PATH:$GOPATH/bin ``` +### Using the `v2` branch + +There is currently a long-lived branch named `v2` that is intended to land as +the new `master` branch once development there has settled down. The current +`master` branch (what will become `v1`) is being manually merged into `v2` on an +irregular human schedule, but generally if one wants to "upgrade" to `v2` *now* +and accept the volatility (read: "awesomeness") that comes along with that, +please use whatever version pinning of your preference, such as via `gopkg.in`: + +``` +$ go get gopkg.in/codegangsta/cli.v2 +``` + +``` go +... +import ( + "gopkg.in/codegangsta/cli.v2" // imports as package "cli" +) +... +``` + ## Getting Started One of the philosophies behind cli is that an API should be playful and full of discovery. So a cli app can be as little as one line of code in `main()`. From 8af550936f59d101e8173a23cd41f8202f6bd67a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 16 May 2016 10:46:43 -0400 Subject: [PATCH 189/381] Add another bit about pinning to `v1` --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 0f9aa33..ac59994 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,24 @@ import ( ... ``` +### Pinning to the `v1` branch + +Similarly to the section above describing use of the `v2` branch, if one wants +to avoid any unexpected compatibility pains once `v2` becomes `master`, then +pinning to the `v1` branch is an acceptable option, e.g.: + +``` +$ go get gopkg.in/codegangsta/cli.v1 +``` + +``` go +... +import ( + "gopkg.in/codegangsta/cli.v1" // imports as package "cli" +) +... +``` + ## Getting Started One of the philosophies behind cli is that an API should be playful and full of discovery. So a cli app can be as little as one line of code in `main()`. From 61e5e9362f9657038caf04eea2e2c2f84901a7c0 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 16 May 2016 10:48:21 -0400 Subject: [PATCH 190/381] Clarify state of `v1` branch --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ac59994..e5b01bd 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ export PATH=$PATH:$GOPATH/bin There is currently a long-lived branch named `v2` that is intended to land as the new `master` branch once development there has settled down. The current -`master` branch (what will become `v1`) is being manually merged into `v2` on an -irregular human schedule, but generally if one wants to "upgrade" to `v2` *now* -and accept the volatility (read: "awesomeness") that comes along with that, -please use whatever version pinning of your preference, such as via `gopkg.in`: +`master` branch (current mirrored as `v1`) is being manually merged into `v2` on +an irregular human-based schedule, but generally if one wants to "upgrade" to +`v2` *now* and accept the volatility (read: "awesomeness") that comes along with +that, please use whatever version pinning of your preference, such as via +`gopkg.in`: ``` $ go get gopkg.in/codegangsta/cli.v2 From 0eb4e0be6c214f8904ef6989b11072c7b897c657 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 17 May 2016 03:33:51 -0400 Subject: [PATCH 191/381] TRIVIAL removal of extra "current" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5b01bd..a5bab32 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ export PATH=$PATH:$GOPATH/bin There is currently a long-lived branch named `v2` that is intended to land as the new `master` branch once development there has settled down. The current -`master` branch (current mirrored as `v1`) is being manually merged into `v2` on +`master` branch (mirrored as `v1`) is being manually merged into `v2` on an irregular human-based schedule, but generally if one wants to "upgrade" to `v2` *now* and accept the volatility (read: "awesomeness") that comes along with that, please use whatever version pinning of your preference, such as via From 00bd2a95b20c78f87919881efc9368a4aa596423 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 21 May 2016 08:36:07 -0400 Subject: [PATCH 192/381] Do not test ./altsrc on go1.1 as the YAML library requires go1.2+ --- README.md | 5 ++++- runtests | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5bab32..eb62b2c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,10 @@ Command line apps are usually so tiny that there is absolutely no reason why you ## Installation -Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). +Make sure you have a working Go environment. Go version 1.1+ is required for +core cli, whereas use of the [`./altsrc`](./altsrc) input extensions requires Go +version 1.2+. [See the install +instructions](http://golang.org/doc/install.html). To install cli, simply run: ``` diff --git a/runtests b/runtests index feacff3..9288f11 100755 --- a/runtests +++ b/runtests @@ -33,7 +33,7 @@ def main(sysargs=sys.argv[:]): def _test(): if check_output('go version'.split()).split()[2] < 'go1.2': - _run('go test -v ./...'.split()) + _run('go test -v .'.split()) return coverprofiles = [] From 9132ebec9e50cf0a6fe92b253aac5724cacf594d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 21 May 2016 08:46:18 -0400 Subject: [PATCH 193/381] Customize install step for go1.1.2 --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b117165..9aa3888 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: go sudo: false go: -- 1.1.2 - 1.2.2 - 1.3.3 - 1.4 @@ -14,6 +13,9 @@ go: matrix: allow_failures: - go: master + include: + - go: 1.1.2 + install: go get -v . before_script: - go get github.com/meatballhat/gfmxr/... From 71b858d7964641e1fcf867dd493cbee0dfff0233 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 21 May 2016 08:49:43 -0400 Subject: [PATCH 194/381] Skipping gfmxr on go1.1, too --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9aa3888..bb886b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,10 @@ matrix: include: - go: 1.1.2 install: go get -v . + before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION + script: + - ./runtests vet + - ./runtests test before_script: - go get github.com/meatballhat/gfmxr/... From 024b4c6240a085f57e3c7e7378106a3252dc0a77 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 22 May 2016 14:42:23 -0700 Subject: [PATCH 195/381] Update references to codegangsta to urfave (new org name) Also add notice to README --- CHANGELOG.md | 52 +++++++++++++++++----------------- README.md | 52 +++++++++++++++++++++------------- altsrc/flag.go | 2 +- altsrc/flag_test.go | 2 +- altsrc/input_source_context.go | 2 +- altsrc/map_input_source.go | 2 +- altsrc/yaml_command_test.go | 2 +- altsrc/yaml_file_loader.go | 2 +- app.go | 2 +- runtests | 2 +- 10 files changed, 66 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a3ed2..b1e9485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ makes it easier to script around apps built using `cli` since they can trust that a 0 exit code indicated a successful execution. - cleanups based on [Go Report Card - feedback](https://goreportcard.com/report/github.com/codegangsta/cli) + feedback](https://goreportcard.com/report/github.com/urfave/cli) ## [1.16.0] - 2016-05-02 ### Added @@ -288,28 +288,28 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/codegangsta/cli/compare/v1.17.0...HEAD -[1.17.0]: https://github.com/codegangsta/cli/compare/v1.16.0...v1.17.0 -[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0 -[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0 -[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 -[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 -[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0 -[1.11.1]: https://github.com/codegangsta/cli/compare/v1.11.0...v1.11.1 -[1.11.0]: https://github.com/codegangsta/cli/compare/v1.10.2...v1.11.0 -[1.10.2]: https://github.com/codegangsta/cli/compare/v1.10.1...v1.10.2 -[1.10.1]: https://github.com/codegangsta/cli/compare/v1.10.0...v1.10.1 -[1.10.0]: https://github.com/codegangsta/cli/compare/v1.9.0...v1.10.0 -[1.9.0]: https://github.com/codegangsta/cli/compare/v1.8.0...v1.9.0 -[1.8.0]: https://github.com/codegangsta/cli/compare/v1.7.1...v1.8.0 -[1.7.1]: https://github.com/codegangsta/cli/compare/v1.7.0...v1.7.1 -[1.7.0]: https://github.com/codegangsta/cli/compare/v1.6.0...v1.7.0 -[1.6.0]: https://github.com/codegangsta/cli/compare/v1.5.0...v1.6.0 -[1.5.0]: https://github.com/codegangsta/cli/compare/v1.4.1...v1.5.0 -[1.4.1]: https://github.com/codegangsta/cli/compare/v1.4.0...v1.4.1 -[1.4.0]: https://github.com/codegangsta/cli/compare/v1.3.1...v1.4.0 -[1.3.1]: https://github.com/codegangsta/cli/compare/v1.3.0...v1.3.1 -[1.3.0]: https://github.com/codegangsta/cli/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/codegangsta/cli/compare/v1.1.0...v1.2.0 -[1.1.0]: https://github.com/codegangsta/cli/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/codegangsta/cli/compare/v0.1.0...v1.0.0 +[Unreleased]: https://github.com/urfave/cli/compare/v1.17.0...HEAD +[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 +[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 +[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 +[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0 +[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0 +[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0 +[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1 +[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0 +[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2 +[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1 +[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0 +[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0 +[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0 +[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0 +[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0 diff --git a/README.md b/README.md index eb62b2c..e7024c1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ -[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) -[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) -[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) -[![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli) -[![top level coverage](https://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) / -[![altsrc coverage](https://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc) +[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) +[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) +[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / +[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) # cli +**Notice:** This is the library formally known as +`github.com/codegangsta/cli` -- Github will automatically redirect requests +to this repository, but we recommend updating your references for clarity. + cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. ## Overview @@ -25,7 +29,7 @@ instructions](http://golang.org/doc/install.html). To install cli, simply run: ``` -$ go get github.com/codegangsta/cli +$ go get github.com/urfave/cli ``` Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: @@ -44,13 +48,13 @@ that, please use whatever version pinning of your preference, such as via `gopkg.in`: ``` -$ go get gopkg.in/codegangsta/cli.v2 +$ go get gopkg.in/urfave/cli.v2 ``` ``` go ... import ( - "gopkg.in/codegangsta/cli.v2" // imports as package "cli" + "gopkg.in/urfave/cli.v2" // imports as package "cli" ) ... ``` @@ -62,13 +66,13 @@ to avoid any unexpected compatibility pains once `v2` becomes `master`, then pinning to the `v1` branch is an acceptable option, e.g.: ``` -$ go get gopkg.in/codegangsta/cli.v1 +$ go get gopkg.in/urfave/cli.v1 ``` ``` go ... import ( - "gopkg.in/codegangsta/cli.v1" // imports as package "cli" + "gopkg.in/urfave/cli.v1" // imports as package "cli" ) ... ``` @@ -82,7 +86,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -102,7 +106,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -136,7 +140,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -254,7 +258,7 @@ app.Action = func(c *cli.Context) error { ... ``` -See full list of flags at http://godoc.org/github.com/codegangsta/cli +See full list of flags at http://godoc.org/github.com/urfave/cli #### Placeholder Values @@ -469,7 +473,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -568,7 +572,7 @@ import ( "io" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -617,8 +621,16 @@ VERSION: ## 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. +Feel free to put up a pull request to fix a bug or maybe add a feature. I will +give it a code review and make sure that it does not break backwards +compatibility. If I or any other collaborators agree that it is in line with +the vision of the project, we will work with you to get the code into +a mergeable state and merge it into the master branch. -If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. +If you have contributed something significant to the project, we 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 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. +If you feel like you have contributed to the project but have not yet been +added as a collaborator, we probably forgot to add you, please open an issue. diff --git a/altsrc/flag.go b/altsrc/flag.go index 0a11ad5..3e44d02 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // FlagInputSourceExtension is an extension interface of cli.Flag that diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 4e25be6..218e9b8 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) type testApplyInputSource struct { diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 6d695ff..8ea7e92 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -3,7 +3,7 @@ package altsrc import ( "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // InputSourceContext is an interface used to allow diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 19f87af..b1c8e4f 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // MapInputSource implements InputSourceContext to return diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 519bd81..31f78ce 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func TestCommandYamlFileTest(t *testing.T) { diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 01797ad..b4e3365 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -12,7 +12,7 @@ import ( "net/url" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" "gopkg.in/yaml.v2" ) diff --git a/app.go b/app.go index 7c9b958..9c7f679 100644 --- a/app.go +++ b/app.go @@ -12,7 +12,7 @@ import ( ) var ( - changeLogURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md" + changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) diff --git a/runtests b/runtests index 9288f11..b0fd06f 100755 --- a/runtests +++ b/runtests @@ -10,7 +10,7 @@ from subprocess import check_call, check_output PACKAGE_NAME = os.environ.get( - 'CLI_PACKAGE_NAME', 'github.com/codegangsta/cli' + 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' ) From 97e55662b14cd0c2ddef86ee4fabb185884b8cc5 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 18:52:21 -0400 Subject: [PATCH 196/381] Add Windows (appveyor) badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e7024c1..0af6ed4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/meatballhat/cli) [![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) From 288d62118aa5ad0b6f980d89d3970bd2c68285a6 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:15:05 -0400 Subject: [PATCH 197/381] Try to get appveyor.yml working again --- appveyor.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3ca7afa..f32e013 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,14 +2,25 @@ version: "{build}" os: Windows Server 2012 R2 +environment: + GOPATH: c:\gopath + GOVERSION: 1.6 + install: + - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - set NEW_BUILD_DIR_DEST=c:\gopath\src\github.com\urfave + - mkdir %NEW_BUILD_DIR_DEST% + - move %APPVEYOR_BUILD_FOLDER% %NEW_BUILD_DIR_DEST% + - set APPVEYOR_BUILD_FOLDER=%NEW_BUILD_DIR%\cli - go version - go env + - go get github.com/urfave/gfmxr/... build_script: - cd %APPVEYOR_BUILD_FOLDER% - - go vet ./... - - go test -v ./... + - ./runtests vet + - ./runtests test + - ./runtests gfmxr test: off From 531c7defe7856ab78c8541195bc1b627734e6b6c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:18:38 -0400 Subject: [PATCH 198/381] Trying xcopy instead of move --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index f32e013..87a3209 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,8 +10,8 @@ install: - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - set NEW_BUILD_DIR_DEST=c:\gopath\src\github.com\urfave - mkdir %NEW_BUILD_DIR_DEST% - - move %APPVEYOR_BUILD_FOLDER% %NEW_BUILD_DIR_DEST% - - set APPVEYOR_BUILD_FOLDER=%NEW_BUILD_DIR%\cli + - xcopy /s %APPVEYOR_BUILD_FOLDER% %NEW_BUILD_DIR_DEST%\cli + - set APPVEYOR_BUILD_FOLDER=%NEW_BUILD_DIR_DEST%\cli - go version - go env - go get github.com/urfave/gfmxr/... From d277cbf893248383a11a041d2cb15fdf7750e54a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:32:48 -0400 Subject: [PATCH 199/381] Use clone_folder in appveyor.yml instead of dir moving shenanigans --- appveyor.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 87a3209..bc8a8d7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,22 +2,20 @@ version: "{build}" os: Windows Server 2012 R2 +clone_folder: c:\gopath\src\github.com\urfave\cli + environment: GOPATH: c:\gopath GOVERSION: 1.6 install: - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - - set NEW_BUILD_DIR_DEST=c:\gopath\src\github.com\urfave - - mkdir %NEW_BUILD_DIR_DEST% - - xcopy /s %APPVEYOR_BUILD_FOLDER% %NEW_BUILD_DIR_DEST%\cli - - set APPVEYOR_BUILD_FOLDER=%NEW_BUILD_DIR_DEST%\cli - go version - go env - go get github.com/urfave/gfmxr/... + - go get -v -t ./... build_script: - - cd %APPVEYOR_BUILD_FOLDER% - ./runtests vet - ./runtests test - ./runtests gfmxr From 9491a913365076b3400ea60a53440de5c18b6819 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:37:59 -0400 Subject: [PATCH 200/381] Try to run a python script --- appveyor.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bc8a8d7..542ef2a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,20 +5,23 @@ os: Windows Server 2012 R2 clone_folder: c:\gopath\src\github.com\urfave\cli environment: - GOPATH: c:\gopath + GOPATH: C:\gopath GOVERSION: 1.6 + PYTHON: C:\Python27-x64 + PYTHON_VERSION: 2.7.x + PYTHON_ARCH: 64 install: - - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env - go get github.com/urfave/gfmxr/... - go get -v -t ./... build_script: - - ./runtests vet - - ./runtests test - - ./runtests gfmxr + - python runtests vet + - python runtests test + - python runtests gfmxr test: off From 4566119b39b91e708320cad678d04131e5197a3b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 19:44:21 -0400 Subject: [PATCH 201/381] Close temporary file before running go tool cover --- runtests | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/runtests b/runtests index b0fd06f..72c1f0d 100755 --- a/runtests +++ b/runtests @@ -49,9 +49,9 @@ def _test(): ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') ]) - combined = _combine_coverprofiles(coverprofiles) - _run('go tool cover -func={}'.format(combined.name).split()) - combined.close() + combined_name = _combine_coverprofiles(coverprofiles) + _run('go tool cover -func={}'.format(combined_name).split()) + os.remove(combined_name) def _gfmxr(): @@ -78,7 +78,9 @@ def _is_go_runnable(line): def _combine_coverprofiles(coverprofiles): - combined = tempfile.NamedTemporaryFile(suffix='.coverprofile') + combined = tempfile.NamedTemporaryFile( + suffix='.coverprofile', delete=False + ) combined.write('mode: set\n') for coverprofile in coverprofiles: @@ -88,7 +90,9 @@ def _combine_coverprofiles(coverprofiles): combined.write(line) combined.flush() - return combined + name = combined.name + combined.close() + return name if __name__ == '__main__': From b37df9de86ebf33dd689debd867420484530b66f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 20:35:25 -0400 Subject: [PATCH 202/381] See why README examples are unhappy on Windows --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 542ef2a..5470392 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ environment: PYTHON: C:\Python27-x64 PYTHON_VERSION: 2.7.x PYTHON_ARCH: 64 + GFMXR_DEBUG: 1 install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% From 377aca16cf86f6171a25565625a8ad9eaa763495 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 19:39:04 -0400 Subject: [PATCH 203/381] Trivial updates to appveyor config --- appveyor.yml | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5470392..173086e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,17 +13,13 @@ environment: GFMXR_DEBUG: 1 install: - - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - - go version - - go env - - go get github.com/urfave/gfmxr/... - - go get -v -t ./... +- set PATH=%GOPATH%\bin;C:\go\bin;%PATH% +- go version +- go env +- go get github.com/urfave/gfmxr/... +- go get -v -t ./... build_script: - - python runtests vet - - python runtests test - - python runtests gfmxr - -test: off - -deploy: off +- python runtests vet +- python runtests test +- python runtests gfmxr From dcc28a1b2b13f64eac3041fc6fbf316635e16517 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:19:54 -0400 Subject: [PATCH 204/381] Enable osx testing --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index bb886b8..796b40c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: go sudo: false +os: +- linux +- osx + go: - 1.2.2 - 1.3.3 From f136df348e2ce8cc5dadbb94d82c40fd90c013af Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:21:14 -0400 Subject: [PATCH 205/381] Only test latest go on osx --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 796b40c..96dedf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,6 @@ language: go sudo: false -os: -- linux -- osx - go: - 1.2.2 - 1.3.3 @@ -18,6 +14,8 @@ matrix: allow_failures: - go: master include: + - go: 1.6.2 + os: osx - go: 1.1.2 install: go get -v . before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION From e4666418bb09211416bfabc686f74bdbb3baeb56 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:34:33 -0400 Subject: [PATCH 206/381] Add (and backfill) some notes about platform support --- CHANGELOG.md | 2 ++ README.md | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e9485..0d51d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ## [Unreleased] ### Added - `./runtests` test runner with coverage tracking by default +- testing on OS X +- testing on Windows ### Fixed - Printing of command aliases in help text diff --git a/README.md b/README.md index 0af6ed4..581bb9a 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands c export PATH=$PATH:$GOPATH/bin ``` +### Supported platforms + +cli is tested against multiple versions of Go on Linux, and against the latest +released version of Go on OS X and Windows. For full details, see +[`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml). + ### Using the `v2` branch There is currently a long-lived branch named `v2` that is intended to land as From f2d92acb5d28d88360fee09a73e966f7046de864 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 24 May 2016 16:07:30 -0400 Subject: [PATCH 207/381] Point at correct gfmxr location --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 96dedf9..657e96a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ matrix: - ./runtests test before_script: -- go get github.com/meatballhat/gfmxr/... +- go get github.com/urfave/gfmxr/... script: - ./runtests vet From c455f5c3ecfb36d79842f20440e4e085f78f404a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 27 May 2016 14:48:19 -0400 Subject: [PATCH 208/381] TRIVIAL formally -> formerly --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 581bb9a..80337c5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ # cli -**Notice:** This is the library formally known as +**Notice:** This is the library formerly known as `github.com/codegangsta/cli` -- Github will automatically redirect requests to this repository, but we recommend updating your references for clarity. From 41310fbe1bdc07efdd92cd8cda45e4bbd2bb1e1a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 31 May 2016 15:49:48 -0400 Subject: [PATCH 209/381] Enable more examples as runnable in README so that they can be verified by the migrator script over in the v2 branch. --- README.md | 557 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 386 insertions(+), 171 deletions(-) diff --git a/README.md b/README.md index 80337c5..2021ece 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,19 @@ `github.com/codegangsta/cli` -- Github will automatically redirect requests to this repository, but we recommend updating your references for clarity. -cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. +cli is a simple, fast, and fun package for building command line apps in Go. The +goal is to enable developers to write fast and distributable command line +applications in an expressive way. ## Overview -Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. +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 comes into play.** cli makes command line programming fun, organized, and expressive! +**This is where cli comes into play.** cli makes command line programming fun, +organized, and expressive! ## Installation @@ -33,7 +39,8 @@ To install cli, simply run: $ go get github.com/urfave/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 ``` @@ -86,13 +93,19 @@ import ( ## Getting Started -One of the philosophies behind cli is that an API should be playful and full of discovery. So a cli app can be as little as one line of code in `main()`. +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + ``` go package main import ( "os" + "github.com/urfave/cli" ) @@ -101,7 +114,8 @@ func main() { } ``` -This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: +This app will run and show help text, but is not very useful. Let's give an +action to execute and some help documentation: ``` go -... -app.Action = func(c *cli.Context) error { - fmt.Println("Hello", c.Args()[0]) - return nil +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Action = func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + } + + app.Run(os.Args) } -... ``` ### Flags Setting and querying flags is simple. + ``` go -... -app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, -} -app.Action = func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args()[0] +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) + + app.Action = func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil } - return nil + + app.Run(os.Args) } -... ``` -You can also set a destination variable for a flag, to which the content will be scanned. +You can also set a destination variable for a flag, to which the content will be +scanned. + ``` go -... -var language string -app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, -} -app.Action = func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args()[0] +package main + +import ( + "os" + "fmt" + + "github.com/urfave/cli" +) + +func main() { + var language string + + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) + + app.Action = func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args()[0] + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil } - return nil + + app.Run(os.Args) } -... ``` See full list of flags at http://godoc.org/github.com/urfave/cli #### Placeholder Values -Sometimes it's useful to specify a flag's value within the usage string itself. Such placeholders are -indicated with back quotes. +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. For example this: + ```go -cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + app.Run(os.Args) } ``` @@ -287,145 +377,244 @@ Will result in help output like: --config FILE, -c FILE Load configuration from FILE ``` -Note that only the first placeholder is used. Subsequent back-quoted words will be left as-is. +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. #### 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 { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - }, +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, + } + + app.Run(os.Args) } ``` -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. +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. + ``` go -app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "APP_LANG", - }, +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, + } + + app.Run(os.Args) } ``` -The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. +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", - }, +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, + } + + app.Run(os.Args) } ``` #### Values from alternate input sources (YAML and others) -There is a separate package altsrc that adds support for getting flag values from other input sources like YAML. +There is a separate package altsrc that adds support for getting flag values +from other input sources like YAML. -In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below: +In order to get values for a flag from an alternate input source the following +code would be added to wrap an existing cli.Flag like below: ``` go altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) ``` -Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below. +Initialization must also occur for these flags. Below is an example initializing +getting data from a yaml file below. ``` go command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) ``` -The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context. -It will then use that file name to initialize the yaml input source for any flags that are defined on that command. -As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. +The code above will use the "load" string as a flag name to get the file name of +a yaml file from the cli.Context. It will then use that file name to initialize +the yaml input source for any flags that are defined on that command. As a note +the "load" flag used would also have to be defined on the command flags in order +for this code snipped to work. -Currently only YAML files are supported but developers can add support for other input sources by implementing the -altsrc.InputSourceContext for their given sources. +Currently only YAML files are supported but developers can add support for other +input sources by implementing the altsrc.InputSourceContext for their given +sources. Here is a more complete sample of a command using YAML support: + ``` go - command := &cli.Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(c *cli.Context) error { - // Action to run - return nil - }, - Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, +package notmain + +import ( + "fmt" + "os" + + "github.com/urfave/cli" + "github.com/urfave/cli/altsrc" +) + +func main() { + app := cli.NewApp() + + flags := []cli.Flag{ + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}, } - command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - err := command.Run(c) + + app.Action = func(c *cli.Context) error { + fmt.Println("yaml ist rad") + return nil + } + + app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) + app.Flags = flags + + app.Run(os.Args) +} ``` ### Subcommands Subcommands can be defined for a more git-like command line app. + ```go -... -app.Commands = []cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil + }, }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, }, - }, - { - Name: "template", - Aliases: []string{"r"}, - Usage: "options for task templates", - Subcommands: []cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, }, }, }, - }, + } + + app.Run(os.Args) } -... ``` ### Subcommands categories @@ -437,7 +626,17 @@ output. E.g. ```go -... +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Commands = []cli.Command{ { Name: "noop", @@ -451,20 +650,20 @@ E.g. Category: "template", }, } -... + + app.Run(os.Args) +} ``` Will include: ``` -... COMMANDS: noop Template actions: add remove -... ``` ### Exit code @@ -509,32 +708,48 @@ flag on the `App` object. By default, this setting will only auto-complete to show an app's subcommands, but you can write your own completion methods for the App or its subcommands. -```go -... -var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} -app := cli.NewApp() -app.EnableBashCompletion = true -app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - BashComplete: func(c *cli.Context) { - // This will complete if no args are passed - if c.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, }, } + + app.Run(os.Args) } -... ``` #### To Enable From 0990e4b2ad0aea31745051e26b2e3fc424ac6b06 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 1 Jun 2016 10:46:39 -0400 Subject: [PATCH 210/381] Update appveyor badge URL to point to new team name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2021ece..e6701e5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) -[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/meatballhat/cli) +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli) [![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) From 1c81757e1f5188fface135d3c7d73ebb47202c87 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 1 Jun 2016 13:28:27 -0400 Subject: [PATCH 211/381] Add more v1 API examples --- README.md | 376 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 373 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e6701e5..f8361ce 100644 --- a/README.md +++ b/README.md @@ -752,14 +752,14 @@ func main() { } ``` -#### To Enable +#### Enabling 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` -#### To Distribute +#### Distribution Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename it to the name of the program you wish to add autocomplete support for (or @@ -775,7 +775,48 @@ Alternatively, you can just document that users should source the generic `autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set to the name of their program (as above). -### Generated Help Text Customization +#### Customization + +The default bash completion flag (`--generate-bash-completion`) is defined as +`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.BashCompletionFlag = cli.BoolFlag{ + Name: "compgen", + Hidden: true, + } + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "wat", + }, + } + app.Run(os.Args) +} +``` + +### Generated Help Text + +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization All of the help text generation may be customized, and at multiple levels. The templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and @@ -841,6 +882,335 @@ VERSION: } ``` +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.HelpFlag = cli.BoolFlag{ + Name: "halp, haaaaalp", + Usage: "HALP", + EnvVar: "SHOW_HALP,HALPPLZ", + } + + cli.NewApp().Run(os.Args) +} +``` + +### Version Flag + +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be cusomized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.VersionFlag = cli.BoolFlag{ + Name: "print-version, V", + Usage: "print only the version", + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "v19.99.0" + app.Run(os.Args) +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "v19.99.0" + app.Run(os.Args) +} +``` + +#### Full API Example + +**NOTE**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + +``` go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "time" + + "github.com/urfave/cli" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = cli.BoolFlag{Name: "halp"} + cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} + cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) + } + cli.OsExiter = func(c int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.GetName()) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +func main() { + app := cli.NewApp() + app.Name = "kənˈtrīv" + app.Version = "v19.99.0" + app.Compiled = time.Now() + app.Authors = []cli.Author{ + { + Name: "Example Human", + Email: "human@example.com", + }, + } + app.Copyright = "(c) 1999 Serious Enterprise" + app.HelpName = "contrive" + app.Usage = "demonstrate available API" + app.UsageText = "contrive - demonstrating the available API" + app.ArgsUsage = "[args and such]" + app.Commands = []cli.Command{ + { + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "forever, forevvarr"}, + }, + Subcommands: cli.Commands{ + { + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err + }, + }, + } + app.Flags = []cli.Flag{ + cli.BoolFlag{Name: "fancy"}, + cli.BoolTFlag{Name: "fancy"}, + cli.StringFlag{Name: "dance-move, d"}, + } + app.EnableBashCompletion = true + app.HideHelp = false + app.HideVersion = false + app.BashComplete = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + } + app.Before = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + } + app.After = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + } + app.CommandNotFound = func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + } + app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + } + app.Action = func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) + + categories := c.App.Categories() + categories.AddCommand("sounds", cli.Command{ + Name: "bloop", + }) + + for _, category := range categories { + fmt.Fprintf(c.App.Writer, "%s\n", category.Name) + fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + } + + c.App.Command("doo") + c.App.Run([]string{"app", "doo", "wop"}) + c.App.RunAsSubcommand(c) + c.App.Setup() + c.App.VisibleCategories() + c.App.VisibleCommands() + c.App.VisibleFlags() + + c.Args().First() + c.Args().Get(1) + c.Args().Present() + c.Args().Tail() + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) + nc.Args() + nc.Bool("nope") + nc.BoolT("nerp") + nc.Duration("howlong") + nc.Float64("hay") + nc.Generic("bloop") + nc.Int("bips") + nc.IntSlice("blups") + nc.String("snurt") + nc.StringSlice("snurkles") + nc.GlobalBool("global-nope") + nc.GlobalBoolT("global-nerp") + nc.GlobalDuration("global-howlong") + nc.GlobalFloat64("global-hay") + nc.GlobalGeneric("global-bloop") + nc.GlobalInt("global-bips") + nc.GlobalIntSlice("global-blups") + nc.GlobalString("global-snurt") + nc.GlobalStringSlice("global-snurkles") + + nc.FlagNames() + nc.GlobalFlagNames() + nc.GlobalIsSet("wat") + nc.GlobalSet("wat", "nope") + nc.NArg() + nc.NumFlags() + nc.Parent() + nc.Set("wat", "also-nope") + + ec := cli.NewExitError("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + return ec + } + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + app.Metadata = map[string]interface{}{ + "layers": "many", + "explicable": false, + } +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` + ## Contribution Guidelines Feel free to put up a pull request to fix a bug or maybe add a feature. I will From b5c48311fae486d73bd59bf944f6b031b9451a98 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 1 Jun 2016 14:06:51 -0400 Subject: [PATCH 212/381] Ensure the full API example really runs --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f8361ce..3b34449 100644 --- a/README.md +++ b/README.md @@ -983,6 +983,9 @@ func main() { **NOTE**: This is a contrived (functioning) example meant strictly for API demonstration purposes. Use of one's imagination is encouraged. + ``` go package main @@ -992,6 +995,7 @@ import ( "fmt" "io" "io/ioutil" + "os" "time" "github.com/urfave/cli" @@ -1100,7 +1104,7 @@ func main() { } app.Flags = []cli.Flag{ cli.BoolFlag{Name: "fancy"}, - cli.BoolTFlag{Name: "fancy"}, + cli.BoolTFlag{Name: "fancier"}, cli.StringFlag{Name: "dance-move, d"}, } app.EnableBashCompletion = true @@ -1150,8 +1154,13 @@ func main() { } c.App.Command("doo") - c.App.Run([]string{"app", "doo", "wop"}) - c.App.RunAsSubcommand(c) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } + + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } c.App.Setup() c.App.VisibleCategories() c.App.VisibleCommands() @@ -1195,14 +1204,19 @@ func main() { ec := cli.NewExitError("ohwell", 86) fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") return ec } app.Writer = &hexWriter{} + // just kidding there + app.Writer = os.Stdout app.ErrWriter = &hexWriter{} app.Metadata = map[string]interface{}{ "layers": "many", "explicable": false, } + + app.Run(os.Args) } func wopAction(c *cli.Context) error { From a121e978f7ddf85ec44f2b1780569f094562ba9c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 31 May 2016 16:00:14 -0700 Subject: [PATCH 213/381] Switch "printHelp" tabwriter padchar to space Using tabs for alignment is troubling if the output is used for anything besides display in a terminal (or if the user's terminal allows for adjustment of the default tab size), as noted by the documentation for `tabwriter` (https://golang.org/pkg/text/tabwriter/#Writer.Init): > (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result) The safer solution is to use `' '` as the `padchar`, which only carries the assumption of a fixed-width font (which is a more reasonable assumption than a fixed, constant tab size). --- help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.go b/help.go index a9e7327..f2ddc54 100644 --- a/help.go +++ b/help.go @@ -191,7 +191,7 @@ func printHelp(out io.Writer, templ string, data interface{}) { "join": strings.Join, } - w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) + w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) err := t.Execute(w, data) if err != nil { From 288b636ba31cc99171170ed7fb57f07f72ec6c04 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 1 Jun 2016 22:27:36 -0400 Subject: [PATCH 214/381] Modifying full API example for more migrator exercise --- README.md | 93 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 3b34449..9bbfe83 100644 --- a/README.md +++ b/README.md @@ -1042,7 +1042,7 @@ func main() { app.Version = "v19.99.0" app.Compiled = time.Now() app.Authors = []cli.Author{ - { + cli.Author{ Name: "Example Human", Email: "human@example.com", }, @@ -1053,7 +1053,7 @@ func main() { app.UsageText = "contrive - demonstrating the available API" app.ArgsUsage = "[args and such]" app.Commands = []cli.Command{ - { + cli.Command{ Name: "doo", Aliases: []string{"do"}, Category: "motion", @@ -1065,7 +1065,7 @@ func main() { cli.BoolFlag{Name: "forever, forevvarr"}, }, Subcommands: cli.Commands{ - { + cli.Command{ Name: "wop", Action: wopAction, }, @@ -1147,13 +1147,13 @@ func main() { Name: "bloop", }) - for _, category := range categories { + for _, category := range c.App.Categories() { fmt.Fprintf(c.App.Writer, "%s\n", category.Name) fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) } - c.App.Command("doo") + fmt.Printf("%#v\n", c.App.Command("doo")) if c.Bool("infinite") { c.App.Run([]string{"app", "doo", "wop"}) } @@ -1162,44 +1162,48 @@ func main() { c.App.RunAsSubcommand(c) } c.App.Setup() - c.App.VisibleCategories() - c.App.VisibleCommands() - c.App.VisibleFlags() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) - c.Args().First() - c.Args().Get(1) - c.Args().Present() - c.Args().Tail() + fmt.Printf("%#v\n", c.Args().First()) + if len(c.Args()) > 0 { + fmt.Printf("%#v\n", c.Args()[1]) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) set := flag.NewFlagSet("contrive", 0) nc := cli.NewContext(c.App, set, c) - nc.Args() - nc.Bool("nope") - nc.BoolT("nerp") - nc.Duration("howlong") - nc.Float64("hay") - nc.Generic("bloop") - nc.Int("bips") - nc.IntSlice("blups") - nc.String("snurt") - nc.StringSlice("snurkles") - nc.GlobalBool("global-nope") - nc.GlobalBoolT("global-nerp") - nc.GlobalDuration("global-howlong") - nc.GlobalFloat64("global-hay") - nc.GlobalGeneric("global-bloop") - nc.GlobalInt("global-bips") - nc.GlobalIntSlice("global-blups") - nc.GlobalString("global-snurt") - nc.GlobalStringSlice("global-snurkles") - - nc.FlagNames() - nc.GlobalFlagNames() - nc.GlobalIsSet("wat") - nc.GlobalSet("wat", "nope") - nc.NArg() - nc.NumFlags() - nc.Parent() + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", nc.BoolT("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) + fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) + fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) + fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) + fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) + fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) + fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) + fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) + fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.GlobalFlagNames()) + fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) + fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Parent()) + nc.Set("wat", "also-nope") ec := cli.NewExitError("ohwell", 86) @@ -1207,13 +1211,16 @@ func main() { fmt.Printf("made it!\n") return ec } - app.Writer = &hexWriter{} - // just kidding there - app.Writer = os.Stdout - app.ErrWriter = &hexWriter{} + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + app.Metadata = map[string]interface{}{ "layers": "many", "explicable": false, + "whatever-values": 19.99, } app.Run(os.Args) From dedc6e14746fb4b1a99dbf8630785c648c0aa437 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 1 Jun 2016 20:10:13 -0700 Subject: [PATCH 215/381] Add note about #441 (replace tabs with spaces for alignment) in CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d51d2a..ac6d73a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ - testing on OS X - testing on Windows +### Changed +- Use spaces for alignment in help/usage output instead of tabs, making the + output alignment consistent regardless of tab width + ### Fixed - Printing of command aliases in help text From e97f74a5708775999e80bcd0026238c62fca3ec2 Mon Sep 17 00:00:00 2001 From: James Cunningham Date: Tue, 7 Jun 2016 16:28:10 +0100 Subject: [PATCH 216/381] fix ActionFunc signature of ShowAppHelp action --- help.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/help.go b/help.go index f2ddc54..0f4cf14 100644 --- a/help.go +++ b/help.go @@ -117,8 +117,9 @@ var HelpPrinter helpPrinter = printHelp var VersionPrinter = printVersion // ShowAppHelp is an action that displays the help. -func ShowAppHelp(c *Context) { +func ShowAppHelp(c *Context) error { HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) + return nil } // DefaultAppComplete prints the list of subcommands as the default app completion method From f621deee5a7eb89d23ef3e74c0bec74c65c854ea Mon Sep 17 00:00:00 2001 From: Joshua Rubin Date: Wed, 8 Jun 2016 04:27:57 -0600 Subject: [PATCH 217/381] fix panic getting visible flags --- flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flag.go b/flag.go index 1e8112e..2ed875b 100644 --- a/flag.go +++ b/flag.go @@ -512,7 +512,7 @@ func (f Float64Flag) GetName() string { func visibleFlags(fl []Flag) []Flag { visible := []Flag{} for _, flag := range fl { - if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { + if !reflect.Indirect(reflect.ValueOf(flag)).FieldByName("Hidden").Bool() { visible = append(visible, flag) } } From 7cd5bed6cb6f7941dc875d61d1ba24a72a448f21 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 8 Jun 2016 10:34:44 -0400 Subject: [PATCH 218/381] Backporting flagValue func from v2 branch --- flag.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/flag.go b/flag.go index 2ed875b..b087e25 100644 --- a/flag.go +++ b/flag.go @@ -512,7 +512,7 @@ func (f Float64Flag) GetName() string { func visibleFlags(fl []Flag) []Flag { visible := []Flag{} for _, flag := range fl { - if !reflect.Indirect(reflect.ValueOf(flag)).FieldByName("Hidden").Bool() { + if !flagValue(flag).FieldByName("Hidden").Bool() { visible = append(visible, flag) } } @@ -578,8 +578,16 @@ func withEnvHint(envVar, str string) string { return str + envText } -func stringifyFlag(f Flag) string { +func flagValue(f Flag) reflect.Value { fv := reflect.ValueOf(f) + for fv.Kind() == reflect.Ptr { + fv = reflect.Indirect(fv) + } + return fv +} + +func stringifyFlag(f Flag) string { + fv := flagValue(f) switch f.(type) { case IntSliceFlag: From d645386c59ca8482ad37255caefe65ef3422dda2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 8 Jun 2016 17:18:25 -0400 Subject: [PATCH 219/381] Mention #448 fix in change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6d73a..b6da886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixed - Printing of command aliases in help text +- Printing of visible flags for both struct and struct pointer flags ## [1.17.0] - 2016-05-09 ### Added From 5a3515fdf81fdb1ca284dfbc130a8662a2759d73 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 9 Jun 2016 17:32:00 -0400 Subject: [PATCH 220/381] Focus on catching fewer panics in HandleAction so that "unknown" panics can still bubble up. --- app.go | 13 ++++++++----- app_test.go | 13 +++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app.go b/app.go index 9c7f679..a17300d 100644 --- a/app.go +++ b/app.go @@ -8,6 +8,7 @@ import ( "path/filepath" "reflect" "sort" + "strings" "time" ) @@ -464,11 +465,13 @@ func (a Author) String() string { func HandleAction(action interface{}, context *Context) (err error) { defer func() { if r := recover(); r != nil { - switch r.(type) { - case error: - err = r.(error) - default: - err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2) + // Try to detect a known reflection error from *this scope*, rather than + // swallowing all panics that may happen when calling an Action func. + s := fmt.Sprintf("%v", r) + if strings.HasPrefix(s, "reflect: ") && strings.Contains(s, "too many input arguments") { + err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2) + } else { + panic(r) } } }() diff --git a/app_test.go b/app_test.go index 42c852e..ee8cc35 100644 --- a/app_test.go +++ b/app_test.go @@ -1452,3 +1452,16 @@ func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) } } + +func TestHandleAction_WithUnknownPanic(t *testing.T) { + defer func() { refute(t, recover(), nil) }() + + var fn ActionFunc + + app := NewApp() + app.Action = func(ctx *Context) error { + fn(ctx) + return nil + } + HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) +} From 4962423cbac126c7b298a25c42ef6a7ee6fa792a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Franc=CC=A7a=20dos=20Reis?= Date: Sat, 11 Jun 2016 15:22:07 -0700 Subject: [PATCH 221/381] Adding Int64Flag type and related tests --- flag.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ flag_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/flag.go b/flag.go index b087e25..a67f69c 100644 --- a/flag.go +++ b/flag.go @@ -420,6 +420,51 @@ func (f IntFlag) GetName() string { return f.Name } +// Int64Flag is a flag that takes a 64-bit integer +// Errors if the value provided cannot be parsed +type Int64Flag struct { + Name string + Value int64 + Usage string + EnvVar string + Destination *int64 + Hidden bool +} + +// String returns the usage +func (f Int64Flag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f Int64Flag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err == nil { + f.Value = envValInt + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Int64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Int64(name, f.Value, f.Usage) + }) +} + +// GetName returns the name of the flag. +func (f Int64Flag) GetName() string { + return f.Name +} + // DurationFlag is a flag that takes a duration specified in Go's duration // format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { diff --git a/flag_test.go b/flag_test.go index e0df23b..32d57fb 100644 --- a/flag_test.go +++ b/flag_test.go @@ -162,6 +162,42 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { } } +var int64FlagTests = []struct { + name string + expected string +}{ + {"hats", "--hats value\t(default: 8589934592)"}, + {"H", "-H value\t(default: 8589934592)"}, +} + +func TestInt64FlagHelpOutput(t *testing.T) { + for _, test := range int64FlagTests { + flag := Int64Flag{Name: test.name, Value: 8589934592} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range int64FlagTests { + flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + var durationFlagTests = []struct { name string expected string From 80d3d863d9dade84a6ade0729923e87310d84253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Franc=CC=A7a=20dos=20Reis?= Date: Sat, 11 Jun 2016 15:41:24 -0700 Subject: [PATCH 222/381] Adding Int64 and GlobalInt64 in context, plus related tests --- context.go | 26 ++++++++++++++++++++++++++ context_test.go | 19 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/context.go b/context.go index c342463..112dac3 100644 --- a/context.go +++ b/context.go @@ -31,6 +31,11 @@ func (c *Context) Int(name string) int { return lookupInt(name, c.flagSet) } +// Int64 looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Int64(name string) int64 { + return lookupInt64(name, c.flagSet) +} + // Duration 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 { @@ -84,6 +89,14 @@ func (c *Context) GlobalInt(name string) int { return 0 } +// GlobalInt64 looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalInt64(name string) int64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt64(name, fs) + } + return 0 +} + // GlobalFloat64 looks up the value of a global float64 flag, returns float64(0) // if no float64 flag exists func (c *Context) GlobalFloat64(name string) float64 { @@ -316,6 +329,19 @@ func lookupInt(name string, set *flag.FlagSet) int { return 0 } +func lookupInt64(name string, set *flag.FlagSet) int64 { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseInt(f.Value.String(), 10, 64) + if err != nil { + return 0 + } + return val + } + + return 0 +} + func lookupDuration(name string, set *flag.FlagSet) time.Duration { f := set.Lookup(name) if f != nil { diff --git a/context_test.go b/context_test.go index 28d4884..7625c5b 100644 --- a/context_test.go +++ b/context_test.go @@ -9,17 +9,21 @@ import ( func TestNewContext(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") + set.Int("myflagInt64", 12, "doc") set.Float64("myflag64", float64(17), "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Int("myflag", 42, "doc") + globalSet.Int("myflagInt64", 42, "doc") globalSet.Float64("myflag64", float64(47), "doc") globalCtx := NewContext(nil, globalSet, nil) command := Command{Name: "mycommand"} c := NewContext(nil, set, globalCtx) c.Command = command expect(t, c.Int("myflag"), 12) + expect(t, c.Int64("myflagInt64"), int64(12)) expect(t, c.Float64("myflag64"), float64(17)) expect(t, c.GlobalInt("myflag"), 42) + expect(t, c.GlobalInt64("myflagInt64"), int64(42)) expect(t, c.GlobalFloat64("myflag64"), float64(47)) expect(t, c.Command.Name, "mycommand") } @@ -31,6 +35,13 @@ func TestContext_Int(t *testing.T) { expect(t, c.Int("myflag"), 12) } +func TestContext_Int64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int64("myflagInt64", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.Int64("myflagInt64"), int64(12)) +} + func TestContext_GlobalInt(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") @@ -39,6 +50,14 @@ func TestContext_GlobalInt(t *testing.T) { expect(t, c.GlobalInt("nope"), 0) } +func TestContext_GlobalInt64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int64("myflagInt64", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.GlobalInt64("myflagInt64"), int64(12)) + expect(t, c.GlobalInt64("nope"), int64(0)) +} + func TestContext_Float64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Float64("myflag", float64(17), "doc") From 5c7cca7f1682ac72d6a2481e004f6bfa636ad260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Franc=CC=A7a=20dos=20Reis?= Date: Sat, 11 Jun 2016 21:54:33 -0700 Subject: [PATCH 223/381] Adding Int64Slice related stuff in flag.go and context.go, and related tests --- context.go | 25 +++++++++++++ flag.go | 85 +++++++++++++++++++++++++++++++++++++++++++ flag_test.go | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) diff --git a/context.go b/context.go index 112dac3..d6d4a19 100644 --- a/context.go +++ b/context.go @@ -75,6 +75,12 @@ func (c *Context) IntSlice(name string) []int { return lookupIntSlice(name, c.flagSet) } +// Int64Slice looks up the value of a local int slice flag, returns nil if no int +// slice flag exists +func (c *Context) Int64Slice(name string) []int64 { + return lookupInt64Slice(name, c.flagSet) +} + // Generic looks up the value of a local generic flag, returns nil if no generic // flag exists func (c *Context) Generic(name string) interface{} { @@ -160,6 +166,15 @@ func (c *Context) GlobalIntSlice(name string) []int { return nil } +// GlobalInt64Slice looks up the value of a global int slice flag, returns nil if +// no int slice flag exists +func (c *Context) GlobalInt64Slice(name string) []int64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt64Slice(name, fs) + } + return nil +} + // GlobalGeneric looks up the value of a global generic flag, returns nil if no // generic flag exists func (c *Context) GlobalGeneric(name string) interface{} { @@ -396,6 +411,16 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int { return nil } +func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*Int64Slice)).Value() + + } + + return nil +} + func lookupGeneric(name string, set *flag.FlagSet) interface{} { f := set.Lookup(name) if f != nil { diff --git a/flag.go b/flag.go index a67f69c..abeff90 100644 --- a/flag.go +++ b/flag.go @@ -245,6 +245,77 @@ func (f IntSliceFlag) GetName() string { return f.Name } +// Int64Slice is an opaque type for []int to satisfy flag.Value +type Int64Slice []int64 + +// Set parses the value into an integer and appends it to the list of values +func (f *Int64Slice) Set(value string) error { + tmp, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + *f = append(*f, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Int64Slice) String() string { + return fmt.Sprintf("%d", *f) +} + +// Value returns the slice of ints set by this flag +func (f *Int64Slice) Value() []int64 { + return *f +} + +// Int64SliceFlag is an int flag that can be specified multiple times on the +// command-line +type Int64SliceFlag struct { + Name string + Value *Int64Slice + Usage string + EnvVar string + Hidden bool +} + +// String returns the usage +func (f Int64SliceFlag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f Int64SliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + newVal := &Int64Slice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(ErrWriter, err.Error()) + } + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &Int64Slice{} + } + set.Var(f.Value, name, f.Usage) + }) +} + +// GetName returns the name of the flag. +func (f Int64SliceFlag) GetName() string { + return f.Name +} + // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string @@ -638,6 +709,9 @@ func stringifyFlag(f Flag) string { case IntSliceFlag: return withEnvHint(fv.FieldByName("EnvVar").String(), stringifyIntSliceFlag(f.(IntSliceFlag))) + case Int64SliceFlag: + return withEnvHint(fv.FieldByName("EnvVar").String(), + stringifyInt64SliceFlag(f.(Int64SliceFlag))) case StringSliceFlag: return withEnvHint(fv.FieldByName("EnvVar").String(), stringifyStringSliceFlag(f.(StringSliceFlag))) @@ -683,6 +757,17 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { return stringifySliceFlag(f.Usage, f.Name, defaultVals) } +func stringifyInt64SliceFlag(f Int64SliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + } + } + + return stringifySliceFlag(f.Usage, f.Name, defaultVals) +} + func stringifyStringSliceFlag(f StringSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { diff --git a/flag_test.go b/flag_test.go index 32d57fb..296d5ac 100644 --- a/flag_test.go +++ b/flag_test.go @@ -277,6 +277,49 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +var int64SliceFlagTests = []struct { + name string + value *Int64Slice + expected string +}{ + {"heads", &Int64Slice{}, "--heads value\t"}, + {"H", &Int64Slice{}, "-H value\t"}, + {"H, heads", func() *Int64Slice { + i := &Int64Slice{} + i.Set("2") + i.Set("17179869184") + return i + }(), "-H value, --heads value\t(default: 2, 17179869184)"}, +} + +func TestInt64SliceFlagHelpOutput(t *testing.T) { + for _, test := range int64SliceFlagTests { + flag := Int64SliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_SMURF", "42,17179869184") + for _, test := range int64SliceFlagTests { + flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} + output := flag.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + var float64FlagTests = []struct { name string expected string @@ -615,6 +658,63 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiInt64Slice(t *testing.T) { + (&App{ + Flags: []Flag{ + Int64SliceFlag{Name: "serve, s", Value: &Int64Slice{}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("serve"), []int64{10, 17179869184}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.Int64Slice("s"), []int64{10, 17179869184}) { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "17179869184"}) +} + +func TestParseMultiInt64SliceFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,17179869184") + + (&App{ + Flags: []Flag{ + Int64SliceFlag{Name: "intervals, i", Value: &Int64Slice{}, EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Int64Slice("i"), []int64{20, 30, 17179869184}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,17179869184") + + (&App{ + Flags: []Flag{ + Int64SliceFlag{Name: "intervals, i", Value: &Int64Slice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Int64Slice("i"), []int64{20, 30, 17179869184}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + func TestParseMultiFloat64(t *testing.T) { a := App{ Flags: []Flag{ From 537f5beb66a21f2ec875cae4be798249f8fd6983 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 16 Jun 2016 10:14:28 -0400 Subject: [PATCH 224/381] Tweaks to Int64Flag PR --- CHANGELOG.md | 1 + context_test.go | 2 +- flag.go | 7 ++----- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6da886..205d8db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - `./runtests` test runner with coverage tracking by default - testing on OS X - testing on Windows +- `Int64Flag` type and supporting code ### Changed - Use spaces for alignment in help/usage output instead of tabs, making the diff --git a/context_test.go b/context_test.go index 7625c5b..ae37637 100644 --- a/context_test.go +++ b/context_test.go @@ -13,7 +13,7 @@ func TestNewContext(t *testing.T) { set.Float64("myflag64", float64(17), "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Int("myflag", 42, "doc") - globalSet.Int("myflagInt64", 42, "doc") + globalSet.Int64("myflagInt64", int64(42), "doc") globalSet.Float64("myflag64", float64(47), "doc") globalCtx := NewContext(nil, globalSet, nil) command := Command{Name: "mycommand"} diff --git a/flag.go b/flag.go index abeff90..6af15ed 100644 --- a/flag.go +++ b/flag.go @@ -189,7 +189,7 @@ func (f *IntSlice) Set(value string) error { // String returns a readable representation of this value (for usage defaults) func (f *IntSlice) String() string { - return fmt.Sprintf("%d", *f) + return fmt.Sprintf("%#v", *f) } // Value returns the slice of ints set by this flag @@ -260,7 +260,7 @@ func (f *Int64Slice) Set(value string) error { // String returns a readable representation of this value (for usage defaults) func (f *Int64Slice) String() string { - return fmt.Sprintf("%d", *f) + return fmt.Sprintf("%#v", *f) } // Value returns the slice of ints set by this flag @@ -447,7 +447,6 @@ func (f StringFlag) GetName() string { } // IntFlag is a flag that takes an integer -// Errors if the value provided cannot be parsed type IntFlag struct { Name string Value int @@ -492,7 +491,6 @@ func (f IntFlag) GetName() string { } // Int64Flag is a flag that takes a 64-bit integer -// Errors if the value provided cannot be parsed type Int64Flag struct { Name string Value int64 @@ -582,7 +580,6 @@ func (f DurationFlag) GetName() string { } // Float64Flag is a flag that takes an float value -// Errors if the value provided cannot be parsed type Float64Flag struct { Name string Value float64 From a0e694ed72569a637acbd432fbee7fc74cfe506c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 16 Jun 2016 11:13:32 -0400 Subject: [PATCH 225/381] Add UintFlag, Uint64Flag types and supporting code --- CHANGELOG.md | 2 +- README.md | 31 +++++++++++++++-- context.go | 58 ++++++++++++++++++++++++++++++-- context_test.go | 24 +++++++++++++- flag.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ flag_test.go | 72 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 268 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 205d8db..d975be7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - `./runtests` test runner with coverage tracking by default - testing on OS X - testing on Windows -- `Int64Flag` type and supporting code +- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code ### Changed - Use spaces for alignment in help/usage output instead of tabs, making the diff --git a/README.md b/README.md index 9bbfe83..d073997 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS - --version Shows version information + --version Shows version information ``` ### Arguments @@ -984,7 +984,7 @@ func main() { demonstration purposes. Use of one's imagination is encouraged. ``` go package main @@ -1036,6 +1036,19 @@ func (w *hexWriter) Write(p []byte) (int, error) { return len(p), nil } +type genericType struct{ + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + func main() { app := cli.NewApp() app.Name = "kənˈtrīv" @@ -1105,7 +1118,17 @@ func main() { app.Flags = []cli.Flag{ cli.BoolFlag{Name: "fancy"}, cli.BoolTFlag{Name: "fancier"}, + cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, + cli.Float64Flag{Name: "howmuch"}, + cli.GenericFlag{Name: "wat", Value: &genericType{}}, + cli.Int64Flag{Name: "longdistance"}, + cli.Int64SliceFlag{Name: "intervals"}, + cli.IntFlag{Name: "distance"}, + cli.IntSliceFlag{Name: "times"}, cli.StringFlag{Name: "dance-move, d"}, + cli.StringSliceFlag{Name: "names, N"}, + cli.UintFlag{Name: "age"}, + cli.Uint64Flag{Name: "bigage"}, } app.EnableBashCompletion = true app.HideHelp = false @@ -1182,10 +1205,14 @@ func main() { fmt.Printf("%#v\n", nc.Duration("howlong")) fmt.Printf("%#v\n", nc.Float64("hay")) fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) fmt.Printf("%#v\n", nc.Int("bips")) fmt.Printf("%#v\n", nc.IntSlice("blups")) fmt.Printf("%#v\n", nc.String("snurt")) fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) diff --git a/context.go b/context.go index d6d4a19..879bae5 100644 --- a/context.go +++ b/context.go @@ -36,6 +36,16 @@ func (c *Context) Int64(name string) int64 { return lookupInt64(name, c.flagSet) } +// Uint looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Uint(name string) uint { + return lookupUint(name, c.flagSet) +} + +// Uint64 looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Uint64(name string) uint64 { + return lookupUint64(name, c.flagSet) +} + // Duration 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 { @@ -103,6 +113,22 @@ func (c *Context) GlobalInt64(name string) int64 { return 0 } +// GlobalUint looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalUint(name string) uint { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupUint(name, fs) + } + return 0 +} + +// GlobalUint64 looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalUint64(name string) uint64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupUint64(name, fs) + } + return 0 +} + // GlobalFloat64 looks up the value of a global float64 flag, returns float64(0) // if no float64 flag exists func (c *Context) GlobalFloat64(name string) float64 { @@ -334,11 +360,11 @@ func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { func lookupInt(name string, set *flag.FlagSet) int { f := set.Lookup(name) if f != nil { - val, err := strconv.Atoi(f.Value.String()) + val, err := strconv.ParseInt(f.Value.String(), 0, 64) if err != nil { return 0 } - return val + return int(val) } return 0 @@ -347,7 +373,33 @@ func lookupInt(name string, set *flag.FlagSet) int { func lookupInt64(name string, set *flag.FlagSet) int64 { f := set.Lookup(name) if f != nil { - val, err := strconv.ParseInt(f.Value.String(), 10, 64) + val, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return val + } + + return 0 +} + +func lookupUint(name string, set *flag.FlagSet) uint { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return uint(val) + } + + return 0 +} + +func lookupUint64(name string, set *flag.FlagSet) uint64 { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseUint(f.Value.String(), 0, 64) if err != nil { return 0 } diff --git a/context_test.go b/context_test.go index ae37637..5c68fdd 100644 --- a/context_test.go +++ b/context_test.go @@ -9,11 +9,15 @@ import ( func TestNewContext(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") - set.Int("myflagInt64", 12, "doc") + set.Int64("myflagInt64", int64(12), "doc") + set.Uint("myflagUint", uint(93), "doc") + set.Uint64("myflagUint64", uint64(93), "doc") set.Float64("myflag64", float64(17), "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Int("myflag", 42, "doc") globalSet.Int64("myflagInt64", int64(42), "doc") + globalSet.Uint("myflagUint", uint(33), "doc") + globalSet.Uint64("myflagUint64", uint64(33), "doc") globalSet.Float64("myflag64", float64(47), "doc") globalCtx := NewContext(nil, globalSet, nil) command := Command{Name: "mycommand"} @@ -21,9 +25,13 @@ func TestNewContext(t *testing.T) { c.Command = command expect(t, c.Int("myflag"), 12) expect(t, c.Int64("myflagInt64"), int64(12)) + expect(t, c.Uint("myflagUint"), uint(93)) + expect(t, c.Uint64("myflagUint64"), uint64(93)) expect(t, c.Float64("myflag64"), float64(17)) expect(t, c.GlobalInt("myflag"), 42) expect(t, c.GlobalInt64("myflagInt64"), int64(42)) + expect(t, c.GlobalUint("myflagUint"), uint(33)) + expect(t, c.GlobalUint64("myflagUint64"), uint64(33)) expect(t, c.GlobalFloat64("myflag64"), float64(47)) expect(t, c.Command.Name, "mycommand") } @@ -42,6 +50,20 @@ func TestContext_Int64(t *testing.T) { expect(t, c.Int64("myflagInt64"), int64(12)) } +func TestContext_Uint(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Uint("myflagUint", uint(13), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Uint("myflagUint"), uint(13)) +} + +func TestContext_Uint64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Uint64("myflagUint64", uint64(9), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Uint64("myflagUint64"), uint64(9)) +} + func TestContext_GlobalInt(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") diff --git a/flag.go b/flag.go index 6af15ed..f8a28d1 100644 --- a/flag.go +++ b/flag.go @@ -534,6 +534,94 @@ func (f Int64Flag) GetName() string { return f.Name } +// UintFlag is a flag that takes an unsigned integer +type UintFlag struct { + Name string + Value uint + Usage string + EnvVar string + Destination *uint + Hidden bool +} + +// String returns the usage +func (f UintFlag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f UintFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err == nil { + f.Value = uint(envValInt) + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.UintVar(f.Destination, name, f.Value, f.Usage) + return + } + set.Uint(name, f.Value, f.Usage) + }) +} + +// GetName returns the name of the flag. +func (f UintFlag) GetName() string { + return f.Name +} + +// Uint64Flag is a flag that takes an unsigned 64-bit integer +type Uint64Flag struct { + Name string + Value uint64 + Usage string + EnvVar string + Destination *uint64 + Hidden bool +} + +// String returns the usage +func (f Uint64Flag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f Uint64Flag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err == nil { + f.Value = uint64(envValInt) + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Uint64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Uint64(name, f.Value, f.Usage) + }) +} + +// GetName returns the name of the flag. +func (f Uint64Flag) GetName() string { + return f.Name +} + // DurationFlag is a flag that takes a duration specified in Go's duration // format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { diff --git a/flag_test.go b/flag_test.go index 296d5ac..a7afcc4 100644 --- a/flag_test.go +++ b/flag_test.go @@ -198,6 +198,78 @@ func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { } } +var uintFlagTests = []struct { + name string + expected string +}{ + {"nerfs", "--nerfs value\t(default: 41)"}, + {"N", "-N value\t(default: 41)"}, +} + +func TestUintFlagHelpOutput(t *testing.T) { + for _, test := range uintFlagTests { + flag := UintFlag{Name: test.name, Value: 41} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range uintFlagTests { + flag := UintFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var uint64FlagTests = []struct { + name string + expected string +}{ + {"gerfs", "--gerfs value\t(default: 8589934582)"}, + {"G", "-G value\t(default: 8589934582)"}, +} + +func TestUint64FlagHelpOutput(t *testing.T) { + for _, test := range uint64FlagTests { + flag := Uint64Flag{Name: test.name, Value: 8589934582} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range uint64FlagTests { + flag := UintFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + var durationFlagTests = []struct { name string expected string From ee69d3c011b74a397d473ba29fbb076be6f8df4c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 16 Jun 2016 11:47:19 -0400 Subject: [PATCH 226/381] Generate TOC in README and assert it is up to date --- .gitignore | 1 + .travis.yml | 8 ++++++++ README.md | 37 ++++++++++++++++++++++++++++++++++--- runtests | 8 +++++++- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7823778..faf70c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.coverprofile +node_modules/ diff --git a/.travis.yml b/.travis.yml index 657e96a..273d017 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: go sudo: false +cache: + directories: + - node_modules + go: - 1.2.2 - 1.3.3 @@ -25,8 +29,12 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... +- if [ ! -f node_modules/.bin/markdown-toc ] ; then + npm install markdown-toc ; + fi script: - ./runtests vet - ./runtests test - ./runtests gfmxr +- ./runtests toc diff --git a/README.md b/README.md index 9bbfe83..005dba0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +cli +=== + [![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli) [![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) @@ -6,9 +9,6 @@ [![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / [![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) - -# cli - **Notice:** This is the library formerly known as `github.com/codegangsta/cli` -- Github will automatically redirect requests to this repository, but we recommend updating your references for clarity. @@ -17,6 +17,37 @@ cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. + + +- [Overview](#overview) +- [Installation](#installation) + * [Supported platforms](#supported-platforms) + * [Using the `v2` branch](#using-the-v2-branch) + * [Pinning to the `v1` branch](#pinning-to-the-v1-branch) +- [Getting Started](#getting-started) +- [Examples](#examples) + * [Arguments](#arguments) + * [Flags](#flags) + + [Placeholder Values](#placeholder-values) + + [Alternate Names](#alternate-names) + + [Values from the Environment](#values-from-the-environment) + + [Values from alternate input sources (YAML and others)](#values-from-alternate-input-sources-yaml-and-others) + * [Subcommands](#subcommands) + * [Subcommands categories](#subcommands-categories) + * [Exit code](#exit-code) + * [Bash Completion](#bash-completion) + + [Enabling](#enabling) + + [Distribution](#distribution) + + [Customization](#customization) + * [Generated Help Text](#generated-help-text) + + [Customization](#customization-1) + * [Version Flag](#version-flag) + + [Customization](#customization-2) + + [Full API Example](#full-api-example) +- [Contribution Guidelines](#contribution-guidelines) + + + ## Overview Command line apps are usually so tiny that there is absolutely no reason why diff --git a/runtests b/runtests index 72c1f0d..0a7b483 100755 --- a/runtests +++ b/runtests @@ -18,7 +18,8 @@ def main(sysargs=sys.argv[:]): targets = { 'vet': _vet, 'test': _test, - 'gfmxr': _gfmxr + 'gfmxr': _gfmxr, + 'toc': _toc, } parser = argparse.ArgumentParser() @@ -62,6 +63,11 @@ def _vet(): _run('go vet ./...'.split()) +def _toc(): + _run(['node_modules/.bin/markdown-toc', '-i', 'README.md']) + _run(['git', 'diff', '--quiet']) + + def _run(command): print('runtests: {}'.format(' '.join(command)), file=sys.stderr) check_call(command) From c20f912780127757faee8fd4ccafe6fcd02b7ec7 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 20 Jun 2016 10:57:45 -0400 Subject: [PATCH 227/381] Add stronger warning about `v2` volatility and make **Notice** style more consistent --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fe549f..ebb1d74 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ released version of Go on OS X and Windows. For full details, see ### Using the `v2` branch +**Warning**: The `v2` branch is currently unreleased and considered unstable. + There is currently a long-lived branch named `v2` that is intended to land as the new `master` branch once development there has settled down. The current `master` branch (mirrored as `v1`) is being manually merged into `v2` on @@ -1011,7 +1013,7 @@ func main() { #### Full API Example -**NOTE**: This is a contrived (functioning) example meant strictly for API +**Notice**: This is a contrived (functioning) example meant strictly for API demonstration purposes. Use of one's imagination is encouraged. ``` go package main @@ -972,7 +972,7 @@ func main() { app := cli.NewApp() app.Name = "partay" - app.Version = "v19.99.0" + app.Version = "19.99.0" app.Run(os.Args) } ``` @@ -981,7 +981,7 @@ Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e. ``` go package main @@ -1004,7 +1004,7 @@ func main() { app := cli.NewApp() app.Name = "partay" - app.Version = "v19.99.0" + app.Version = "19.99.0" app.Run(os.Args) } ``` @@ -1083,7 +1083,7 @@ func (g *genericType) String() string { func main() { app := cli.NewApp() app.Name = "kənˈtrīv" - app.Version = "v19.99.0" + app.Version = "19.99.0" app.Compiled = time.Now() app.Authors = []cli.Author{ cli.Author{ From 812de9e2507c3ec9ce2750b67e5efe8cc643c0f4 Mon Sep 17 00:00:00 2001 From: kandayasu Date: Tue, 26 Jul 2016 01:37:03 +0900 Subject: [PATCH 259/381] type "TomlMap" to private (rename TomlMap -> tomlMap) --- altsrc/toml_file_loader.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 64250a3..bc2c11d 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -13,7 +13,7 @@ import ( "github.com/urfave/cli" ) -type TomlMap struct { +type tomlMap struct { Map map[interface{}]interface{} } @@ -66,7 +66,7 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { return ret, nil } -func (self *TomlMap) UnmarshalTOML(i interface{}) error { +func (self *tomlMap) UnmarshalTOML(i interface{}) error { if tmp, err := unmarshalMap(i); err == nil { self.Map = tmp } else { @@ -82,7 +82,7 @@ type tomlSourceContext struct { // NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath. func NewTomlSourceFromFile(file string) (InputSourceContext, error) { tsc := &tomlSourceContext{FilePath: file} - var results TomlMap = TomlMap{} + var results tomlMap = tomlMap{} if err := readCommandToml(tsc.FilePath, &results); err != nil { return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error()) } From 36b7d89bedc1626a65c03df4f9fad9d4a96a4354 Mon Sep 17 00:00:00 2001 From: kandayasu Date: Tue, 26 Jul 2016 01:37:18 +0900 Subject: [PATCH 260/381] Fix typo --- altsrc/toml_command_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/altsrc/toml_command_test.go b/altsrc/toml_command_test.go index c887ab2..1d91d56 100644 --- a/altsrc/toml_command_test.go +++ b/altsrc/toml_command_test.go @@ -1,4 +1,4 @@ -// Disabling building of tom support in cases where golang is 1.0 or 1.1 +// Disabling building of toml support in cases where golang is 1.0 or 1.1 // as the encoding library is not implemented or supported. // +build go1.2 From b616f6088660d2eaa33739718f0583f8d467a178 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 31 Jul 2016 12:11:52 -0700 Subject: [PATCH 261/381] Note TOML support in README and CHANGELOG --- CHANGELOG.md | 1 + README.md | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26dd564..8b0d0ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Added - Flag type code generation via `go generate` - Write to stderr and exit 1 if action returns non-nil error +- Added support for TOML to the `altsrc` loader ### Changed - Raise minimum tested/supported Go version to 1.2+ diff --git a/README.md b/README.md index 615e95d..31b25b8 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ applications in an expressive way. + [Placeholder Values](#placeholder-values) + [Alternate Names](#alternate-names) + [Values from the Environment](#values-from-the-environment) - + [Values from alternate input sources (YAML and others)](#values-from-alternate-input-sources-yaml-and-others) + + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) * [Exit code](#exit-code) @@ -513,10 +513,14 @@ func main() { } ``` -#### Values from alternate input sources (YAML and others) +#### Values from alternate input sources (YAML, TOML, and others) There is a separate package altsrc that adds support for getting flag values -from other input sources like YAML. +from other file input sources. + +Currently supported input source formats: +* YAML +* TOML In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below: @@ -538,9 +542,9 @@ the yaml input source for any flags that are defined on that command. As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. -Currently only YAML files are supported but developers can add support for other -input sources by implementing the altsrc.InputSourceContext for their given -sources. +Currently only the aboved specified formats are supported but developers can +add support for other input sources by implementing the +altsrc.InputSourceContext for their given sources. Here is a more complete sample of a command using YAML support: From 6c1f51aa95e7966c49a5211ffabe337bf4d2f3fb Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 31 Jul 2016 14:14:46 -0700 Subject: [PATCH 262/381] Fix context.(Global)IsSet to respect environment variables This appeared to be the least messy approach to hack in support for IsSet also checking environment variables to see if a particular cli.Flag was set without making backwards incompatible changes to the interface. I intend to fix this more properly in v2, probably by adding another method to the cli.Flag interface to push the responsibility down as it occurred to me that it was really the `Flag`s themselves that offer support for configuration via the environment as opposed to the `context` or other supporting structures. This opens the door for the anything implementing the `Flag` interface to have additional sources of input while still supporting `context.IsSet`. --- context.go | 79 ++++++++++++++++++++++++++++++++++++++----------- context_test.go | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 18 deletions(-) diff --git a/context.go b/context.go index 14ad3f7..5ea4f8f 100644 --- a/context.go +++ b/context.go @@ -3,6 +3,8 @@ package cli import ( "errors" "flag" + "os" + "reflect" "strings" ) @@ -11,12 +13,11 @@ import ( // can be used to retrieve context-specific Args and // parsed command-line options. type Context struct { - App *App - Command Command - flagSet *flag.FlagSet - setFlags map[string]bool - globalSetFlags map[string]bool - parentContext *Context + App *App + Command Command + flagSet *flag.FlagSet + setFlags map[string]bool + parentContext *Context } // NewContext creates a new context. For use in when invoking an App or Command action. @@ -43,28 +44,70 @@ func (c *Context) GlobalSet(name, value string) error { func (c *Context) IsSet(name string) bool { if c.setFlags == nil { c.setFlags = make(map[string]bool) + c.flagSet.Visit(func(f *flag.Flag) { c.setFlags[f.Name] = true }) + + c.flagSet.VisitAll(func(f *flag.Flag) { + if _, ok := c.setFlags[f.Name]; ok { + return + } + c.setFlags[f.Name] = false + }) + + // XXX hack to support IsSet for flags with EnvVar + // + // There isn't an easy way to do this with the current implementation since + // whether a flag was set via an environment variable is very difficult to + // determine here. Instead, we intend to introduce a backwards incompatible + // change in version 2 to add `IsSet` to the Flag interface to push the + // responsibility closer to where the information required to determine + // whether a flag is set by non-standard means such as environment + // variables is avaliable. + // + // See https://github.com/urfave/cli/issues/294 for additional discussion + flags := c.Command.Flags + if c.Command.Name == "" { // cannot == Command{} since it contains slice types + if c.App != nil { + flags = c.App.Flags + } + } + for _, f := range flags { + eachName(f.GetName(), func(name string) { + if isSet, ok := c.setFlags[name]; isSet || !ok { + return + } + + envVars := reflect.ValueOf(f).FieldByName("EnvVar").String() + + eachName(envVars, func(envVar string) { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + c.setFlags[name] = true + return + } + }) + }) + } } - return c.setFlags[name] == true + + return c.setFlags[name] } // GlobalIsSet 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) - ctx := c - if ctx.parentContext != nil { - ctx = ctx.parentContext - } - for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { - ctx.flagSet.Visit(func(f *flag.Flag) { - c.globalSetFlags[f.Name] = true - }) + ctx := c + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + + for ; ctx != nil; ctx = ctx.parentContext { + if ctx.IsSet(name) { + return true } } - return c.globalSetFlags[name] + return false } // FlagNames returns a slice of flag names used in this context. diff --git a/context_test.go b/context_test.go index 5c68fdd..0cf84d1 100644 --- a/context_test.go +++ b/context_test.go @@ -2,6 +2,7 @@ package cli import ( "flag" + "os" "testing" "time" ) @@ -180,6 +181,33 @@ func TestContext_IsSet(t *testing.T) { expect(t, c.IsSet("myflagGlobal"), false) } +// XXX Corresponds to hack in context.IsSet for flags with EnvVar field +// Should be moved to `flag_test` in v2 +func TestContext_IsSet_fromEnv(t *testing.T) { + var timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet bool + + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + Float64Flag{Name: "no-env-var, n"}, + }, + Action: func(ctx *Context) error { + timeoutIsSet = ctx.IsSet("timeout") + tIsSet = ctx.IsSet("t") + noEnvVarIsSet = ctx.IsSet("no-env-var") + nIsSet = ctx.IsSet("n") + return nil + }, + } + a.Run([]string{"run"}) + expect(t, timeoutIsSet, true) + expect(t, tIsSet, true) + expect(t, noEnvVarIsSet, false) + expect(t, nIsSet, false) +} + func TestContext_GlobalIsSet(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") @@ -199,6 +227,38 @@ func TestContext_GlobalIsSet(t *testing.T) { expect(t, c.GlobalIsSet("bogusGlobal"), false) } +// XXX Corresponds to hack in context.IsSet for flags with EnvVar field +// Should be moved to `flag_test` in v2 +func TestContext_GlobalIsSet_fromEnv(t *testing.T) { + var timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet bool + + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + Float64Flag{Name: "no-env-var, n"}, + }, + Commands: []Command{ + { + Name: "hello", + Action: func(ctx *Context) error { + timeoutIsSet = ctx.GlobalIsSet("timeout") + tIsSet = ctx.GlobalIsSet("t") + noEnvVarIsSet = ctx.GlobalIsSet("no-env-var") + nIsSet = ctx.GlobalIsSet("n") + return nil + }, + }, + }, + } + a.Run([]string{"run", "hello"}) + expect(t, timeoutIsSet, true) + expect(t, tIsSet, true) + expect(t, noEnvVarIsSet, false) + expect(t, nIsSet, false) +} + func TestContext_NumFlags(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") From 168c95418e66e019fe17b8f4f5c45aa62ff80e23 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 31 Jul 2016 20:09:57 -0700 Subject: [PATCH 263/381] Ensure that EnvVar struct field exists before interrogating it Otherwise you end up with `` which, in practice, would probably work, but this is cleaner. --- context.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 5ea4f8f..15570c5 100644 --- a/context.go +++ b/context.go @@ -79,9 +79,12 @@ func (c *Context) IsSet(name string) bool { return } - envVars := reflect.ValueOf(f).FieldByName("EnvVar").String() + envVarValue := reflect.ValueOf(f).FieldByName("EnvVar") + if !envVarValue.IsValid() { + return + } - eachName(envVars, func(envVar string) { + eachName(envVarValue.String(), func(envVar string) { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { c.setFlags[name] = true From 8e6aa34a1284bc29e84866ed015bca0f3dd2d88e Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 21 Aug 2016 13:51:55 -0700 Subject: [PATCH 264/381] remove the possiblity of end-user's seeing deprecation warnings Instead use deprecation pattern described in https://blog.golang.org/godoc-documenting-go-code. Fixes #507 --- app.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index 0755bb6..3b54518 100644 --- a/app.go +++ b/app.go @@ -62,10 +62,11 @@ type App struct { // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics After AfterFunc + // The action to execute when no subcommands are specified + // Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}` + // *Note*: support for the deprecated `Action` signature will be removed in a future version Action interface{} - // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind - // of deprecation period has passed, maybe? // Execute this function if the proper command cannot be found CommandNotFound CommandNotFoundFunc @@ -247,11 +248,12 @@ func (a *App) Run(arguments []string) (err error) { return err } -// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling +// RunAndExitOnError calls .Run() and exits non-zero if an error was returned +// +// Deprecated: instead you should return an error that fulfills cli.ExitCoder +// to cli.App.Run. This will cause the application to exit with the given eror +// code in the cli.ExitCoder func (a *App) RunAndExitOnError() { - fmt.Fprintf(a.errWriter(), - "DEPRECATED cli.App.RunAndExitOnError. %s See %s\n", - contactSysadmin, runAndExitOnErrorDeprecationURL) if err := a.Run(os.Args); err != nil { fmt.Fprintln(a.errWriter(), err) OsExiter(1) @@ -471,7 +473,7 @@ func HandleAction(action interface{}, context *Context) (err error) { // swallowing all panics that may happen when calling an Action func. s := fmt.Sprintf("%v", r) if strings.HasPrefix(s, "reflect: ") && strings.Contains(s, "too many input arguments") { - err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2) + err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v."), 2) } else { panic(r) } @@ -485,9 +487,6 @@ func HandleAction(action interface{}, context *Context) (err error) { vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) if len(vals) == 0 { - fmt.Fprintf(ErrWriter, - "DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n", - contactSysadmin, appActionDeprecationURL) return nil } From a5ca09a9344c444c8dfcd5c1718d06b75b7aac1e Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 21 Aug 2016 14:06:59 -0700 Subject: [PATCH 265/381] fixup! remove the possiblity of end-user's seeing deprecation warnings --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 3b54518..b9adf46 100644 --- a/app.go +++ b/app.go @@ -473,7 +473,7 @@ func HandleAction(action interface{}, context *Context) (err error) { // swallowing all panics that may happen when calling an Action func. s := fmt.Sprintf("%v", r) if strings.HasPrefix(s, "reflect: ") && strings.Contains(s, "too many input arguments") { - err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v."), 2) + err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v.", r), 2) } else { panic(r) } From c0cf41eb54ec35f7c325e9b42aaec3c33a36194f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 22 Aug 2016 15:26:33 -0400 Subject: [PATCH 266/381] Skip gfmrun installation and tests below go1.3 --- .travis.yml | 2 +- runtests | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d1d820d..a6a1386 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ matrix: os: osx before_script: -- go get github.com/urfave/gfmrun/... +- go get github.com/urfave/gfmrun/... || true - go get golang.org/x/tools/... || true - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; diff --git a/runtests b/runtests index e13faf7..ee22bde 100755 --- a/runtests +++ b/runtests @@ -57,6 +57,10 @@ def _test(): def _gfmrun(): + go_version = check_output('go version'.split()).split()[2] + if go_version < 'go1.3': + print('runtests: skip on {}'.format(go_version), file=sys.stderr) + return _run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md']) From c75c862386f960a352e2e48611de3544d585bac4 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Sat, 27 Aug 2016 19:09:14 -0400 Subject: [PATCH 267/381] Fix import paths in altsrc Uses gopkg.in as the import path for the `altsrc` package. Fixes: #473 --- altsrc/flag.go | 2 +- altsrc/flag_generated.go | 2 +- altsrc/flag_test.go | 2 +- altsrc/input_source_context.go | 2 +- altsrc/map_input_source.go | 2 +- altsrc/toml_command_test.go | 2 +- altsrc/toml_file_loader.go | 2 +- altsrc/yaml_command_test.go | 2 +- altsrc/yaml_file_loader.go | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index 8add2fb..ec14e40 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" ) // FlagInputSourceExtension is an extension interface of cli.Flag that diff --git a/altsrc/flag_generated.go b/altsrc/flag_generated.go index fa76724..b6b96a1 100644 --- a/altsrc/flag_generated.go +++ b/altsrc/flag_generated.go @@ -3,7 +3,7 @@ package altsrc import ( "flag" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" ) // WARNING: This file is generated! diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 218e9b8..9e9c96d 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" ) type testApplyInputSource struct { diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 8ea7e92..276dcda 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -3,7 +3,7 @@ package altsrc import ( "time" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" ) // InputSourceContext is an interface used to allow diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index b1c8e4f..b720995 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" ) // MapInputSource implements InputSourceContext to return diff --git a/altsrc/toml_command_test.go b/altsrc/toml_command_test.go index 1d91d56..a5053d4 100644 --- a/altsrc/toml_command_test.go +++ b/altsrc/toml_command_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" ) func TestCommandTomFileTest(t *testing.T) { diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index bc2c11d..39c124f 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -10,7 +10,7 @@ import ( "reflect" "github.com/BurntSushi/toml" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" ) type tomlMap struct { diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 31f78ce..9d3f431 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" ) func TestCommandYamlFileTest(t *testing.T) { diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index b4e3365..335356f 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -12,7 +12,7 @@ import ( "net/url" "os" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v1" "gopkg.in/yaml.v2" ) From df95e0708f6416a3f148c984464cfd417e9eccfe Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 3 Sep 2016 14:22:45 -0700 Subject: [PATCH 268/381] Manually set import in altsrc flag generation Otherwise `goimports` switches it to `github.com/urfave/cli` --- generate-flag-types | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/generate-flag-types b/generate-flag-types index 8567f5d..47a168b 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -202,6 +202,10 @@ def _write_altsrc_flag_types(outfile, types): _fwrite(outfile, """\ package altsrc + import ( + "gopkg.in/urfave/cli.v1" + ) + // WARNING: This file is generated! """) From 4b62cb6b3397f6046f1e001f1be53daa7b4d970d Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 3 Sep 2016 13:18:48 -0700 Subject: [PATCH 269/381] Update wording around gopkg.in pinning to be more accurate Since we have `v1.X` tags, gopkg.in/urfave/cli.v1 will pull the latest tagged release rather than the `v1` branch. Since there are no tagged `v2` releases, `gopkg.in` will pull the latest commit of that branch. Fixes #513 --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 31b25b8..2c863ae 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ applications in an expressive way. - [Installation](#installation) * [Supported platforms](#supported-platforms) * [Using the `v2` branch](#using-the-v2-branch) - * [Pinning to the `v1` branch](#pinning-to-the-v1-branch) + * [Pinning to the `v1` releases](#pinning-to-the-v1-releases) - [Getting Started](#getting-started) - [Examples](#examples) * [Arguments](#arguments) @@ -104,11 +104,11 @@ import ( ... ``` -### Pinning to the `v1` branch +### Pinning to the `v1` releases Similarly to the section above describing use of the `v2` branch, if one wants to avoid any unexpected compatibility pains once `v2` becomes `master`, then -pinning to the `v1` branch is an acceptable option, e.g.: +pinning to `v1` is an acceptable option, e.g.: ``` $ go get gopkg.in/urfave/cli.v1 @@ -122,6 +122,8 @@ import ( ... ``` +This will pull the latest tagged `v1` release (e.g. `v1.18.1` at the time of writing). + ## Getting Started One of the philosophies behind cli is that an API should be playful and full of From 76164d6e36d466a0b42d9079d2f4789ace074184 Mon Sep 17 00:00:00 2001 From: Nathan Bullock Date: Mon, 5 Sep 2016 07:16:05 -0400 Subject: [PATCH 270/381] Fix typo in README cosumized -> customized --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31b25b8..2971e26 100644 --- a/README.md +++ b/README.md @@ -847,7 +847,7 @@ func main() { ### Generated Help Text -The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked by the cli internals in order to print generated help text for the app, command, or subcommand, and break execution. @@ -952,7 +952,7 @@ is checked by the cli internals in order to print the `App.Version` via #### Customization -The default flag may be cusomized to something other than `-v/--version` by +The default flag may be customized to something other than `-v/--version` by setting `cli.VersionFlag`, e.g.: +``` go +package main + +import ( + "os" + "sort" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "Language for the greeting", + }, + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + sort.Sort(cli.FlagsByName(app.Flags)) + + app.Run(os.Args) +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +--lang value, -l value Language for the greeting (default: "english") +``` + #### Values from the Environment You can also have the default value set from the environment via `EnvVar`. e.g. diff --git a/flag.go b/flag.go index e748c02..1ff28d3 100644 --- a/flag.go +++ b/flag.go @@ -37,6 +37,21 @@ var HelpFlag = BoolFlag{ // to display a flag. var FlagStringer FlagStringFunc = stringifyFlag +// FlagsByName is a slice of Flag. +type FlagsByName []Flag + +func (f FlagsByName) Len() int { + return len(f) +} + +func (f FlagsByName) Less(i, j int) bool { + return f[i].GetName() < f[j].GetName() +} + +func (f FlagsByName) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + // Flag is a common interface related to parsing flags in cli. // For more advanced flag parsing techniques, it is recommended that // this interface be implemented. From d913b71c72fbd5857a4e5219d53023d14f0eb346 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 21 Oct 2016 10:42:30 +0200 Subject: [PATCH 280/381] .travis.yml: add go 1.7.x Signed-off-by: Antonio Murdaca --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index a6a1386..94836d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ go: - 1.4.2 - 1.5.x - 1.6.x +- 1.7.x - master matrix: @@ -20,6 +21,8 @@ matrix: include: - go: 1.6.x os: osx + - go: 1.7.x + os: osx before_script: - go get github.com/urfave/gfmrun/... || true From 0c143a2a268ff019a99698fe2e6d9e7bfed088c7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 22 Oct 2016 15:58:07 -0700 Subject: [PATCH 281/381] app: Fix trailing space for Author.String() This code initially landed with lots of space: '{name} <{email}> ' or: '{name} ' in 3d718330 (app, help: add support for multiple authors, 2015-01-31). The doubled space between the name and email was removed in c6592bb4 (app, help: add backwards compatibility for Authors, 2015-02-21), but a trailing space remained in both the email and email-less cases. This commit removes that trailing space. --- app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index e5c2839..77eb141 100644 --- a/app.go +++ b/app.go @@ -458,10 +458,10 @@ type Author struct { func (a Author) String() string { e := "" if a.Email != "" { - e = "<" + a.Email + "> " + e = " <" + a.Email + ">" } - return fmt.Sprintf("%v %v", a.Name, e) + return fmt.Sprintf("%v%v", a.Name, e) } // HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an From 3c2bce5807d92a07ae1d3adf503a9811525e3e4b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 22 Oct 2016 16:02:21 -0700 Subject: [PATCH 282/381] help: Cleanup AppHelpTemplate trailing whitespace Most of the changes here remove trailing whitespace, but I also add code to select "AUTHOR" or "AUTHORS" as appropriate instead of the previous "AUTHOR(S)". The template for listing with an entry per line is: {{range $index, $entry := pipeline}}{{if $index}} {{end}}{{$entry}}{{end}} That range syntax is discussed in [1]. Also add a unit test, which tests both these whitespace changes and also the earlier App.Description addition. [1]: https://golang.org/pkg/text/template/#hdr-Variables --- app_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++- help.go | 31 +++++++++++++++-------------- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/app_test.go b/app_test.go index 23c8aa6..2b541f8 100644 --- a/app_test.go +++ b/app_test.go @@ -90,7 +90,62 @@ func ExampleApp_Run_subcommand() { // Hello, Jeremy } -func ExampleApp_Run_help() { +func ExampleApp_Run_appHelp() { + // set args for examples sake + os.Args = []string{"greet", "help"} + + app := NewApp() + app.Name = "greet" + app.Version = "0.1.0" + app.Description = "This is how we describe greet the app" + app.Authors = []Author{ + {Name: "Harrison", Email: "harrison@lolwut.com"}, + {Name: "Oliver Allen", Email: "oliver@toyshop.com"}, + } + app.Flags = []Flag{ + StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + } + app.Commands = []Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, + }, + } + app.Run(os.Args) + // Output: + // NAME: + // greet - A new cli application + // + // USAGE: + // greet [global options] command [command options] [arguments...] + // + // VERSION: + // 0.1.0 + // + // DESCRIPTION: + // This is how we describe greet the app + // + // AUTHORS: + // Harrison + // Oliver Allen + // + // COMMANDS: + // describeit, d use it to see a description + // help, h Shows a list of commands or help for one command + // + // GLOBAL OPTIONS: + // --name value a name to say (default: "bob") + // --help, -h show help + // --version, -v print the version +} + +func ExampleApp_Run_commandHelp() { // set args for examples sake os.Args = []string{"greet", "h", "describeit"} diff --git a/help.go b/help.go index 529e5ea..515f744 100644 --- a/help.go +++ b/help.go @@ -16,27 +16,28 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} - {{if .Version}}{{if not .HideVersion}} + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + VERSION: - {{.Version}} - {{end}}{{end}}{{if .Description}} + {{.Version}}{{end}}{{end}}{{if .Description}} + DESCRIPTION: - {{.Description}} -{{end}}{{if len .Authors}} -AUTHOR(S): - {{range .Authors}}{{.}}{{end}} - {{end}}{{if .VisibleCommands}} + {{.Description}}{{end}}{{if len .Authors}} + +AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} -{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + GLOBAL OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright}} + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + COPYRIGHT: - {{.Copyright}} - {{end}} + {{.Copyright}}{{end}} ` // CommandHelpTemplate is the text template for the command help topic. From b377b5d9e93d2359a7ffe16fc1bb902b3df291b6 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Tue, 1 Nov 2016 20:33:12 -0700 Subject: [PATCH 283/381] Use type assertions rather than reflection to determine how to call the `Action` This has some benefits, but results in possibly less informative error messaging; however, given that there are only two accepted types, I think the error messaging is sufficient. --- app.go | 52 +++++++++++----------------------------------------- app_test.go | 6 +++--- 2 files changed, 14 insertions(+), 44 deletions(-) diff --git a/app.go b/app.go index dd2d2df..26cf09a 100644 --- a/app.go +++ b/app.go @@ -6,9 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" - "reflect" "sort" - "strings" "time" ) @@ -19,11 +17,8 @@ var ( contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." - errNonFuncAction = NewExitError("ERROR invalid Action type. "+ - fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+ - fmt.Sprintf("See %s", appActionDeprecationURL), 2) - errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+ - fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+ + errInvalidActionType = NewExitError("ERROR invalid Action type. "+ + fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ fmt.Sprintf("See %s", appActionDeprecationURL), 2) ) @@ -468,41 +463,16 @@ func (a Author) String() string { return fmt.Sprintf("%v%v", a.Name, e) } -// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an -// ActionFunc, a func with the legacy signature for Action, or some other -// invalid thing. If it's an ActionFunc or a func with the legacy signature for -// Action, the func is run! +// HandleAction attempts to figure out which Action signature was used. If +// it's an ActionFunc or a func with the legacy signature for Action, the func +// is run! func HandleAction(action interface{}, context *Context) (err error) { - defer func() { - if r := recover(); r != nil { - // Try to detect a known reflection error from *this scope*, rather than - // swallowing all panics that may happen when calling an Action func. - s := fmt.Sprintf("%v", r) - if strings.HasPrefix(s, "reflect: ") && strings.Contains(s, "too many input arguments") { - err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v.", r), 2) - } else { - panic(r) - } - } - }() - - if reflect.TypeOf(action).Kind() != reflect.Func { - return errNonFuncAction - } - - vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) - - if len(vals) == 0 { + if a, ok := action.(func(*Context) error); ok { + return a(context) + } else if a, ok := action.(func(*Context)); ok { // deprecated function signature + a(context) return nil + } else { + return errInvalidActionType } - - if len(vals) > 1 { - return errInvalidActionSignature - } - - if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok { - return retErr - } - - return err } diff --git a/app_test.go b/app_test.go index e36a8c2..40a598d 100644 --- a/app_test.go +++ b/app_test.go @@ -1492,7 +1492,7 @@ func TestHandleAction_WithNonFuncAction(t *testing.T) { t.Fatalf("expected to receive a *ExitError") } - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { + if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") { t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) } @@ -1516,7 +1516,7 @@ func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { t.Fatalf("expected to receive a *ExitError") } - if !strings.HasPrefix(exitErr.Error(), "ERROR unknown Action error") { + if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) } @@ -1540,7 +1540,7 @@ func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { t.Fatalf("expected to receive a *ExitError") } - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action signature") { + if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) } From ea3df26e645524f2b56f2c6c4d81826ab43a3f45 Mon Sep 17 00:00:00 2001 From: Joshua Rubin Date: Fri, 4 Nov 2016 14:56:28 -0600 Subject: [PATCH 284/381] make shell autocomplete more robust --- app.go | 18 +++++++++--------- command.go | 29 ++++++++++++----------------- context.go | 9 ++++++++- help.go | 39 +++++++++++++++++++++++++++++++-------- 4 files changed, 60 insertions(+), 35 deletions(-) diff --git a/app.go b/app.go index 26cf09a..50edf9a 100644 --- a/app.go +++ b/app.go @@ -145,10 +145,6 @@ func (a *App) Setup() { } } - if a.EnableBashCompletion { - a.appendFlag(BashCompletionFlag) - } - if !a.HideVersion { a.appendFlag(VersionFlag) } @@ -173,6 +169,14 @@ func (a *App) Setup() { func (a *App) Run(arguments []string) (err error) { a.Setup() + // handle the completion flag separately from the flagset since + // completion could be attempted after a flag, but before its value was put + // on the command line. this causes the flagset to interpret the completion + // flag name as the value of the flag before it which is undesirable + // note that we can only do this because the shell autocomplete function + // always appends the completion flag at the end of the command + complete, arguments := checkCompleteFlag(a, arguments) + // parse flags set := flagSet(a.Name, a.Flags) set.SetOutput(ioutil.Discard) @@ -184,6 +188,7 @@ func (a *App) Run(arguments []string) (err error) { ShowAppHelp(context) return nerr } + context.complete = complete if checkCompletions(context) { return nil @@ -283,11 +288,6 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } a.Commands = newCmds - // append flags - if a.EnableBashCompletion { - a.appendFlag(BashCompletionFlag) - } - // parse flags set := flagSet(a.Name, a.Flags) set.SetOutput(ioutil.Discard) diff --git a/command.go b/command.go index d955249..04eb0b8 100644 --- a/command.go +++ b/command.go @@ -87,10 +87,6 @@ func (c Command) Run(ctx *Context) (err error) { ) } - if ctx.App.EnableBashCompletion { - c.Flags = append(c.Flags, BashCompletionFlag) - } - set := flagSet(c.Name, c.Flags) set.SetOutput(ioutil.Discard) @@ -132,18 +128,6 @@ func (c Command) Run(ctx *Context) (err error) { err = set.Parse(ctx.Args().Tail()) } - if err != nil { - if c.OnUsageError != nil { - err := c.OnUsageError(ctx, err, false) - HandleExitCoder(err) - return err - } - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error()) - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return err - } - nerr := normalizeFlags(c.Flags, set) if nerr != nil { fmt.Fprintln(ctx.App.Writer, nerr) @@ -153,11 +137,22 @@ func (c Command) Run(ctx *Context) (err error) { } context := NewContext(ctx.App, set, ctx) - if checkCommandCompletions(context, c.Name) { return nil } + if err != nil { + if c.OnUsageError != nil { + err := c.OnUsageError(ctx, err, false) + HandleExitCoder(err) + return err + } + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error()) + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err + } + if checkCommandHelp(context, c.Name) { return nil } diff --git a/context.go b/context.go index 492a742..81404c4 100644 --- a/context.go +++ b/context.go @@ -15,6 +15,7 @@ import ( type Context struct { App *App Command Command + complete bool flagSet *flag.FlagSet setFlags map[string]bool parentContext *Context @@ -22,7 +23,13 @@ type Context struct { // NewContext creates a new context. For use in when invoking an App or Command action. func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { - return &Context{App: app, flagSet: set, parentContext: parentCtx} + c := &Context{App: app, flagSet: set, parentContext: parentCtx} + + if parentCtx != nil { + c.complete = parentCtx.complete + } + + return c } // NumFlags returns the number of flags set diff --git a/help.go b/help.go index 515f744..e453e72 100644 --- a/help.go +++ b/help.go @@ -252,20 +252,43 @@ func checkSubcommandHelp(c *Context) bool { return false } +func checkCompleteFlag(a *App, arguments []string) (bool, []string) { + if !a.EnableBashCompletion { + return false, arguments + } + + pos := len(arguments) - 1 + lastArg := arguments[pos] + + if lastArg != "--"+BashCompletionFlag.Name { + return false, arguments + } + + return true, arguments[:pos] +} + func checkCompletions(c *Context) bool { - if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { - ShowCompletions(c) - return true + if !c.complete { + return false } - return false + if args := c.Args(); args.Present() { + name := args.First() + if cmd := c.App.Command(name); cmd != nil { + // let the command handle the completion + return false + } + } + + ShowCompletions(c) + return true } func checkCommandCompletions(c *Context, name string) bool { - if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { - ShowCommandCompletions(c, name) - return true + if !c.complete { + return false } - return false + ShowCommandCompletions(c, name) + return true } From 79591889a97af09f177601e3e9e61f9c7e359359 Mon Sep 17 00:00:00 2001 From: mh-cbon Date: Sat, 5 Nov 2016 10:27:46 +0100 Subject: [PATCH 285/381] Close #558: detect FormattedError and print their stack trace --- errors.go | 14 ++++++++++---- errors_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/errors.go b/errors.go index ddef369..e0f295e 100644 --- a/errors.go +++ b/errors.go @@ -34,6 +34,10 @@ func (m MultiError) Error() string { return strings.Join(errs, "\n") } +type ErrorFormatter interface { + Format(s fmt.State, verb rune) +} + // ExitCoder is the interface checked by `App` and `Command` for a custom exit // code type ExitCoder interface { @@ -44,11 +48,11 @@ type ExitCoder interface { // ExitError fulfills both the builtin `error` interface and `ExitCoder` type ExitError struct { exitCode int - message string + message interface{} } // NewExitError makes a new *ExitError -func NewExitError(message string, exitCode int) *ExitError { +func NewExitError(message interface{}, exitCode int) *ExitError { return &ExitError{ exitCode: exitCode, message: message, @@ -58,7 +62,7 @@ func NewExitError(message string, exitCode int) *ExitError { // Error returns the string message, fulfilling the interface required by // `error` func (ee *ExitError) Error() string { - return ee.message + return fmt.Sprintf("%v", ee.message) } // ExitCode returns the exit code, fulfilling the interface required by @@ -77,7 +81,9 @@ func HandleExitCoder(err error) { } if exitErr, ok := err.(ExitCoder); ok { - if err.Error() != "" { + if _, ok := exitErr.(ErrorFormatter); ok { + fmt.Fprintf(ErrWriter, "%+v\n", err) + } else { fmt.Fprintln(ErrWriter, err) } OsExiter(exitErr.ExitCode()) diff --git a/errors_test.go b/errors_test.go index 04df031..e6dfc16 100644 --- a/errors_test.go +++ b/errors_test.go @@ -3,6 +3,7 @@ package cli import ( "bytes" "errors" + "fmt" "testing" ) @@ -104,3 +105,32 @@ func TestHandleExitCoder_ErrorWithoutMessage(t *testing.T) { expect(t, called, true) expect(t, ErrWriter.(*bytes.Buffer).String(), "") } + +// make a stub to not import pkg/errors +type ErrorWithFormat struct { + error +} + +func (f *ErrorWithFormat) Format(s fmt.State, verb rune) { + fmt.Fprintf(s, "This the format: %v", f.error) +} + +func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { + called := false + + OsExiter = func(rc int) { + called = true + } + ErrWriter = &bytes.Buffer{} + + defer func() { + OsExiter = fakeOsExiter + ErrWriter = fakeErrWriter + }() + + err := &ErrorWithFormat{error: errors.New("I am formatted")} + HandleExitCoder(err) + + expect(t, called, true) + expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: I am formatted\n") +} From 6c50b15a273d29dc3820b5e4d50d78eeb113d335 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Fri, 11 Nov 2016 13:11:50 +0900 Subject: [PATCH 286/381] Exit with the code of ExitCoder if exists --- errors.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/errors.go b/errors.go index fd67b96..583f89f 100644 --- a/errors.go +++ b/errors.go @@ -85,10 +85,8 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.Errors { - fmt.Fprintln(ErrWriter, merr) - } - OsExiter(1) + code := handleMultiError(multiErr) + OsExiter(code) return } @@ -97,3 +95,18 @@ func HandleExitCoder(err error) { } OsExiter(1) } + +func handleMultiError(multiErr MultiError) int { + code := 1 + for _, merr := range multiErr.Errors { + if multiErr2, ok := merr.(MultiError); ok { + code = handleMultiError(multiErr2) + } else { + fmt.Fprintln(ErrWriter, merr) + if exitErr, ok := merr.(ExitCoder); ok { + code = exitErr.ExitCode() + } + } + } + return code +} From 0113f56d1094e8565f6d1ad10526865fd61d57aa Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 12 Nov 2016 13:37:07 -0800 Subject: [PATCH 287/381] If no action is specified on the command or app, print the help documentation Rather than panic'ing or displaying an opaque error message about the signature which is more confusing to the end user. Fixes #562 --- app.go | 4 ++++ app_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ command.go | 4 ++++ help.go | 2 +- 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 26cf09a..a90a4df 100644 --- a/app.go +++ b/app.go @@ -242,6 +242,10 @@ func (a *App) Run(arguments []string) (err error) { } } + if a.Action == nil { + a.Action = helpCommand.Action + } + // Run default Action err = HandleAction(a.Action, context) diff --git a/app_test.go b/app_test.go index 40a598d..711de47 100644 --- a/app_test.go +++ b/app_test.go @@ -178,6 +178,49 @@ func ExampleApp_Run_commandHelp() { // This is how we describe describeit the function } +func ExampleApp_Run_noAction() { + app := App{} + app.Name = "greet" + app.Run([]string{"greet"}) + // Output: + // NAME: + // greet + // + // USAGE: + // [global options] command [command options] [arguments...] + // + // COMMANDS: + // help, h Shows a list of commands or help for one command + // + // GLOBAL OPTIONS: + // --help, -h show help + // --version, -v print the version +} + +func ExampleApp_Run_subcommandNoAction() { + app := App{} + app.Name = "greet" + app.Commands = []Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + }, + } + app.Run([]string{"greet", "describeit"}) + // Output: + // NAME: + // describeit - use it to see a description + // + // USAGE: + // describeit [arguments...] + // + // DESCRIPTION: + // This is how we describe describeit the function + +} + func ExampleApp_Run_bashComplete() { // set args for examples sake os.Args = []string{"greet", "--generate-bash-completion"} diff --git a/command.go b/command.go index d955249..8f1a215 100644 --- a/command.go +++ b/command.go @@ -187,6 +187,10 @@ func (c Command) Run(ctx *Context) (err error) { } } + if c.Action == nil { + c.Action = helpSubcommand.Action + } + context.Command = c err = HandleAction(c.Action, context) diff --git a/help.go b/help.go index 515f744..5ee11a6 100644 --- a/help.go +++ b/help.go @@ -13,7 +13,7 @@ import ( // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var AppHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} USAGE: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} From b0a8f25773c10b7445d0d740d380a56e3dcd2166 Mon Sep 17 00:00:00 2001 From: mh-cbon Date: Sun, 13 Nov 2016 22:20:13 +0100 Subject: [PATCH 288/381] 558: handle multi formatter errors --- errors.go | 16 +++++++++++----- errors_test.go | 23 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/errors.go b/errors.go index e0f295e..0206ff4 100644 --- a/errors.go +++ b/errors.go @@ -81,10 +81,12 @@ func HandleExitCoder(err error) { } if exitErr, ok := err.(ExitCoder); ok { - if _, ok := exitErr.(ErrorFormatter); ok { - fmt.Fprintf(ErrWriter, "%+v\n", err) - } else { - fmt.Fprintln(ErrWriter, err) + if err.Error() != "" { + if _, ok := exitErr.(ErrorFormatter); ok { + fmt.Fprintf(ErrWriter, "%+v\n", err) + } else { + fmt.Fprintln(ErrWriter, err) + } } OsExiter(exitErr.ExitCode()) return @@ -98,7 +100,11 @@ func HandleExitCoder(err error) { } if err.Error() != "" { - fmt.Fprintln(ErrWriter, err) + if _, ok := err.(ErrorFormatter); ok { + fmt.Fprintf(ErrWriter, "%+v\n", err) + } else { + fmt.Fprintln(ErrWriter, err) + } } OsExiter(1) } diff --git a/errors_test.go b/errors_test.go index e6dfc16..131bd38 100644 --- a/errors_test.go +++ b/errors_test.go @@ -111,6 +111,10 @@ type ErrorWithFormat struct { error } +func NewErrorWithFormat(m string) *ErrorWithFormat { + return &ErrorWithFormat{error: errors.New(m)} +} + func (f *ErrorWithFormat) Format(s fmt.State, verb rune) { fmt.Fprintf(s, "This the format: %v", f.error) } @@ -128,9 +132,26 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { ErrWriter = fakeErrWriter }() - err := &ErrorWithFormat{error: errors.New("I am formatted")} + err := NewErrorWithFormat("I am formatted") HandleExitCoder(err) expect(t, called, true) expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: I am formatted\n") } + +func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { + called := false + + OsExiter = func(rc int) { + called = true + } + ErrWriter = &bytes.Buffer{} + + defer func() { OsExiter = fakeOsExiter }() + + err := NewMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2")) + HandleExitCoder(err) + + expect(t, called, true) + expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: err1\nThis the format: err2\n") +} From a00c3f5872b3129785af69ee16a2912cd58079d3 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 11 Sep 2016 20:32:43 -0700 Subject: [PATCH 289/381] Consider empty environment variables as set When assigning values to flags (also when interogatting via `context.(Global)IsSet`. For boolean flags, consider empty as `false`. Using `syscall.Getenv` rather than `os.LookupEnv` in order to support older Golang versions. --- altsrc/flag.go | 8 +++---- context.go | 4 ++-- context_test.go | 8 ++++++- flag.go | 38 ++++++++++++++++++----------- flag_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 22 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index ec14e40..84ef009 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -2,9 +2,9 @@ package altsrc import ( "fmt" - "os" "strconv" "strings" + "syscall" "gopkg.in/urfave/cli.v1" ) @@ -237,13 +237,11 @@ func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourc func isEnvVarSet(envVars string) bool { for _, envVar := range strings.Split(envVars, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if _, ok := syscall.Getenv(envVar); ok { // TODO: Can't use this for bools as // set means that it was true or false based on // Bool flag type, should work for other types - if len(envVal) > 0 { - return true - } + return true } } diff --git a/context.go b/context.go index 492a742..3893a50 100644 --- a/context.go +++ b/context.go @@ -3,9 +3,9 @@ package cli import ( "errors" "flag" - "os" "reflect" "strings" + "syscall" ) // Context is a type that is passed through to @@ -91,7 +91,7 @@ func (c *Context) IsSet(name string) bool { eachName(envVarValue.String(), func(envVar string) { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if _, ok := syscall.Getenv(envVar); ok { c.setFlags[name] = true return } diff --git a/context_test.go b/context_test.go index 0cf84d1..b5bc993 100644 --- a/context_test.go +++ b/context_test.go @@ -184,18 +184,22 @@ func TestContext_IsSet(t *testing.T) { // XXX Corresponds to hack in context.IsSet for flags with EnvVar field // Should be moved to `flag_test` in v2 func TestContext_IsSet_fromEnv(t *testing.T) { - var timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet bool + var timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet, passwordIsSet, pIsSet bool os.Clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + os.Setenv("APP_PASSWORD", "") a := App{ Flags: []Flag{ Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + Float64Flag{Name: "password, p", EnvVar: "APP_PASSWORD"}, Float64Flag{Name: "no-env-var, n"}, }, Action: func(ctx *Context) error { timeoutIsSet = ctx.IsSet("timeout") tIsSet = ctx.IsSet("t") + passwordIsSet = ctx.IsSet("password") + pIsSet = ctx.IsSet("p") noEnvVarIsSet = ctx.IsSet("no-env-var") nIsSet = ctx.IsSet("n") return nil @@ -204,6 +208,8 @@ func TestContext_IsSet_fromEnv(t *testing.T) { a.Run([]string{"run"}) expect(t, timeoutIsSet, true) expect(t, tIsSet, true) + expect(t, passwordIsSet, true) + expect(t, pIsSet, true) expect(t, noEnvVarIsSet, false) expect(t, nIsSet, false) } diff --git a/flag.go b/flag.go index 1ff28d3..5e565fb 100644 --- a/flag.go +++ b/flag.go @@ -3,11 +3,11 @@ package cli import ( "flag" "fmt" - "os" "reflect" "runtime" "strconv" "strings" + "syscall" "time" ) @@ -92,7 +92,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { val.Set(envVal) break } @@ -128,7 +128,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { newVal := &StringSlice{} for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) @@ -176,7 +176,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { newVal := &IntSlice{} for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) @@ -227,7 +227,7 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { newVal := &Int64Slice{} for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) @@ -256,7 +256,12 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { + if envVal == "" { + val = false + break + } + envValBool, err := strconv.ParseBool(envVal) if err == nil { val = envValBool @@ -281,7 +286,12 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { + if envVal == "" { + val = false + break + } + envValBool, err := strconv.ParseBool(envVal) if err == nil { val = envValBool @@ -305,7 +315,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { f.Value = envVal break } @@ -326,7 +336,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err == nil { f.Value = int(envValInt) @@ -350,7 +360,7 @@ func (f Int64Flag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err == nil { f.Value = envValInt @@ -374,7 +384,7 @@ func (f UintFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseUint(envVal, 0, 64) if err == nil { f.Value = uint(envValInt) @@ -398,7 +408,7 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseUint(envVal, 0, 64) if err == nil { f.Value = uint64(envValInt) @@ -422,7 +432,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { envValDuration, err := time.ParseDuration(envVal) if err == nil { f.Value = envValDuration @@ -446,7 +456,7 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { + if envVal, ok := syscall.Getenv(envVar); ok { envValFloat, err := strconv.ParseFloat(envVal, 10) if err == nil { f.Value = float64(envValFloat) diff --git a/flag_test.go b/flag_test.go index a7afcc4..58ada36 100644 --- a/flag_test.go +++ b/flag_test.go @@ -29,6 +29,38 @@ func TestBoolFlagHelpOutput(t *testing.T) { } } +func TestParseBoolFromEnv(t *testing.T) { + var boolFlagTests = []struct { + input string + output bool + }{ + {"", false}, + {"1", true}, + {"false", false}, + {"true", true}, + } + + for _, test := range boolFlagTests { + os.Clearenv() + os.Setenv("DEBUG", test.input) + a := App{ + Flags: []Flag{ + BoolFlag{Name: "debug, d", EnvVar: "DEBUG"}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("debug") != test.output { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("debug")) + } + if ctx.Bool("d") != test.output { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("d")) + } + return nil + }, + } + a.Run([]string{"run"}) + } +} + var stringFlagTests = []struct { name string usage string @@ -941,6 +973,38 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { a.Run([]string{"run"}) } +func TestParseBoolTFromEnv(t *testing.T) { + var boolTFlagTests = []struct { + input string + output bool + }{ + {"", false}, + {"1", true}, + {"false", false}, + {"true", true}, + } + + for _, test := range boolTFlagTests { + os.Clearenv() + os.Setenv("DEBUG", test.input) + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "DEBUG"}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("debug") != test.output { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("debug")) + } + if ctx.Bool("d") != test.output { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("d")) + } + return nil + }, + } + a.Run([]string{"run"}) + } +} + func TestParseMultiBoolT(t *testing.T) { a := App{ Flags: []Flag{ From e367fafa3d3ce89a09e75d1650a9a73057a9bf36 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 17 Sep 2016 16:54:29 -0700 Subject: [PATCH 290/381] Return an error when parsing environment variables for values fails Currently cli silently (aside from IntSlice and Int64Slice which oddly printed directly to the error stream) ignores failures that occur when parsing environment variables for their value for flags that define environment variables. Instead, we should propogate up the error to the user. This is accomplished in a backwards compatible manner by adding a new, internal, interface which defines an applyWithError function that all flags here define. In v2, when we can modify the interface, we can drop this secondary interface and modify `Apply` to return an error. --- app.go | 12 ++- app_test.go | 24 +++++- command.go | 5 +- context.go | 5 ++ context_test.go | 44 +++++++++- flag.go | 225 ++++++++++++++++++++++++++++++++++++++++-------- flag_test.go | 83 ++++++++++++++---- 7 files changed, 333 insertions(+), 65 deletions(-) diff --git a/app.go b/app.go index a90a4df..95aa5ed 100644 --- a/app.go +++ b/app.go @@ -174,7 +174,11 @@ func (a *App) Run(arguments []string) (err error) { a.Setup() // parse flags - set := flagSet(a.Name, a.Flags) + set, err := flagSet(a.Name, a.Flags) + if err != nil { + return err + } + set.SetOutput(ioutil.Discard) err = set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) @@ -293,7 +297,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } // parse flags - set := flagSet(a.Name, a.Flags) + set, err := flagSet(a.Name, a.Flags) + if err != nil { + return err + } + set.SetOutput(ioutil.Discard) err = set.Parse(ctx.Args().Tail()) nerr := normalizeFlags(a.Flags, set) diff --git a/app_test.go b/app_test.go index 711de47..83d096f 100644 --- a/app_test.go +++ b/app_test.go @@ -1523,7 +1523,11 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { func TestHandleAction_WithNonFuncAction(t *testing.T) { app := NewApp() app.Action = 42 - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + err = HandleAction(app.Action, NewContext(app, fs, nil)) if err == nil { t.Fatalf("expected to receive error from Run, got none") @@ -1547,7 +1551,11 @@ func TestHandleAction_WithNonFuncAction(t *testing.T) { func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { app := NewApp() app.Action = func() string { return "" } - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + err = HandleAction(app.Action, NewContext(app, fs, nil)) if err == nil { t.Fatalf("expected to receive error from Run, got none") @@ -1571,7 +1579,11 @@ func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { app := NewApp() app.Action = func(_ *Context) (int, error) { return 0, nil } - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + err = HandleAction(app.Action, NewContext(app, fs, nil)) if err == nil { t.Fatalf("expected to receive error from Run, got none") @@ -1602,5 +1614,9 @@ func TestHandleAction_WithUnknownPanic(t *testing.T) { fn(ctx) return nil } - HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + HandleAction(app.Action, NewContext(app, fs, nil)) } diff --git a/command.go b/command.go index 8f1a215..3cc643d 100644 --- a/command.go +++ b/command.go @@ -91,7 +91,10 @@ func (c Command) Run(ctx *Context) (err error) { c.Flags = append(c.Flags, BashCompletionFlag) } - set := flagSet(c.Name, c.Flags) + set, err := flagSet(c.Name, c.Flags) + if err != nil { + return err + } set.SetOutput(ioutil.Discard) if c.SkipFlagParsing { diff --git a/context.go b/context.go index 3893a50..19de0d8 100644 --- a/context.go +++ b/context.go @@ -147,6 +147,11 @@ func (c *Context) Parent() *Context { return c.parentContext } +// value returns the value of the flag coressponding to `name` +func (c *Context) value(name string) interface{} { + return c.flagSet.Lookup(name).Value.(flag.Getter).Get() +} + // Args contains apps console arguments type Args []string diff --git a/context_test.go b/context_test.go index b5bc993..7a6eebf 100644 --- a/context_test.go +++ b/context_test.go @@ -184,7 +184,12 @@ func TestContext_IsSet(t *testing.T) { // XXX Corresponds to hack in context.IsSet for flags with EnvVar field // Should be moved to `flag_test` in v2 func TestContext_IsSet_fromEnv(t *testing.T) { - var timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet, passwordIsSet, pIsSet bool + var ( + timeoutIsSet, tIsSet bool + noEnvVarIsSet, nIsSet bool + passwordIsSet, pIsSet bool + unparsableIsSet, uIsSet bool + ) os.Clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "15.5") @@ -192,7 +197,8 @@ func TestContext_IsSet_fromEnv(t *testing.T) { a := App{ Flags: []Flag{ Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, - Float64Flag{Name: "password, p", EnvVar: "APP_PASSWORD"}, + StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"}, + Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"}, Float64Flag{Name: "no-env-var, n"}, }, Action: func(ctx *Context) error { @@ -200,6 +206,8 @@ func TestContext_IsSet_fromEnv(t *testing.T) { tIsSet = ctx.IsSet("t") passwordIsSet = ctx.IsSet("password") pIsSet = ctx.IsSet("p") + unparsableIsSet = ctx.IsSet("unparsable") + uIsSet = ctx.IsSet("u") noEnvVarIsSet = ctx.IsSet("no-env-var") nIsSet = ctx.IsSet("n") return nil @@ -212,6 +220,11 @@ func TestContext_IsSet_fromEnv(t *testing.T) { expect(t, pIsSet, true) expect(t, noEnvVarIsSet, false) expect(t, nIsSet, false) + + os.Setenv("APP_UNPARSABLE", "foobar") + a.Run([]string{"run"}) + expect(t, unparsableIsSet, false) + expect(t, uIsSet, false) } func TestContext_GlobalIsSet(t *testing.T) { @@ -236,14 +249,22 @@ func TestContext_GlobalIsSet(t *testing.T) { // XXX Corresponds to hack in context.IsSet for flags with EnvVar field // Should be moved to `flag_test` in v2 func TestContext_GlobalIsSet_fromEnv(t *testing.T) { - var timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet bool + var ( + timeoutIsSet, tIsSet bool + noEnvVarIsSet, nIsSet bool + passwordIsSet, pIsSet bool + unparsableIsSet, uIsSet bool + ) os.Clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + os.Setenv("APP_PASSWORD", "") a := App{ Flags: []Flag{ Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"}, Float64Flag{Name: "no-env-var, n"}, + Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"}, }, Commands: []Command{ { @@ -251,6 +272,10 @@ func TestContext_GlobalIsSet_fromEnv(t *testing.T) { Action: func(ctx *Context) error { timeoutIsSet = ctx.GlobalIsSet("timeout") tIsSet = ctx.GlobalIsSet("t") + passwordIsSet = ctx.GlobalIsSet("password") + pIsSet = ctx.GlobalIsSet("p") + unparsableIsSet = ctx.GlobalIsSet("unparsable") + uIsSet = ctx.GlobalIsSet("u") noEnvVarIsSet = ctx.GlobalIsSet("no-env-var") nIsSet = ctx.GlobalIsSet("n") return nil @@ -258,11 +283,22 @@ func TestContext_GlobalIsSet_fromEnv(t *testing.T) { }, }, } - a.Run([]string{"run", "hello"}) + if err := a.Run([]string{"run", "hello"}); err != nil { + t.Logf("error running Run(): %+v", err) + } expect(t, timeoutIsSet, true) expect(t, tIsSet, true) + expect(t, passwordIsSet, true) + expect(t, pIsSet, true) expect(t, noEnvVarIsSet, false) expect(t, nIsSet, false) + + os.Setenv("APP_UNPARSABLE", "foobar") + if err := a.Run([]string{"run"}); err != nil { + t.Logf("error running Run(): %+v", err) + } + expect(t, unparsableIsSet, false) + expect(t, uIsSet, false) } func TestContext_NumFlags(t *testing.T) { diff --git a/flag.go b/flag.go index 5e565fb..3e63cf0 100644 --- a/flag.go +++ b/flag.go @@ -62,13 +62,29 @@ type Flag interface { GetName() string } -func flagSet(name string, flags []Flag) *flag.FlagSet { +// errorableFlag is an interface that allows us to return errors during apply +// it allows flags defined in this library to return errors in a fashion backwards compatible +// TODO remove in v2 and modify the existing Flag interface to return errors +type errorableFlag interface { + Flag + + applyWithError(*flag.FlagSet) error +} + +func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { set := flag.NewFlagSet(name, flag.ContinueOnError) for _, f := range flags { - f.Apply(set) + //TODO remove in v2 when errorableFlag is removed + if f, ok := f.(errorableFlag); ok { + if err := f.applyWithError(set); err != nil { + return nil, err + } + } else { + f.Apply(set) + } } - return set + return set, nil } func eachName(longName string, fn func(string)) { @@ -87,13 +103,22 @@ type Generic interface { // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag +// Ignores parsing errors func (f GenericFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError 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) applyWithError(set *flag.FlagSet) error { val := f.Value if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { - val.Set(envVal) + if err := val.Set(envVal); err != nil { + return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) + } break } } @@ -102,9 +127,11 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { eachName(f.Name, func(name string) { set.Var(f.Value, name, f.Usage) }) + + return nil } -// StringSlice is an opaque type for []string to satisfy flag.Value +// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter type StringSlice []string // Set appends the string value to the list of values @@ -123,8 +150,19 @@ func (f *StringSlice) Value() []string { return *f } +// Get returns the slice of strings set by this flag +func (f *StringSlice) Get() interface{} { + return *f +} + // Apply populates the flag given the flag set and environment +// Ignores errors func (f StringSliceFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f StringSliceFlag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -132,7 +170,9 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { newVal := &StringSlice{} for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) - newVal.Set(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) + } } f.Value = newVal break @@ -146,9 +186,11 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { } set.Var(f.Value, name, f.Usage) }) + + return nil } -// IntSlice is an opaque type for []int to satisfy flag.Value +// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter type IntSlice []int // Set parses the value into an integer and appends it to the list of values @@ -171,8 +213,19 @@ func (f *IntSlice) Value() []int { return *f } +// Get returns the slice of ints set by this flag +func (f *IntSlice) Get() interface{} { + return *f +} + // Apply populates the flag given the flag set and environment +// Ignores errors func (f IntSliceFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f IntSliceFlag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -180,9 +233,8 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { newVal := &IntSlice{} for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) - err := newVal.Set(s) - if err != nil { - fmt.Fprintf(ErrWriter, err.Error()) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) } } f.Value = newVal @@ -197,9 +249,11 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { } set.Var(f.Value, name, f.Usage) }) + + return nil } -// Int64Slice is an opaque type for []int to satisfy flag.Value +// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter type Int64Slice []int64 // Set parses the value into an integer and appends it to the list of values @@ -222,8 +276,19 @@ func (f *Int64Slice) Value() []int64 { return *f } +// Get returns the slice of ints set by this flag +func (f *Int64Slice) Get() interface{} { + return *f +} + // Apply populates the flag given the flag set and environment +// Ignores errors func (f Int64SliceFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f Int64SliceFlag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -231,9 +296,8 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) { newVal := &Int64Slice{} for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) - err := newVal.Set(s) - if err != nil { - fmt.Fprintf(ErrWriter, err.Error()) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) } } f.Value = newVal @@ -248,10 +312,17 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) { } set.Var(f.Value, name, f.Usage) }) + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f BoolFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f BoolFlag) applyWithError(set *flag.FlagSet) error { val := false if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -263,9 +334,11 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { } envValBool, err := strconv.ParseBool(envVal) - if err == nil { - val = envValBool + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) } + + val = envValBool break } } @@ -278,10 +351,18 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { } set.Bool(name, val, f.Usage) }) + + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f BoolTFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f BoolTFlag) applyWithError(set *flag.FlagSet) error { val := true if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -293,10 +374,12 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { } envValBool, err := strconv.ParseBool(envVal) - if err == nil { - val = envValBool - break + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) } + + val = envValBool + break } } } @@ -308,10 +391,18 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { } set.Bool(name, val, f.Usage) }) + + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f StringFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f StringFlag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -329,19 +420,28 @@ func (f StringFlag) Apply(set *flag.FlagSet) { } set.String(name, f.Value, f.Usage) }) + + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f IntFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f IntFlag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err == nil { - f.Value = int(envValInt) - break + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) } + f.Value = int(envValInt) + break } } } @@ -353,19 +453,29 @@ func (f IntFlag) Apply(set *flag.FlagSet) { } set.Int(name, f.Value, f.Usage) }) + + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f Int64Flag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f Int64Flag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err == nil { - f.Value = envValInt - break + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) } + + f.Value = envValInt + break } } } @@ -377,19 +487,29 @@ func (f Int64Flag) Apply(set *flag.FlagSet) { } set.Int64(name, f.Value, f.Usage) }) + + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f UintFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f UintFlag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err == nil { - f.Value = uint(envValInt) - break + if err != nil { + return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) } + + f.Value = uint(envValInt) + break } } } @@ -401,19 +521,29 @@ func (f UintFlag) Apply(set *flag.FlagSet) { } set.Uint(name, f.Value, f.Usage) }) + + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f Uint64Flag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f Uint64Flag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err == nil { - f.Value = uint64(envValInt) - break + if err != nil { + return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) } + + f.Value = uint64(envValInt) + break } } } @@ -425,19 +555,29 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) { } set.Uint64(name, f.Value, f.Usage) }) + + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f DurationFlag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f DurationFlag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { envValDuration, err := time.ParseDuration(envVal) - if err == nil { - f.Value = envValDuration - break + if err != nil { + return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) } + + f.Value = envValDuration + break } } } @@ -449,18 +589,29 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { } set.Duration(name, f.Value, f.Usage) }) + + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f Float64Flag) Apply(set *flag.FlagSet) { + f.applyWithError(set) +} + +// applyWithError populates the flag given the flag set and environment +func (f Float64Flag) applyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { envValFloat, err := strconv.ParseFloat(envVal, 10) - if err == nil { - f.Value = float64(envValFloat) + if err != nil { + return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) } + + f.Value = float64(envValFloat) + break } } } @@ -472,6 +623,8 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { } set.Float64(name, f.Value, f.Usage) }) + + return nil } func visibleFlags(fl []Flag) []Flag { diff --git a/flag_test.go b/flag_test.go index 58ada36..0dd8654 100644 --- a/flag_test.go +++ b/flag_test.go @@ -29,35 +29,78 @@ func TestBoolFlagHelpOutput(t *testing.T) { } } -func TestParseBoolFromEnv(t *testing.T) { - var boolFlagTests = []struct { +func TestFlagsFromEnv(t *testing.T) { + var flagTests = []struct { input string - output bool + output interface{} + flag Flag + err error }{ - {"", false}, - {"1", true}, - {"false", false}, - {"true", true}, + {"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, + {"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, + {"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, + {"foobar", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Errorf(`could not parse foobar as bool value for flag debug: strconv.ParseBool: parsing "foobar": invalid syntax`)}, + + {"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, + {"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, + {"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, + {"foobar", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Errorf(`could not parse foobar as bool value for flag debug: strconv.ParseBool: parsing "foobar": invalid syntax`)}, + + {"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, nil}, + {"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Errorf(`could not parse foobar as duration for flag time: time: invalid duration foobar`)}, + + {"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, + {"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, + {"foobar", 0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as float64 value for flag seconds: strconv.ParseFloat: parsing "foobar": invalid syntax`)}, + + {"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, + {"1.2", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as int value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, + {"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + + {"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, + {"1.2", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as int value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, + {"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + + {"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, + {"1.2,2", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2,2 as int slice value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, + {"foobar", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + + {"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, + {"1.2,2", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2,2 as int64 slice value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, + {"foobar", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int64 slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + + {"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, nil}, + + {"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, nil}, + + {"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, + {"1.2", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as uint value for flag seconds: strconv.ParseUint: parsing "1.2": invalid syntax`)}, + {"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)}, + + {"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, + {"1.2", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as uint64 value for flag seconds: strconv.ParseUint: parsing "1.2": invalid syntax`)}, + {"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint64 value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)}, + + {"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, nil}, } - for _, test := range boolFlagTests { + for _, test := range flagTests { os.Clearenv() - os.Setenv("DEBUG", test.input) + os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input) a := App{ - Flags: []Flag{ - BoolFlag{Name: "debug, d", EnvVar: "DEBUG"}, - }, + Flags: []Flag{test.flag}, Action: func(ctx *Context) error { - if ctx.Bool("debug") != test.output { - t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("debug")) - } - if ctx.Bool("d") != test.output { - t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("d")) + if !reflect.DeepEqual(ctx.value(test.flag.GetName()), test.output) { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.value(test.flag.GetName())) } return nil }, } - a.Run([]string{"run"}) + + err := a.Run([]string{"run"}) + if !reflect.DeepEqual(test.err, err) { + t.Errorf("expected error %s, got error %s", test.err, err) + } } } @@ -1100,6 +1143,10 @@ func (p *Parser) String() string { return fmt.Sprintf("%s,%s", p[0], p[1]) } +func (p *Parser) Get() interface{} { + return p +} + func TestParseGeneric(t *testing.T) { a := App{ Flags: []Flag{ From b4a64dc08db6f198cd94c009885a6a83c0ad14c5 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 12 Nov 2016 20:01:44 -0800 Subject: [PATCH 291/381] Add windows implementation of Clearenv for tests Apparently `Clearenv` in Windows just sets the variables to "" --- context_test.go | 4 ++-- helpers_unix_test.go | 9 +++++++++ helpers_windows_test.go | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 helpers_unix_test.go create mode 100644 helpers_windows_test.go diff --git a/context_test.go b/context_test.go index 7a6eebf..a1ab05b 100644 --- a/context_test.go +++ b/context_test.go @@ -191,7 +191,7 @@ func TestContext_IsSet_fromEnv(t *testing.T) { unparsableIsSet, uIsSet bool ) - os.Clearenv() + clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "15.5") os.Setenv("APP_PASSWORD", "") a := App{ @@ -256,7 +256,7 @@ func TestContext_GlobalIsSet_fromEnv(t *testing.T) { unparsableIsSet, uIsSet bool ) - os.Clearenv() + clearenv() os.Setenv("APP_TIMEOUT_SECONDS", "15.5") os.Setenv("APP_PASSWORD", "") a := App{ diff --git a/helpers_unix_test.go b/helpers_unix_test.go new file mode 100644 index 0000000..ae27fc5 --- /dev/null +++ b/helpers_unix_test.go @@ -0,0 +1,9 @@ +// +build darwin dragonfly freebsd linux netbsd openbsd solaris + +package cli + +import "os" + +func clearenv() { + os.Clearenv() +} diff --git a/helpers_windows_test.go b/helpers_windows_test.go new file mode 100644 index 0000000..4eb84f9 --- /dev/null +++ b/helpers_windows_test.go @@ -0,0 +1,20 @@ +package cli + +import ( + "os" + "syscall" +) + +// os.Clearenv() doesn't actually unset variables on Windows +// See: https://github.com/golang/go/issues/17902 +func clearenv() { + for _, s := range os.Environ() { + for j := 1; j < len(s); j++ { + if s[j] == '=' { + keyp, _ := syscall.UTF16PtrFromString(s[0:j]) + syscall.SetEnvironmentVariable(keyp, nil) + break + } + } + } +} From de551c44504d08ca37fb9b1fcfeebcd9799f1656 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 28 Aug 2016 17:43:50 -0700 Subject: [PATCH 292/381] Backport removal of deprecation warnings #508 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b0d0ee..5cbbe5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ ### Changed - Raise minimum tested/supported Go version to 1.2+ +## [1.18.1] - 2016-08-28 +### Fixed +- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported) + ## [1.18.0] - 2016-06-27 ### Added - `./runtests` test runner with coverage tracking by default @@ -29,6 +33,10 @@ - No longer swallows `panic`s that occur within the `Action`s themselves when detecting the signature of the `Action` field +## [1.17.1] - 2016-08-28 +### Fixed +- Removed deprecation warnings to STDERR to avoid them leaking to the end-user + ## [1.17.0] - 2016-05-09 ### Added - Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` @@ -50,6 +58,10 @@ - cleanups based on [Go Report Card feedback](https://goreportcard.com/report/github.com/urfave/cli) +## [1.16.1] - 2016-08-28 +### Fixed +- Removed deprecation warnings to STDERR to avoid them leaking to the end-user + ## [1.16.0] - 2016-05-02 ### Added - `Hidden` field on all flag struct types to omit from generated help text From 4c2360f9755dd0d69902a9f7bdc64aef4dd30d24 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 11 Sep 2016 20:17:58 -0700 Subject: [PATCH 293/381] Prepare CHANGELOG for v1.19.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cbbe5b..c5661f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,34 @@ **ATTN**: This project uses [semantic versioning](http://semver.org/). ## [Unreleased] + + +## [1.19.0] - 2016-09-11 ### Added - Flag type code generation via `go generate` - Write to stderr and exit 1 if action returns non-nil error - Added support for TOML to the `altsrc` loader +- `SkipArgReorder` was added to allow users to skip the argument reordering. + This is useful if you want to consider all "flags" after an argument as + arguments rather than flags (the default behavior of the stdlib `flag` + library). This is backported functionality from the [removal of the flag + reordering](https://github.com/urfave/cli/pull/398) in the unreleased version + 2 ### Changed - Raise minimum tested/supported Go version to 1.2+ +### Fixed +- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized) +- Correctly show help message if `-h` is provided to a subcommand +- `context.(Global)IsSet` now respects environment variables. Previously it + would return `false` if a flag was specified in the environment rather than + as an argument +- Removed deprecation warnings to STDERR to avoid them leaking to the end-user +- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This + fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well + as `altsrc` where Go would complain that the types didn't match + ## [1.18.1] - 2016-08-28 ### Fixed - Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported) From fa7ccb1447a78d77447007b830b81cbb0cec7c89 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 12 Nov 2016 13:53:26 -0800 Subject: [PATCH 294/381] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5661f7..b030967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Raise minimum tested/supported Go version to 1.2+ ### Fixed +- If no action is specified on a command or app, the help is now printed instead of `panic`ing - `App.Metadata` is initialized automatically now (previously was `nil` unless initialized) - Correctly show help message if `-h` is provided to a subcommand - `context.(Global)IsSet` now respects environment variables. Previously it From dcdea39be2d678ff387fa40a6cf63f430dc3d805 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 12 Nov 2016 14:01:13 -0800 Subject: [PATCH 295/381] Add additional entries to changelog for v.19.0 release --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b030967..f672d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,11 @@ ## [Unreleased] -## [1.19.0] - 2016-09-11 +## [1.19.0] - 2016-11-12 ### Added +- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`) +- A `Description` field was added to `App` for a more detailed description of + the application (similar to the existing `Description` field on `Command`) - Flag type code generation via `go generate` - Write to stderr and exit 1 if action returns non-nil error - Added support for TOML to the `altsrc` loader @@ -21,6 +24,12 @@ - Raise minimum tested/supported Go version to 1.2+ ### Fixed +- Consider empty environment variables as set (previously environment variables + with the equivalent of `""` would be skipped rather than their value used). +- Return an error if the value in a given environment variable cannot be parsed + as the flag type. Previously these errors were silently swallowed. +- Print full error when an invalid flag is specified (which includes the invalid flag) +- `App.Writer` defaults to `stdout` when `nil` - If no action is specified on a command or app, the help is now printed instead of `panic`ing - `App.Metadata` is initialized automatically now (previously was `nil` unless initialized) - Correctly show help message if `-h` is provided to a subcommand From 8dd1962f7b0654225424d5d1bc0189b0113fcafc Mon Sep 17 00:00:00 2001 From: Joshua Rubin Date: Mon, 14 Nov 2016 09:35:22 -0700 Subject: [PATCH 296/381] change "complete" to "shellComplete" --- app.go | 4 ++-- context.go | 4 ++-- help.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app.go b/app.go index 50edf9a..9836d31 100644 --- a/app.go +++ b/app.go @@ -175,7 +175,7 @@ func (a *App) Run(arguments []string) (err error) { // flag name as the value of the flag before it which is undesirable // note that we can only do this because the shell autocomplete function // always appends the completion flag at the end of the command - complete, arguments := checkCompleteFlag(a, arguments) + shellComplete, arguments := checkShellCompleteFlag(a, arguments) // parse flags set := flagSet(a.Name, a.Flags) @@ -188,7 +188,7 @@ func (a *App) Run(arguments []string) (err error) { ShowAppHelp(context) return nerr } - context.complete = complete + context.shellComplete = shellComplete if checkCompletions(context) { return nil diff --git a/context.go b/context.go index 81404c4..4f440ec 100644 --- a/context.go +++ b/context.go @@ -15,7 +15,7 @@ import ( type Context struct { App *App Command Command - complete bool + shellComplete bool flagSet *flag.FlagSet setFlags map[string]bool parentContext *Context @@ -26,7 +26,7 @@ func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { c := &Context{App: app, flagSet: set, parentContext: parentCtx} if parentCtx != nil { - c.complete = parentCtx.complete + c.shellComplete = parentCtx.shellComplete } return c diff --git a/help.go b/help.go index e453e72..e261bb6 100644 --- a/help.go +++ b/help.go @@ -252,7 +252,7 @@ func checkSubcommandHelp(c *Context) bool { return false } -func checkCompleteFlag(a *App, arguments []string) (bool, []string) { +func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { if !a.EnableBashCompletion { return false, arguments } @@ -268,7 +268,7 @@ func checkCompleteFlag(a *App, arguments []string) (bool, []string) { } func checkCompletions(c *Context) bool { - if !c.complete { + if !c.shellComplete { return false } @@ -285,7 +285,7 @@ func checkCompletions(c *Context) bool { } func checkCommandCompletions(c *Context, name string) bool { - if !c.complete { + if !c.shellComplete { return false } From 3272baf434b471d349ddd0f51d07ab11a8cb9228 Mon Sep 17 00:00:00 2001 From: Joshua Rubin Date: Mon, 14 Nov 2016 10:10:51 -0700 Subject: [PATCH 297/381] add a test for shell completion using incomplete flags --- app_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/app_test.go b/app_test.go index 40a598d..ae87237 100644 --- a/app_test.go +++ b/app_test.go @@ -1561,3 +1561,47 @@ func TestHandleAction_WithUnknownPanic(t *testing.T) { } HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) } + +func TestShellCompletionForIncompleteFlags(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{ + Name: "test-completion", + }, + } + app.EnableBashCompletion = true + app.BashComplete = func(ctx *Context) { + for _, command := range ctx.App.Commands { + if command.Hidden { + continue + } + + for _, name := range command.Names() { + fmt.Fprintln(ctx.App.Writer, name) + } + } + + for _, flag := range ctx.App.Flags { + for _, name := range strings.Split(flag.GetName(), ",") { + if name == BashCompletionFlag.Name { + continue + } + + switch name = strings.TrimSpace(name); len(name) { + case 0: + case 1: + fmt.Fprintln(ctx.App.Writer, "-"+name) + default: + fmt.Fprintln(ctx.App.Writer, "--"+name) + } + } + } + } + app.Action = func(ctx *Context) error { + return fmt.Errorf("should not get here") + } + err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.Name}) + if err != nil { + t.Errorf("app should not return an error: %s", err) + } +} From b5d06bd2a9cddb942cf282cad11c97c251b6f6c7 Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Thu, 17 Nov 2016 16:58:46 +0100 Subject: [PATCH 298/381] allow to load YAML configuration files on Windows The funtion `loadDataFrom` does not take care of Windows users since any of the conditions met and it returns an error. The change looks for the runtime where it's running and then if the filePath contains a `\` --- altsrc/yaml_file_loader.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 335356f..433023d 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -78,6 +78,11 @@ func loadDataFrom(filePath string) ([]byte, error) { return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) } return ioutil.ReadFile(filePath) + } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") { + if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { + return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) + } + return ioutil.ReadFile(filePath) } else { return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) } From 9f357f76252fcd4446dbdd981dabd52b40c87481 Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Thu, 17 Nov 2016 17:08:01 +0100 Subject: [PATCH 299/381] fix imports Sorry, forgot to add imports correctly, needed to edit the file and make the commit using the github online editor, since I can't access from my current location from git. --- altsrc/yaml_file_loader.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 433023d..3965fe4 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -11,6 +11,8 @@ import ( "net/http" "net/url" "os" + "runtime" + "strings" "gopkg.in/urfave/cli.v1" From 4661a59b20d724d0476a479a1b2041bf1eccd82b Mon Sep 17 00:00:00 2001 From: drekar Date: Thu, 17 Nov 2016 09:48:03 -1000 Subject: [PATCH 300/381] errorableFlag: scope result of type assertion. --- flag.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flag.go b/flag.go index 3e63cf0..799c6f7 100644 --- a/flag.go +++ b/flag.go @@ -76,8 +76,8 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { for _, f := range flags { //TODO remove in v2 when errorableFlag is removed - if f, ok := f.(errorableFlag); ok { - if err := f.applyWithError(set); err != nil { + if ef, ok := f.(errorableFlag); ok { + if err := ef.applyWithError(set); err != nil { return nil, err } } else { From d71794de198717467a8f053036b5620ccb0d613c Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 13 Nov 2016 16:15:05 -0800 Subject: [PATCH 301/381] Make ApplyWithError a public method on errorableFlag Add to altsrc flags. Otherwise, flagSet() was bypassing altsrc's attempt at shadowing. --- altsrc/flag_generated.go | 91 ++++++++++++++++++++++++++++++++++++++++ flag.go | 82 ++++++++++++++++++------------------ generate-flag-types | 7 ++++ 3 files changed, 139 insertions(+), 41 deletions(-) diff --git a/altsrc/flag_generated.go b/altsrc/flag_generated.go index b6b96a1..0aeb0b0 100644 --- a/altsrc/flag_generated.go +++ b/altsrc/flag_generated.go @@ -27,6 +27,13 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) { f.BoolFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped BoolFlag.ApplyWithError +func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.BoolFlag.ApplyWithError(set) +} + // BoolTFlag is the flag type that wraps cli.BoolTFlag to allow // for other values to be specified type BoolTFlag struct { @@ -46,6 +53,13 @@ func (f *BoolTFlag) Apply(set *flag.FlagSet) { f.BoolTFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped BoolTFlag.ApplyWithError +func (f *BoolTFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.BoolTFlag.ApplyWithError(set) +} + // DurationFlag is the flag type that wraps cli.DurationFlag to allow // for other values to be specified type DurationFlag struct { @@ -65,6 +79,13 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) { f.DurationFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped DurationFlag.ApplyWithError +func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.DurationFlag.ApplyWithError(set) +} + // Float64Flag is the flag type that wraps cli.Float64Flag to allow // for other values to be specified type Float64Flag struct { @@ -84,6 +105,13 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) { f.Float64Flag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped Float64Flag.ApplyWithError +func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.Float64Flag.ApplyWithError(set) +} + // GenericFlag is the flag type that wraps cli.GenericFlag to allow // for other values to be specified type GenericFlag struct { @@ -103,6 +131,13 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) { f.GenericFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped GenericFlag.ApplyWithError +func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.GenericFlag.ApplyWithError(set) +} + // Int64Flag is the flag type that wraps cli.Int64Flag to allow // for other values to be specified type Int64Flag struct { @@ -122,6 +157,13 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) { f.Int64Flag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped Int64Flag.ApplyWithError +func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.Int64Flag.ApplyWithError(set) +} + // IntFlag is the flag type that wraps cli.IntFlag to allow // for other values to be specified type IntFlag struct { @@ -141,6 +183,13 @@ func (f *IntFlag) Apply(set *flag.FlagSet) { f.IntFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped IntFlag.ApplyWithError +func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.IntFlag.ApplyWithError(set) +} + // IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow // for other values to be specified type IntSliceFlag struct { @@ -160,6 +209,13 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) { f.IntSliceFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped IntSliceFlag.ApplyWithError +func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.IntSliceFlag.ApplyWithError(set) +} + // Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow // for other values to be specified type Int64SliceFlag struct { @@ -179,6 +235,13 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { f.Int64SliceFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped Int64SliceFlag.ApplyWithError +func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.Int64SliceFlag.ApplyWithError(set) +} + // StringFlag is the flag type that wraps cli.StringFlag to allow // for other values to be specified type StringFlag struct { @@ -198,6 +261,13 @@ func (f *StringFlag) Apply(set *flag.FlagSet) { f.StringFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped StringFlag.ApplyWithError +func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.StringFlag.ApplyWithError(set) +} + // StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow // for other values to be specified type StringSliceFlag struct { @@ -217,6 +287,13 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) { f.StringSliceFlag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped StringSliceFlag.ApplyWithError +func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.StringSliceFlag.ApplyWithError(set) +} + // Uint64Flag is the flag type that wraps cli.Uint64Flag to allow // for other values to be specified type Uint64Flag struct { @@ -236,6 +313,13 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) { f.Uint64Flag.Apply(set) } +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped Uint64Flag.ApplyWithError +func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.Uint64Flag.ApplyWithError(set) +} + // UintFlag is the flag type that wraps cli.UintFlag to allow // for other values to be specified type UintFlag struct { @@ -254,3 +338,10 @@ func (f *UintFlag) Apply(set *flag.FlagSet) { f.set = set f.UintFlag.Apply(set) } + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped UintFlag.ApplyWithError +func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.UintFlag.ApplyWithError(set) +} diff --git a/flag.go b/flag.go index 799c6f7..7dd8a2c 100644 --- a/flag.go +++ b/flag.go @@ -68,7 +68,7 @@ type Flag interface { type errorableFlag interface { Flag - applyWithError(*flag.FlagSet) error + ApplyWithError(*flag.FlagSet) error } func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { @@ -77,7 +77,7 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { for _, f := range flags { //TODO remove in v2 when errorableFlag is removed if ef, ok := f.(errorableFlag); ok { - if err := ef.applyWithError(set); err != nil { + if err := ef.ApplyWithError(set); err != nil { return nil, err } } else { @@ -105,12 +105,12 @@ type Generic interface { // provided by the user for parsing by the flag // Ignores parsing errors func (f GenericFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError takes the flagset and calls Set on the generic flag with the value +// ApplyWithError 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) applyWithError(set *flag.FlagSet) error { +func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { val := f.Value if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -158,11 +158,11 @@ func (f *StringSlice) Get() interface{} { // Apply populates the flag given the flag set and environment // Ignores errors func (f StringSliceFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f StringSliceFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -221,11 +221,11 @@ func (f *IntSlice) Get() interface{} { // Apply populates the flag given the flag set and environment // Ignores errors func (f IntSliceFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f IntSliceFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -284,11 +284,11 @@ func (f *Int64Slice) Get() interface{} { // Apply populates the flag given the flag set and environment // Ignores errors func (f Int64SliceFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f Int64SliceFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -318,11 +318,11 @@ func (f Int64SliceFlag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f BoolFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f BoolFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { val := false if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -358,11 +358,11 @@ func (f BoolFlag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f BoolTFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f BoolTFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { val := true if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { @@ -398,11 +398,11 @@ func (f BoolTFlag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f StringFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f StringFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -427,11 +427,11 @@ func (f StringFlag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f IntFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f IntFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -460,11 +460,11 @@ func (f IntFlag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f Int64Flag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f Int64Flag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -494,11 +494,11 @@ func (f Int64Flag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f UintFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f UintFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -528,11 +528,11 @@ func (f UintFlag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f Uint64Flag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f Uint64Flag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -562,11 +562,11 @@ func (f Uint64Flag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f DurationFlag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f DurationFlag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) @@ -596,11 +596,11 @@ func (f DurationFlag) applyWithError(set *flag.FlagSet) error { // Apply populates the flag given the flag set and environment // Ignores errors func (f Float64Flag) Apply(set *flag.FlagSet) { - f.applyWithError(set) + f.ApplyWithError(set) } -// applyWithError populates the flag given the flag set and environment -func (f Float64Flag) applyWithError(set *flag.FlagSet) error { +// ApplyWithError populates the flag given the flag set and environment +func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) diff --git a/generate-flag-types b/generate-flag-types index 47a168b..7147381 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -232,6 +232,13 @@ def _write_altsrc_flag_types(outfile, types): f.set = set f.{name}Flag.Apply(set) }} + + // ApplyWithError saves the flagSet for later usage calls, then calls the + // wrapped {name}Flag.ApplyWithError + func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{ + f.set = set + return f.{name}Flag.ApplyWithError(set) + }} """.format(**typedef)) From a8fc36b690712e61212d8cb9450eabf2be359fca Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 19 Nov 2016 10:54:17 -0800 Subject: [PATCH 302/381] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f672d59..9a6b36e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ library). This is backported functionality from the [removal of the flag reordering](https://github.com/urfave/cli/pull/398) in the unreleased version 2 +- For formatted errors (those implementing `ErrorFormatter`), the errors will + be formatted during output. Compatible with `pkg/errors`. ### Changed - Raise minimum tested/supported Go version to 1.2+ From 7fbcf2396a29f95b33946a716a2c4e959c92e154 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 19 Nov 2016 11:04:57 -0800 Subject: [PATCH 303/381] Update date of 1.19.0 release in CHANGELOG [ci skip] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a6b36e..edd24a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ## [Unreleased] -## [1.19.0] - 2016-11-12 +## [1.19.0] - 2016-11-19 ### Added - `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`) - A `Description` field was added to `App` for a more detailed description of From 6503447ae720ddabdb8902f7aebef525191da02f Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Sat, 19 Nov 2016 22:37:11 +0100 Subject: [PATCH 304/381] added comment to windows filePath check --- altsrc/yaml_file_loader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 3965fe4..dd808d5 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -81,6 +81,7 @@ func loadDataFrom(filePath string) ([]byte, error) { } return ioutil.ReadFile(filePath) } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") { + // on Windows systems u.Path is always empty, so we need to check the string directly. if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) } From 8fa549846ee27c7f7542c70de604c6f8807fdf07 Mon Sep 17 00:00:00 2001 From: Kasey Klipsch Date: Mon, 21 Nov 2016 09:47:23 -0600 Subject: [PATCH 305/381] #556 broke the api for users who were using the ActionFunc --- app.go | 4 +++- app_test.go | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 38d89ae..95ffc0b 100644 --- a/app.go +++ b/app.go @@ -479,7 +479,9 @@ func (a Author) String() string { // it's an ActionFunc or a func with the legacy signature for Action, the func // is run! func HandleAction(action interface{}, context *Context) (err error) { - if a, ok := action.(func(*Context) error); ok { + if a, ok := action.(ActionFunc); ok { + return a(context) + } else if a, ok := action.(func(*Context) error); ok { return a(context) } else if a, ok := action.(func(*Context)); ok { // deprecated function signature a(context) diff --git a/app_test.go b/app_test.go index fadeb20..10f1562 100644 --- a/app_test.go +++ b/app_test.go @@ -1664,3 +1664,22 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) { t.Errorf("app should not return an error: %s", err) } } + +func TestHandleActionActuallyWorksWithActions(t *testing.T) { + var f ActionFunc + called := false + f = func(c *Context) error { + called = true + return nil + } + + err := HandleAction(f, nil) + + if err != nil { + t.Errorf("Should not have errored: %v", err) + } + + if !called { + t.Errorf("Function was not called") + } +} From 0ef658215409bbac685b2219743292620e5687fa Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 21 Nov 2016 20:26:35 -0800 Subject: [PATCH 306/381] Prep 1.19.1 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index edd24a0..07f7546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ ## [Unreleased] +## [1.19.1] - 2016-11-21 + +### Fixed + +- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as + the `Action` for a command would cause it to error rather than calling the + function. Should not have a affected declarative cases using `func(c + *cli.Context) err)`. +- Shell completion now handles the case where the user specifies + `--generate-bash-completion` immediately after a flag that takes an argument. + Previously it call the application with `--generate-bash-completion` as the + flag value. ## [1.19.0] - 2016-11-19 ### Added From 4267cd827cbfb066cf031bcb01ddbecdf7d0c49e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 4 Jan 2017 10:28:58 +0800 Subject: [PATCH 307/381] [ci skip] Fix template syntax error --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bb5f61e..6ba60c4 100644 --- a/README.md +++ b/README.md @@ -940,16 +940,13 @@ SUPPORT: support@awesometown.example.com cli.AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command -[command options]{{end}} {{if -.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{if len .Authors}} -AUTHOR(S): +AUTHOR: {{range .Authors}}{{ . }}{{end}} {{end}}{{if .Commands}} COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t" -}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range .VisibleFlags}}{{.}} {{end}}{{end}}{{if .Copyright }} From ac772237b96509d5150567da31ba6c6c3897d452 Mon Sep 17 00:00:00 2001 From: Maximilian Meister Date: Sun, 18 Dec 2016 17:43:48 +0100 Subject: [PATCH 308/381] command: enable ordering commands by name --- README.md | 24 ++++++++++++++++++++++-- command.go | 14 ++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ba60c4..2bbbd8e 100644 --- a/README.md +++ b/README.md @@ -455,13 +455,13 @@ error. Flags for the application and commands are shown in the order they are defined. However, it's possible to sort them from outside this library by using `FlagsByName` -with `sort`. +or `CommandsByName` with `sort`. For example this: ``` go package main @@ -488,7 +488,27 @@ func main() { }, } + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + } + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) app.Run(os.Args) } diff --git a/command.go b/command.go index 2628fbf..d297eb9 100644 --- a/command.go +++ b/command.go @@ -61,6 +61,20 @@ type Command struct { commandNamePath []string } +type CommandsByName []Command + +func (c CommandsByName) Len() int { + return len(c) +} + +func (c CommandsByName) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c CommandsByName) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + // FullName returns the full name of the command. // For subcommands this ensures that parent commands are part of the command path func (c Command) FullName() string { From 0083ae8732e4b5d6911729b5e60c99a19d6179ac Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 9 Jan 2017 15:57:49 -0800 Subject: [PATCH 309/381] Usage/Description/ArgsUsage correctly copied when using subcommand --- command.go | 8 +++----- help.go | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/command.go b/command.go index 2628fbf..68c760e 100644 --- a/command.go +++ b/command.go @@ -230,11 +230,9 @@ func (c Command) startApp(ctx *Context) error { app.HelpName = app.Name } - if c.Description != "" { - app.Usage = c.Description - } else { - app.Usage = c.Usage - } + app.Usage = c.Usage + app.Description = c.Description + app.ArgsUsage = c.ArgsUsage // set CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound diff --git a/help.go b/help.go index c8c1aee..d00e4da 100644 --- a/help.go +++ b/help.go @@ -64,7 +64,7 @@ OPTIONS: // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} USAGE: {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} From acc622e5fb6f273c26946ded483aa813bebdcee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 11 Jan 2017 14:38:54 +0200 Subject: [PATCH 310/381] Invalidate context.setFlags cache on modification. --- context.go | 2 ++ context_test.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/context.go b/context.go index cb89e92..db94191 100644 --- a/context.go +++ b/context.go @@ -39,11 +39,13 @@ func (c *Context) NumFlags() int { // Set sets a context flag to a value. func (c *Context) Set(name, value string) error { + c.setFlags = nil return c.flagSet.Set(name, value) } // GlobalSet sets a context flag to a value on the global flagset func (c *Context) GlobalSet(name, value string) error { + globalContext(c).setFlags = nil return globalContext(c).flagSet.Set(name, value) } diff --git a/context_test.go b/context_test.go index a1ab05b..7acca10 100644 --- a/context_test.go +++ b/context_test.go @@ -375,8 +375,10 @@ func TestContext_Set(t *testing.T) { set.Int("int", 5, "an int") c := NewContext(nil, set, nil) + expect(t, c.IsSet("int"), false) c.Set("int", "1") expect(t, c.Int("int"), 1) + expect(t, c.IsSet("int"), true) } func TestContext_GlobalSet(t *testing.T) { @@ -393,7 +395,9 @@ func TestContext_GlobalSet(t *testing.T) { expect(t, c.Int("int"), 1) expect(t, c.GlobalInt("int"), 5) + expect(t, c.GlobalIsSet("int"), false) c.GlobalSet("int", "1") expect(t, c.Int("int"), 1) expect(t, c.GlobalInt("int"), 1) + expect(t, c.GlobalIsSet("int"), true) } From 71ced406af64ee9961533d518dab28d455a66666 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Thu, 12 Jan 2017 18:59:38 +0900 Subject: [PATCH 311/381] Treat `rc` first called as exit code Because default OsExiter is os.Exit. --- errors_test.go | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/errors_test.go b/errors_test.go index 131bd38..d3764b7 100644 --- a/errors_test.go +++ b/errors_test.go @@ -12,8 +12,10 @@ func TestHandleExitCoder_nil(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() @@ -29,8 +31,10 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() @@ -46,8 +50,10 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } defer func() { OsExiter = fakeOsExiter }() @@ -65,8 +71,10 @@ func TestHandleExitCoder_ErrorWithMessage(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } ErrWriter = &bytes.Buffer{} @@ -88,8 +96,10 @@ func TestHandleExitCoder_ErrorWithoutMessage(t *testing.T) { called := false OsExiter = func(rc int) { - exitCode = rc - called = true + if !called { + exitCode = rc + called = true + } } ErrWriter = &bytes.Buffer{} @@ -123,7 +133,9 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { called := false OsExiter = func(rc int) { - called = true + if !called { + called = true + } } ErrWriter = &bytes.Buffer{} @@ -143,7 +155,9 @@ func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { called := false OsExiter = func(rc int) { - called = true + if !called { + called = true + } } ErrWriter = &bytes.Buffer{} From 286b4b56d988e0ac6da075615f1da496db94da87 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Thu, 12 Jan 2017 19:12:17 +0900 Subject: [PATCH 312/381] Document on exit code in case of MultiError is given --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 8aa8d9e..f9d648e 100644 --- a/errors.go +++ b/errors.go @@ -74,7 +74,7 @@ func (ee *ExitError) ExitCode() int { // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if // so prints the error to stderr (if it is non-empty) and calls OsExiter with the // given exit code. If the given error is a MultiError, then this func is -// called on all members of the Errors slice. +// called on all members of the Errors slice and calls OsExiter with the last exit code. func HandleExitCoder(err error) { if err == nil { return From adec15acf537c7b696c299223a16c60cb3eeeed8 Mon Sep 17 00:00:00 2001 From: HIROSE Masaaki Date: Fri, 13 Jan 2017 15:37:09 +0900 Subject: [PATCH 313/381] Add another ExitCoder to assert that it uses last one --- errors_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/errors_test.go b/errors_test.go index d3764b7..d9e0da6 100644 --- a/errors_test.go +++ b/errors_test.go @@ -59,10 +59,11 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { defer func() { OsExiter = fakeOsExiter }() exitErr := NewExitError("galactic perimeter breach", 9) - err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) + exitErr2 := NewExitError("last ExitCoder", 11) + err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2) HandleExitCoder(err) - expect(t, exitCode, 9) + expect(t, exitCode, 11) expect(t, called, true) } From 7b912d9f8f8a78f192061ed5b0f40918dfda5a07 Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Thu, 17 Nov 2016 16:58:46 +0100 Subject: [PATCH 314/381] allow to load YAML configuration files on Windows The funtion `loadDataFrom` does not take care of Windows users since any of the conditions met and it returns an error. The change looks for the runtime where it's running and then if the filePath contains a `\` --- altsrc/yaml_file_loader.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 335356f..433023d 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -78,6 +78,11 @@ func loadDataFrom(filePath string) ([]byte, error) { return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) } return ioutil.ReadFile(filePath) + } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") { + if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { + return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) + } + return ioutil.ReadFile(filePath) } else { return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) } From 341ca93b01a20fe6fc361e005b45d968fd559aa5 Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Thu, 17 Nov 2016 17:08:01 +0100 Subject: [PATCH 315/381] fix imports Sorry, forgot to add imports correctly, needed to edit the file and make the commit using the github online editor, since I can't access from my current location from git. --- altsrc/yaml_file_loader.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 433023d..3965fe4 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -11,6 +11,8 @@ import ( "net/http" "net/url" "os" + "runtime" + "strings" "gopkg.in/urfave/cli.v1" From f1be59ff3d239f0942b201619030d302bce912cc Mon Sep 17 00:00:00 2001 From: Antonio Fdez Date: Sat, 19 Nov 2016 22:37:11 +0100 Subject: [PATCH 316/381] added comment to windows filePath check --- altsrc/yaml_file_loader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 3965fe4..dd808d5 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -81,6 +81,7 @@ func loadDataFrom(filePath string) ([]byte, error) { } return ioutil.ReadFile(filePath) } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") { + // on Windows systems u.Path is always empty, so we need to check the string directly. if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) } From f8347a97c68c1b2a11f418da0225953c9b007708 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 8 Jan 2017 18:11:26 -0500 Subject: [PATCH 317/381] Fix altsrc slice inputs so that they correctly parse Was previously attempting to cast directly from []interface{} to []string or []int which Go doesn't support. Instead, we iterate over and cast each value (error'ing if the value is not the correct format). Fixes #580 --- altsrc/flag_test.go | 12 ++++---- altsrc/map_input_source.go | 58 +++++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 9e9c96d..cd18294 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -59,7 +59,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), FlagName: "test", - MapValue: []string{"hello", "world"}, + MapValue: []interface{}{"hello", "world"}, }) expect(t, c.StringSlice("test"), []string{"hello", "world"}) } @@ -68,7 +68,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), FlagName: "test", - MapValue: []string{"hello", "world"}, + MapValue: []interface{}{"hello", "world"}, ContextValueString: "ohno", }) expect(t, c.StringSlice("test"), []string{"ohno"}) @@ -78,7 +78,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), FlagName: "test", - MapValue: []string{"hello", "world"}, + MapValue: []interface{}{"hello", "world"}, EnvVarName: "TEST", EnvVarValue: "oh,no", }) @@ -89,7 +89,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), FlagName: "test", - MapValue: []int{1, 2}, + MapValue: []interface{}{1, 2}, }) expect(t, c.IntSlice("test"), []int{1, 2}) } @@ -98,7 +98,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), FlagName: "test", - MapValue: []int{1, 2}, + MapValue: []interface{}{1, 2}, ContextValueString: "3", }) expect(t, c.IntSlice("test"), []int{3}) @@ -108,7 +108,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), FlagName: "test", - MapValue: []int{1, 2}, + MapValue: []interface{}{1, 2}, EnvVarName: "TEST", EnvVarValue: "3,4", }) diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index b720995..b3169e0 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -130,45 +130,59 @@ func (fsm *MapInputSource) String(name string) (string, error) { // StringSlice returns an []string from the map if it exists otherwise returns nil func (fsm *MapInputSource) StringSlice(name string) ([]string, error) { otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.([]string) - if !isType { - return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue) + if !exists { + otherGenericValue, exists = nestedVal(name, fsm.valueMap) + if !exists { + return nil, nil } - return otherValue, nil } - nestedGenericValue, exists := nestedVal(name, fsm.valueMap) - if exists { - otherValue, isType := nestedGenericValue.([]string) + + otherValue, isType := otherGenericValue.([]interface{}) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue) + } + + var stringSlice = make([]string, 0, len(otherValue)) + for i, v := range otherValue { + stringValue, isType := v.(string) + if !isType { - return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue) + return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "string", v) } - return otherValue, nil + + stringSlice = append(stringSlice, stringValue) } - return nil, nil + return stringSlice, nil } // IntSlice returns an []int from the map if it exists otherwise returns nil func (fsm *MapInputSource) IntSlice(name string) ([]int, error) { otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.([]int) - if !isType { - return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue) + if !exists { + otherGenericValue, exists = nestedVal(name, fsm.valueMap) + if !exists { + return nil, nil } - return otherValue, nil } - nestedGenericValue, exists := nestedVal(name, fsm.valueMap) - if exists { - otherValue, isType := nestedGenericValue.([]int) + + otherValue, isType := otherGenericValue.([]interface{}) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue) + } + + var intSlice = make([]int, 0, len(otherValue)) + for i, v := range otherValue { + intValue, isType := v.(int) + if !isType { - return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue) + return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "int", v) } - return otherValue, nil + + intSlice = append(intSlice, intValue) } - return nil, nil + return intSlice, nil } // Generic returns an cli.Generic from the map if it exists otherwise returns nil From e87dfbb6bb85250206841a9576e51b1323910356 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 16 Jan 2017 17:34:12 -0800 Subject: [PATCH 318/381] altsrc: Support slices for TOML --- altsrc/toml_file_loader.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 39c124f..37870fc 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -57,8 +57,8 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { } else { return nil, err } - case reflect.Array: - fallthrough // [todo] - Support array type + case reflect.Array, reflect.Slice: + ret[key] = val.([]interface{}) default: return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind()) } From 4ed366e2011dfb9efa3399c709af0976d5e87db4 Mon Sep 17 00:00:00 2001 From: Robert Bittle Date: Wed, 18 Jan 2017 23:29:26 -0500 Subject: [PATCH 319/381] Pass the ErrWriter on the root app to subcommands --- command.go | 1 + command_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/command.go b/command.go index d297eb9..4a409d4 100644 --- a/command.go +++ b/command.go @@ -264,6 +264,7 @@ func (c Command) startApp(ctx *Context) error { app.Author = ctx.App.Author app.Email = ctx.App.Email app.Writer = ctx.App.Writer + app.ErrWriter = ctx.App.ErrWriter app.categories = CommandCategories{} for _, command := range c.Subcommands { diff --git a/command_test.go b/command_test.go index 5e0e8de..5710184 100644 --- a/command_test.go +++ b/command_test.go @@ -153,3 +153,32 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { t.Errorf("Expect an intercepted error, but got \"%v\"", err) } } + +func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { + app := NewApp() + app.ErrWriter = ioutil.Discard + app.Commands = []Command{ + { + Name: "bar", + Usage: "this is for testing", + Subcommands: []Command{ + { + Name: "baz", + Usage: "this is for testing", + Action: func(c *Context) error { + if c.App.ErrWriter != ioutil.Discard { + return fmt.Errorf("ErrWriter not passed") + } + + return nil + }, + }, + }, + }, + } + + err := app.Run([]string{"foo", "bar", "baz"}) + if err != nil { + t.Fatal(err) + } +} From 6a70c4cc923c7359bacfa0500dc234d62e0ca986 Mon Sep 17 00:00:00 2001 From: John Weldon Date: Sat, 2 Jul 2016 12:35:48 -0700 Subject: [PATCH 320/381] Add JSON InputSource to altsrc package - Implement NewJSONSource* functions for returning an InputSource from various JSON data sources. - Copy and modify YAML tests for the JSON InputSource Changes: * Reverted the method calls and structs to match the v1 interface --- README.md | 6 +- altsrc/json_command_test.go | 324 ++++++++++++++++++++++++++++++++++ altsrc/json_source_context.go | 208 ++++++++++++++++++++++ 3 files changed, 535 insertions(+), 3 deletions(-) create mode 100644 altsrc/json_command_test.go create mode 100644 altsrc/json_source_context.go diff --git a/README.md b/README.md index 2bbbd8e..0a442a9 100644 --- a/README.md +++ b/README.md @@ -615,9 +615,9 @@ the yaml input source for any flags that are defined on that command. As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. -Currently only the aboved specified formats are supported but developers can -add support for other input sources by implementing the -altsrc.InputSourceContext for their given sources. +Currently only YAML and JSON files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. Here is a more complete sample of a command using YAML support: diff --git a/altsrc/json_command_test.go b/altsrc/json_command_test.go new file mode 100644 index 0000000..1f9af36 --- /dev/null +++ b/altsrc/json_command_test.go @@ -0,0 +1,324 @@ +package altsrc + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + "gopkg.in/urfave/cli.v1" +) + +const ( + fileName = "current.json" + simpleJSON = `{"test": 15}` + nestedJSON = `{"top": {"test": 15}}` +) + +func TestCommandJSONFileTest(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestGlobalEnvVarWins(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 10) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestGlobalEnvVarWinsNested(t *testing.T) { + cleanup := writeTempFile(t, fileName, nestedJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 10) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestSpecifiedFlagWins(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName, "--test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 7) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestSpecifiedFlagWinsNested(t *testing.T) { + cleanup := writeTempFile(t, fileName, nestedJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName, "--top.test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 7) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestDefaultValueFileWins(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestDefaultValueFileWinsNested(t *testing.T) { + cleanup := writeTempFile(t, fileName, nestedJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWins(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 11) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWinsNested(t *testing.T) { + cleanup := writeTempFile(t, fileName, nestedJSON) + defer cleanup() + + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 11) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func writeTempFile(t *testing.T, name string, content string) func() { + if err := ioutil.WriteFile(name, []byte(content), 0666); err != nil { + t.Fatalf("cannot write %q: %v", name, err) + } + return func() { + if err := os.Remove(name); err != nil { + t.Errorf("cannot remove %q: %v", name, err) + } + } +} diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go new file mode 100644 index 0000000..47ce82c --- /dev/null +++ b/altsrc/json_source_context.go @@ -0,0 +1,208 @@ +package altsrc + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "strings" + "time" + + "gopkg.in/urfave/cli.v1" +) + +// NewJSONSourceFromFlagFunc returns a func that takes a cli.Context +// and returns an InputSourceContext suitable for retrieving config +// variables from a file containing JSON data with the file name defined +// by the given flag. +func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) { + return func(context *cli.Context) (InputSourceContext, error) { + return NewJSONSourceFromFile(context.String(flag)) + } +} + +// NewJSONSourceFromFile returns an InputSourceContext suitable for +// retrieving config variables from a file (or url) containing JSON +// data. +func NewJSONSourceFromFile(f string) (InputSourceContext, error) { + data, err := loadDataFrom(f) + if err != nil { + return nil, err + } + return NewJSONSource(data) +} + +// NewJSONSourceFromReader returns an InputSourceContext suitable for +// retrieving config variables from an io.Reader that returns JSON data. +func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + return NewJSONSource(data) +} + +// NewJSONSource returns an InputSourceContext suitable for retrieving +// config variables from raw JSON data. +func NewJSONSource(data []byte) (InputSourceContext, error) { + var deserialized map[string]interface{} + if err := json.Unmarshal(data, &deserialized); err != nil { + return nil, err + } + return &jsonSource{deserialized: deserialized}, nil +} + +func (x *jsonSource) Int(name string) (int, error) { + i, err := x.getValue(name) + if err != nil { + return 0, err + } + switch v := i.(type) { + default: + return 0, fmt.Errorf("unexpected type %T for %q", i, name) + case int: + return v, nil + case float64: + return int(float64(v)), nil + case float32: + return int(float32(v)), nil + } +} + +func (x *jsonSource) Duration(name string) (time.Duration, error) { + i, err := x.getValue(name) + if err != nil { + return 0, err + } + v, ok := (time.Duration)(0), false + if v, ok = i.(time.Duration); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) Float64(name string) (float64, error) { + i, err := x.getValue(name) + if err != nil { + return 0, err + } + v, ok := (float64)(0), false + if v, ok = i.(float64); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) String(name string) (string, error) { + i, err := x.getValue(name) + if err != nil { + return "", err + } + v, ok := "", false + if v, ok = i.(string); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) StringSlice(name string) ([]string, error) { + i, err := x.getValue(name) + if err != nil { + return nil, err + } + switch v := i.(type) { + default: + return nil, fmt.Errorf("unexpected type %T for %q", i, name) + case []string: + return v, nil + case []interface{}: + c := []string{} + for _, s := range v { + if str, ok := s.(string); ok { + c = append(c, str) + } else { + return c, fmt.Errorf("unexpected item type %T in %T for %q", s, c, name) + } + } + return c, nil + } +} + +func (x *jsonSource) IntSlice(name string) ([]int, error) { + i, err := x.getValue(name) + if err != nil { + return nil, err + } + switch v := i.(type) { + default: + return nil, fmt.Errorf("unexpected type %T for %q", i, name) + case []int: + return v, nil + case []interface{}: + c := []int{} + for _, s := range v { + if i2, ok := s.(int); ok { + c = append(c, i2) + } else { + return c, fmt.Errorf("unexpected item type %T in %T for %q", s, c, name) + } + } + return c, nil + } +} + +func (x *jsonSource) Generic(name string) (cli.Generic, error) { + i, err := x.getValue(name) + if err != nil { + return nil, err + } + v, ok := (cli.Generic)(nil), false + if v, ok = i.(cli.Generic); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) Bool(name string) (bool, error) { + i, err := x.getValue(name) + if err != nil { + return false, err + } + v, ok := false, false + if v, ok = i.(bool); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +// since this source appears to require all configuration to be specified, the +// concept of a boolean defaulting to true seems inconsistent with no defaults +func (x *jsonSource) BoolT(name string) (bool, error) { + return false, fmt.Errorf("unsupported type BoolT for JSONSource") +} + +func (x *jsonSource) getValue(key string) (interface{}, error) { + return jsonGetValue(key, x.deserialized) +} + +func jsonGetValue(key string, m map[string]interface{}) (interface{}, error) { + var ret interface{} + var ok bool + working := m + keys := strings.Split(key, ".") + for ix, k := range keys { + if ret, ok = working[k]; !ok { + return ret, fmt.Errorf("missing key %q", key) + } + if working, ok = ret.(map[string]interface{}); !ok { + if ix < len(keys)-1 { + return ret, fmt.Errorf("unexpected intermediate value at %q segment of %q: %T", k, key, ret) + } + } + } + return ret, nil +} + +type jsonSource struct { + deserialized map[string]interface{} +} From 2526b57c56f30b50466c96c4133b1a4ad0f0191f Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Tue, 14 Feb 2017 21:17:05 -0800 Subject: [PATCH 321/381] Allow slightly longer lines in Python scripts --- .flake8 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6deafc2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 From 8d8f927bcb0447918aaa09c5b87160ddf2e5842b Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 4 Mar 2017 14:28:24 -0800 Subject: [PATCH 322/381] Change flag test error checking to use regexp rather than strings As the error messages changed in 1.8 --- flag_test.go | 92 +++++++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/flag_test.go b/flag_test.go index 0dd8654..1ccb639 100644 --- a/flag_test.go +++ b/flag_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "reflect" + "regexp" "runtime" "strings" "testing" @@ -31,57 +32,57 @@ func TestBoolFlagHelpOutput(t *testing.T) { func TestFlagsFromEnv(t *testing.T) { var flagTests = []struct { - input string - output interface{} - flag Flag - err error + input string + output interface{} + flag Flag + errRegexp string }{ - {"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"foobar", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Errorf(`could not parse foobar as bool value for flag debug: strconv.ParseBool: parsing "foobar": invalid syntax`)}, + {"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"foobar", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, - {"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil}, - {"foobar", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Errorf(`could not parse foobar as bool value for flag debug: strconv.ParseBool: parsing "foobar": invalid syntax`)}, + {"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"foobar", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, - {"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, nil}, - {"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Errorf(`could not parse foobar as duration for flag time: time: invalid duration foobar`)}, + {"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, ""}, + {"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)}, - {"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"foobar", 0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as float64 value for flag seconds: strconv.ParseFloat: parsing "foobar": invalid syntax`)}, + {"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"foobar", 0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)}, - {"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as int value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, - {"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + {"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, + {"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, - {"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as int value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, - {"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + {"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, + {"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, - {"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2,2", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2,2 as int slice value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, - {"foobar", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + {"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2,2", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)}, + {"foobar", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)}, - {"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2,2", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2,2 as int64 slice value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)}, - {"foobar", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int64 slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)}, + {"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2,2", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)}, + {"foobar", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)}, - {"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, nil}, + {"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, ""}, - {"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, nil}, + {"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, ""}, - {"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as uint value for flag seconds: strconv.ParseUint: parsing "1.2": invalid syntax`)}, - {"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)}, + {"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)}, + {"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)}, - {"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil}, - {"1.2", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as uint64 value for flag seconds: strconv.ParseUint: parsing "1.2": invalid syntax`)}, - {"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint64 value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)}, + {"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)}, + {"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)}, - {"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, nil}, + {"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, ""}, } for _, test := range flagTests { @@ -98,8 +99,19 @@ func TestFlagsFromEnv(t *testing.T) { } err := a.Run([]string{"run"}) - if !reflect.DeepEqual(test.err, err) { - t.Errorf("expected error %s, got error %s", test.err, err) + + if test.errRegexp != "" { + if err == nil { + t.Errorf("expected error to match %s, got none", test.errRegexp) + } else { + if matched, _ := regexp.MatchString(test.errRegexp, err.Error()); !matched { + t.Errorf("expected error to match %s, got error %s", test.errRegexp, err) + } + } + } else { + if err != nil && test.errRegexp == "" { + t.Errorf("expected no error got %s", err) + } } } } From 9e5b04886c4bfee2ceba1465b8121057355c4e53 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 4 Mar 2017 14:33:36 -0800 Subject: [PATCH 323/381] Remove logic that exited even if the error was not an OsExiter This was introduced in #496, but was discovered to be a regression in the behavior of the library. --- errors.go | 9 --------- errors_test.go | 52 +------------------------------------------------- 2 files changed, 1 insertion(+), 60 deletions(-) diff --git a/errors.go b/errors.go index f9d648e..562b295 100644 --- a/errors.go +++ b/errors.go @@ -97,15 +97,6 @@ func HandleExitCoder(err error) { OsExiter(code) return } - - if err.Error() != "" { - if _, ok := err.(ErrorFormatter); ok { - fmt.Fprintf(ErrWriter, "%+v\n", err) - } else { - fmt.Fprintln(ErrWriter, err) - } - } - OsExiter(1) } func handleMultiError(multiErr MultiError) int { diff --git a/errors_test.go b/errors_test.go index d9e0da6..9b609c5 100644 --- a/errors_test.go +++ b/errors_test.go @@ -67,56 +67,6 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { expect(t, called, true) } -func TestHandleExitCoder_ErrorWithMessage(t *testing.T) { - exitCode := 0 - called := false - - OsExiter = func(rc int) { - if !called { - exitCode = rc - called = true - } - } - ErrWriter = &bytes.Buffer{} - - defer func() { - OsExiter = fakeOsExiter - ErrWriter = fakeErrWriter - }() - - err := errors.New("gourd havens") - HandleExitCoder(err) - - expect(t, exitCode, 1) - expect(t, called, true) - expect(t, ErrWriter.(*bytes.Buffer).String(), "gourd havens\n") -} - -func TestHandleExitCoder_ErrorWithoutMessage(t *testing.T) { - exitCode := 0 - called := false - - OsExiter = func(rc int) { - if !called { - exitCode = rc - called = true - } - } - ErrWriter = &bytes.Buffer{} - - defer func() { - OsExiter = fakeOsExiter - ErrWriter = fakeErrWriter - }() - - err := errors.New("") - HandleExitCoder(err) - - expect(t, exitCode, 1) - expect(t, called, true) - expect(t, ErrWriter.(*bytes.Buffer).String(), "") -} - // make a stub to not import pkg/errors type ErrorWithFormat struct { error @@ -145,7 +95,7 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { ErrWriter = fakeErrWriter }() - err := NewErrorWithFormat("I am formatted") + err := NewExitError(NewErrorWithFormat("I am formatted"), 1) HandleExitCoder(err) expect(t, called, true) From b9c535392027a122ff9776531002d450821426e5 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 6 Apr 2017 15:11:23 +0200 Subject: [PATCH 324/381] Unset PROG variable to fix issue when mulitple apps run autocomplete from etc/bash_completion.d directory --- autocomplete/bash_autocomplete | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) mode change 100644 => 100755 autocomplete/bash_autocomplete diff --git a/autocomplete/bash_autocomplete b/autocomplete/bash_autocomplete old mode 100644 new mode 100755 index 21a232f..37d9c14 --- a/autocomplete/bash_autocomplete +++ b/autocomplete/bash_autocomplete @@ -3,12 +3,14 @@ : ${PROG:=$(basename ${BASH_SOURCE})} _cli_bash_autocomplete() { - local cur opts base - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - } - - complete -F _cli_bash_autocomplete $PROG + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 +} + +complete -F _cli_bash_autocomplete $PROG + +unset PROG From 572dc46db5570298570b06ed63ae261086c8c7a4 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 24 Apr 2017 10:39:43 -0700 Subject: [PATCH 325/381] Explicitly fetch `goimports` Fetching the whole tree was failing on some Go versions and we really only need goimports. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94836d7..4417251 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ matrix: before_script: - go get github.com/urfave/gfmrun/... || true -- go get golang.org/x/tools/... || true +- go get golang.org/x/tools/cmd/goimports - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi From 1bf0bb96b7b005507c19a2d71f4f3cadcc1202e2 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 24 Apr 2017 10:54:32 -0700 Subject: [PATCH 326/381] Only support supported Go versions As described on https://github.com/golang/go/wiki/Go-Release-Cycle The maintainers do not test compatibility for libraries (e.g. in golang.org/x/tools) on older versions. --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4417251..890e185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,9 @@ cache: - node_modules go: -- 1.2.x -- 1.3.x -- 1.4.2 -- 1.5.x - 1.6.x - 1.7.x +- 1.8.x - master matrix: @@ -23,6 +20,8 @@ matrix: os: osx - go: 1.7.x os: osx + - go: 1.8.x + os: osx before_script: - go get github.com/urfave/gfmrun/... || true From 87fe13079e3f452a366ae8d7f991261f9324d630 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 24 Apr 2017 15:19:34 -0700 Subject: [PATCH 327/381] Rely on Command context in Run() Was previously relying on the parent context which caused things like `.Command` to not be available to OnUsageError(). Fixes #609 --- command.go | 16 ++++++++-------- command_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/command.go b/command.go index 40ebdb6..63f183a 100644 --- a/command.go +++ b/command.go @@ -154,19 +154,20 @@ func (c Command) Run(ctx *Context) (err error) { } context := NewContext(ctx.App, set, ctx) + context.Command = c if checkCommandCompletions(context, c.Name) { return nil } if err != nil { if c.OnUsageError != nil { - err := c.OnUsageError(ctx, err, false) + err := c.OnUsageError(context, err, false) HandleExitCoder(err) return err } - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error()) - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) + fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) + fmt.Fprintln(context.App.Writer) + ShowCommandHelp(context, c.Name) return err } @@ -191,9 +192,9 @@ func (c Command) Run(ctx *Context) (err error) { if c.Before != nil { err = c.Before(context) if err != nil { - fmt.Fprintln(ctx.App.Writer, err) - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) + fmt.Fprintln(context.App.Writer, err) + fmt.Fprintln(context.App.Writer) + ShowCommandHelp(context, c.Name) HandleExitCoder(err) return err } @@ -203,7 +204,6 @@ func (c Command) Run(ctx *Context) (err error) { c.Action = helpSubcommand.Action } - context.Command = c err = HandleAction(c.Action, context) if err != nil { diff --git a/command_test.go b/command_test.go index 5710184..10fff2d 100644 --- a/command_test.go +++ b/command_test.go @@ -127,6 +127,30 @@ func TestCommand_Run_BeforeSavesMetadata(t *testing.T) { } } +func TestCommand_OnUsageError_hasCommandContext(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + { + Name: "bar", + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted in bar") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() app.Commands = []Command{ From 1794792adfc0f1cea5a4e56eefc36fd849018d05 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Fri, 5 May 2017 20:07:18 -0700 Subject: [PATCH 328/381] Add ability to use custom Flag types Users can now use custom flags types (conforming to the Flag interface) in their applications. They can also use custom flags for the three global flags (Help, Version, bash completion). --- app_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++-- flag.go | 12 +++++------ help.go | 10 ++++----- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/app_test.go b/app_test.go index 10f1562..e14ddaf 100644 --- a/app_test.go +++ b/app_test.go @@ -1520,6 +1520,63 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { } } +// A custom flag that conforms to the relevant interfaces, but has none of the +// fields that the other flag types do. +type customBoolFlag struct { + Nombre string +} + +// Don't use the normal FlagStringer +func (c *customBoolFlag) String() string { + return "***" + c.Nombre + "***" +} + +func (c *customBoolFlag) GetName() string { + return c.Nombre +} + +func (c *customBoolFlag) Apply(set *flag.FlagSet) { + set.String(c.Nombre, c.Nombre, "") +} + +func TestCustomFlagsUnused(t *testing.T) { + app := NewApp() + app.Flags = []Flag{&customBoolFlag{"custom"}} + + err := app.Run([]string{"foo"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestCustomFlagsUsed(t *testing.T) { + app := NewApp() + app.Flags = []Flag{&customBoolFlag{"custom"}} + + err := app.Run([]string{"foo", "--custom=bar"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestCustomHelpVersionFlags(t *testing.T) { + app := NewApp() + + // Be sure to reset the global flags + defer func(helpFlag Flag, versionFlag Flag) { + HelpFlag = helpFlag + VersionFlag = versionFlag + }(HelpFlag, VersionFlag) + + HelpFlag = &customBoolFlag{"help-custom"} + VersionFlag = &customBoolFlag{"version-custom"} + + err := app.Run([]string{"foo", "--help-custom=bar"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + func TestHandleAction_WithNonFuncAction(t *testing.T) { app := NewApp() app.Action = 42 @@ -1642,7 +1699,7 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) { for _, flag := range ctx.App.Flags { for _, name := range strings.Split(flag.GetName(), ",") { - if name == BashCompletionFlag.Name { + if name == BashCompletionFlag.GetName() { continue } @@ -1659,7 +1716,7 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) { app.Action = func(ctx *Context) error { return fmt.Errorf("should not get here") } - err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.Name}) + err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.GetName()}) if err != nil { t.Errorf("app should not return an error: %s", err) } diff --git a/flag.go b/flag.go index 7dd8a2c..877ff35 100644 --- a/flag.go +++ b/flag.go @@ -14,13 +14,13 @@ import ( const defaultPlaceholder = "value" // BashCompletionFlag enables bash-completion for all commands and subcommands -var BashCompletionFlag = BoolFlag{ +var BashCompletionFlag Flag = BoolFlag{ Name: "generate-bash-completion", Hidden: true, } // VersionFlag prints the version for the application -var VersionFlag = BoolFlag{ +var VersionFlag Flag = BoolFlag{ Name: "version, v", Usage: "print the version", } @@ -28,7 +28,7 @@ var VersionFlag = BoolFlag{ // HelpFlag 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{ +var HelpFlag Flag = BoolFlag{ Name: "help, h", Usage: "show help", } @@ -630,7 +630,8 @@ func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { func visibleFlags(fl []Flag) []Flag { visible := []Flag{} for _, flag := range fl { - if !flagValue(flag).FieldByName("Hidden").Bool() { + field := flagValue(flag).FieldByName("Hidden") + if !field.IsValid() || !field.Bool() { visible = append(visible, flag) } } @@ -723,9 +724,8 @@ func stringifyFlag(f Flag) string { needsPlaceholder := false defaultValueString := "" - val := fv.FieldByName("Value") - if val.IsValid() { + if val := fv.FieldByName("Value"); val.IsValid() { needsPlaceholder = true defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) diff --git a/help.go b/help.go index d00e4da..df4cb56 100644 --- a/help.go +++ b/help.go @@ -212,8 +212,8 @@ func printHelp(out io.Writer, templ string, data interface{}) { func checkVersion(c *Context) bool { found := false - if VersionFlag.Name != "" { - eachName(VersionFlag.Name, func(name string) { + if VersionFlag.GetName() != "" { + eachName(VersionFlag.GetName(), func(name string) { if c.GlobalBool(name) || c.Bool(name) { found = true } @@ -224,8 +224,8 @@ func checkVersion(c *Context) bool { func checkHelp(c *Context) bool { found := false - if HelpFlag.Name != "" { - eachName(HelpFlag.Name, func(name string) { + if HelpFlag.GetName() != "" { + eachName(HelpFlag.GetName(), func(name string) { if c.GlobalBool(name) || c.Bool(name) { found = true } @@ -260,7 +260,7 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { pos := len(arguments) - 1 lastArg := arguments[pos] - if lastArg != "--"+BashCompletionFlag.Name { + if lastArg != "--"+BashCompletionFlag.GetName() { return false, arguments } From f7d6a07f2d060ba198dc9c00a48fa6dbe03fb7b5 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 25 Nov 2016 00:16:48 -0800 Subject: [PATCH 329/381] Add support for custom help templates. --- app.go | 4 ++++ command.go | 6 ++++++ context.go | 16 +++++++++++++++- help.go | 24 ++++++++++++++++++++++-- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 95ffc0b..8f84cb3 100644 --- a/app.go +++ b/app.go @@ -85,6 +85,10 @@ type App struct { ErrWriter io.Writer // Other custom info Metadata map[string]interface{} + // CustomAppHelpTemplate the text template for app help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomAppHelpTemplate string didSetup bool } diff --git a/command.go b/command.go index 63f183a..a83495e 100644 --- a/command.go +++ b/command.go @@ -59,6 +59,11 @@ type Command struct { // Full name of command for help, defaults to full command name, including parent commands. HelpName string commandNamePath []string + + // CustomHelpTemplate the text template for the command help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomHelpTemplate string } type CommandsByName []Command @@ -250,6 +255,7 @@ func (c Command) startApp(ctx *Context) error { // set CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound + app.CustomAppHelpTemplate = c.CustomHelpTemplate // set the flags and commands app.Commands = c.Subcommands diff --git a/context.go b/context.go index cb89e92..021e5e5 100644 --- a/context.go +++ b/context.go @@ -186,9 +186,23 @@ func (a Args) First() string { return a.Get(0) } +// Last - Return the last argument, or else a blank string +func (a Args) Last() string { + return a.Get(len(a) - 1) +} + +// Head - Return the rest of the arguments (not the last one) +// or else an empty string slice +func (a Args) Head() Args { + if len(a) == 1 { + return a + } + return []string(a)[:len(a)-1] +} + // Tail returns the rest of the arguments (not the first one) // or else an empty string slice -func (a Args) Tail() []string { +func (a Args) Tail() Args { if len(a) >= 2 { return []string(a)[1:] } diff --git a/help.go b/help.go index d00e4da..78f84f5 100644 --- a/help.go +++ b/help.go @@ -120,9 +120,19 @@ var HelpPrinter helpPrinter = printHelp // VersionPrinter prints the version for the App var VersionPrinter = printVersion +// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code. +func ShowAppHelpAndExit(c *Context, exitCode int) { + ShowAppHelp(c) + os.Exit(exitCode) +} + // ShowAppHelp is an action that displays the help. func ShowAppHelp(c *Context) error { - HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) + if c.App.CustomAppHelpTemplate != "" { + HelpPrinter(c.App.Writer, c.App.CustomAppHelpTemplate, c.App) + } else { + HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) + } return nil } @@ -138,6 +148,12 @@ func DefaultAppComplete(c *Context) { } } +// ShowCommandHelpAndExit - exits with code after showing help +func ShowCommandHelpAndExit(c *Context, command string, code int) { + ShowCommandHelp(c, command) + os.Exit(code) +} + // ShowCommandHelp prints help for the given command func ShowCommandHelp(ctx *Context, command string) error { // show the subcommand help for a command with subcommands @@ -148,7 +164,11 @@ func ShowCommandHelp(ctx *Context, command string) error { for _, c := range ctx.App.Commands { if c.HasName(command) { - HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) + if c.CustomHelpTemplate != "" { + HelpPrinter(ctx.App.Writer, c.CustomHelpTemplate, c) + } else { + HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) + } return nil } } From baa33cb888078362b0b955d6f8715445ad2cf662 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 25 Nov 2016 01:07:42 -0800 Subject: [PATCH 330/381] Add support for ExtraInfo. --- app.go | 2 ++ help.go | 26 ++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 8f84cb3..26a4d4d 100644 --- a/app.go +++ b/app.go @@ -85,6 +85,8 @@ type App struct { ErrWriter io.Writer // Other custom info Metadata map[string]interface{} + // Carries a function which returns app specific info. + ExtraInfo func() map[string]string // CustomAppHelpTemplate the text template for app help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. diff --git a/help.go b/help.go index 78f84f5..b8ffee0 100644 --- a/help.go +++ b/help.go @@ -112,11 +112,18 @@ var helpSubcommand = Command{ // Prints help for the App or Command type helpPrinter func(w io.Writer, templ string, data interface{}) +// Prints help for the App or Command with custom template function. +type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{}) + // HelpPrinter is a function that writes the help output. If not set a default // is used. The function signature is: // func(w io.Writer, templ string, data interface{}) var HelpPrinter helpPrinter = printHelp +// HelPrinterCustom is same as HelpPrinter but +// takes a custom function for template function map. +var HelpPrinterCustom helpPrinterCustom = printHelpCustom + // VersionPrinter prints the version for the App var VersionPrinter = printVersion @@ -129,7 +136,13 @@ func ShowAppHelpAndExit(c *Context, exitCode int) { // ShowAppHelp is an action that displays the help. func ShowAppHelp(c *Context) error { if c.App.CustomAppHelpTemplate != "" { - HelpPrinter(c.App.Writer, c.App.CustomAppHelpTemplate, c.App) + if c.App.ExtraInfo != nil { + HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, map[string]interface{}{ + "ExtraInfo": c.App.ExtraInfo, + }) + } else { + HelpPrinter(c.App.Writer, c.App.CustomAppHelpTemplate, c.App) + } } else { HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) } @@ -211,10 +224,15 @@ func ShowCommandCompletions(ctx *Context, command string) { } } -func printHelp(out io.Writer, templ string, data interface{}) { +func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) { funcMap := template.FuncMap{ "join": strings.Join, } + if customFunc != nil { + for key, value := range customFunc { + funcMap[key] = value + } + } w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) @@ -230,6 +248,10 @@ func printHelp(out io.Writer, templ string, data interface{}) { w.Flush() } +func printHelp(out io.Writer, templ string, data interface{}) { + printHelpCustom(out, templ, data, nil) +} + func checkVersion(c *Context) bool { found := false if VersionFlag.Name != "" { From dd3849a7e602d4506eace87bed5202d9d416f44f Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 15 Feb 2017 01:44:04 -0800 Subject: [PATCH 331/381] Add tests as requested. --- context.go | 16 +------ help.go | 26 +++++------ help_test.go | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 27 deletions(-) diff --git a/context.go b/context.go index 021e5e5..cb89e92 100644 --- a/context.go +++ b/context.go @@ -186,23 +186,9 @@ func (a Args) First() string { return a.Get(0) } -// Last - Return the last argument, or else a blank string -func (a Args) Last() string { - return a.Get(len(a) - 1) -} - -// Head - Return the rest of the arguments (not the last one) -// or else an empty string slice -func (a Args) Head() Args { - if len(a) == 1 { - return a - } - return []string(a)[:len(a)-1] -} - // Tail returns the rest of the arguments (not the first one) // or else an empty string slice -func (a Args) Tail() Args { +func (a Args) Tail() []string { if len(a) >= 2 { return []string(a)[1:] } diff --git a/help.go b/help.go index b8ffee0..e5602d8 100644 --- a/help.go +++ b/help.go @@ -120,7 +120,7 @@ type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customF // func(w io.Writer, templ string, data interface{}) var HelpPrinter helpPrinter = printHelp -// HelPrinterCustom is same as HelpPrinter but +// HelpPrinterCustom is same as HelpPrinter but // takes a custom function for template function map. var HelpPrinterCustom helpPrinterCustom = printHelpCustom @@ -134,18 +134,20 @@ func ShowAppHelpAndExit(c *Context, exitCode int) { } // ShowAppHelp is an action that displays the help. -func ShowAppHelp(c *Context) error { - if c.App.CustomAppHelpTemplate != "" { - if c.App.ExtraInfo != nil { - HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, map[string]interface{}{ - "ExtraInfo": c.App.ExtraInfo, - }) - } else { - HelpPrinter(c.App.Writer, c.App.CustomAppHelpTemplate, c.App) - } - } else { +func ShowAppHelp(c *Context) (err error) { + if c.App.CustomAppHelpTemplate == "" { HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) + return + } + customAppData := func() map[string]interface{} { + if c.App.ExtraInfo == nil { + return nil + } + return map[string]interface{}{ + "ExtraInfo": c.App.ExtraInfo, + } } + HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData()) return nil } @@ -178,7 +180,7 @@ func ShowCommandHelp(ctx *Context, command string) error { for _, c := range ctx.App.Commands { if c.HasName(command) { if c.CustomHelpTemplate != "" { - HelpPrinter(ctx.App.Writer, c.CustomHelpTemplate, c) + HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil) } else { HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) } diff --git a/help_test.go b/help_test.go index 7c15400..78d8973 100644 --- a/help_test.go +++ b/help_test.go @@ -3,6 +3,8 @@ package cli import ( "bytes" "flag" + "fmt" + "runtime" "strings" "testing" ) @@ -256,6 +258,49 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) { } } +func TestShowCommandHelp_Customtemplate(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Action: func(ctx *Context) error { + return nil + }, + HelpName: "foo frobbly", + CustomHelpTemplate: `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} [FLAGS] TARGET [TARGET ...] + +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}} +EXAMPLES: + 1. Frobbly runs with this param locally. + $ {{.HelpName}} wobbly +`, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "help", "frobbly"}) + + if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") { + t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "1. Frobbly runs with this param locally.") { + t.Errorf("expected output to include \"1. Frobbly runs with this param locally.\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "$ foo frobbly wobbly") { + t.Errorf("expected output to include \"$ foo frobbly wobbly\"; got: %q", output.String()) + } +} + func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ Commands: []Command{ @@ -287,3 +332,78 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) { t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) } } + +func TestShowAppHelp_CustomAppTemplate(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Action: func(ctx *Context) error { + return nil + }, + }, + { + Name: "secretfrob", + Hidden: true, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + ExtraInfo: func() map[string]string { + platform := fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH) + goruntime := fmt.Sprintf("Version: %s | CPUs: %d", runtime.Version(), runtime.NumCPU()) + return map[string]string{ + "PLATFORM": platform, + "RUNTIME": goruntime, + } + }, + CustomAppHelpTemplate: `NAME: + {{.Name}} - {{.Usage}} + +USAGE: + {{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...] + +COMMANDS: + {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{if .VisibleFlags}} +GLOBAL FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +VERSION: + 2.0.0 +{{"\n"}}{{range $key, $value := ExtraInfo}} +{{$key}}: + {{$value}} +{{end}}`, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"app", "--help"}) + + if strings.Contains(output.String(), "secretfrob") { + t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "frobbly") { + t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "PLATFORM:") || + !strings.Contains(output.String(), "OS:") || + !strings.Contains(output.String(), "Arch:") { + t.Errorf("expected output to include \"PLATFORM:, OS: and Arch:\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "RUNTIME:") || + !strings.Contains(output.String(), "Version:") || + !strings.Contains(output.String(), "CPUs:") { + t.Errorf("expected output to include \"RUNTIME:, Version: and CPUs:\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "VERSION:") || + !strings.Contains(output.String(), "2.0.0") { + t.Errorf("expected output to include \"VERSION:, 2.0.0\"; got: %q", output.String()) + } +} From 291122b8f0ad73134ddee3cdc27b5164e4480ab3 Mon Sep 17 00:00:00 2001 From: "Joe Richey joerichey@google.com" Date: Fri, 5 May 2017 15:01:44 -0700 Subject: [PATCH 332/381] Subcommand OnUsageError should be passed to app Not all of the Command components were being passed to the created App in the startApp function. --- command.go | 1 + command_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/command.go b/command.go index a83495e..bb0733c 100644 --- a/command.go +++ b/command.go @@ -291,6 +291,7 @@ func (c Command) startApp(ctx *Context) error { } else { app.Action = helpSubcommand.Action } + app.OnUsageError = c.OnUsageError for index, cc := range app.Commands { app.Commands[index].commandNamePath = []string{c.Name, cc.Name} diff --git a/command_test.go b/command_test.go index 10fff2d..4ad994c 100644 --- a/command_test.go +++ b/command_test.go @@ -178,6 +178,38 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { } } +func TestCommand_OnUsageError_WithSubcommand(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + { + Name: "bar", + Subcommands: []Command{ + { + Name: "baz", + }, + }, + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { app := NewApp() app.ErrWriter = ioutil.Discard From c64d74a5d989d144e33854e303f20981f9e8f049 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 24 Apr 2017 14:54:35 -0700 Subject: [PATCH 333/381] Do not double print errors from Before() They should be handled by HandleExitCoder() as `After()` errors are. --- app.go | 1 - command.go | 2 -- 2 files changed, 3 deletions(-) diff --git a/app.go b/app.go index 26a4d4d..51fc45d 100644 --- a/app.go +++ b/app.go @@ -240,7 +240,6 @@ func (a *App) Run(arguments []string) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) ShowAppHelp(context) HandleExitCoder(beforeErr) err = beforeErr diff --git a/command.go b/command.go index bb0733c..23de294 100644 --- a/command.go +++ b/command.go @@ -197,8 +197,6 @@ func (c Command) Run(ctx *Context) (err error) { if c.Before != nil { err = c.Before(context) if err != nil { - fmt.Fprintln(context.App.Writer, err) - fmt.Fprintln(context.App.Writer) ShowCommandHelp(context, c.Name) HandleExitCoder(err) return err From 48db8e24356a42996e7d6b0b62c55e0df1d4492e Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 29 Jan 2017 21:05:21 -0800 Subject: [PATCH 334/381] Display UsageText on Commands and Subcommands If it is set, otherwise build the usage. Fixes #592 --- help.go | 4 ++-- help_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/help.go b/help.go index 216ff78..57ec98d 100644 --- a/help.go +++ b/help.go @@ -47,7 +47,7 @@ var CommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} @@ -67,7 +67,7 @@ var SubcommandHelpTemplate = `NAME: {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} USAGE: - {{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{end}}{{range .VisibleCommands}} diff --git a/help_test.go b/help_test.go index 78d8973..70b6300 100644 --- a/help_test.go +++ b/help_test.go @@ -283,7 +283,6 @@ EXAMPLES: }, }, } - output := &bytes.Buffer{} app.Writer = output app.Run([]string{"foo", "help", "frobbly"}) @@ -301,6 +300,50 @@ EXAMPLES: } } +func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + UsageText: "this is usage text", + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + + app.Run([]string{"foo", "frobbly", "--help"}) + + if !strings.Contains(output.String(), "this is usage text") { + t.Errorf("expected output to include usage text; got: %q", output.String()) + } +} + +func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Subcommands: []Command{ + { + Name: "bobbly", + UsageText: "this is usage text", + }, + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "frobbly", "bobbly", "--help"}) + + if !strings.Contains(output.String(), "this is usage text") { + t.Errorf("expected output to include usage text; got: %q", output.String()) + } +} + func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ Commands: []Command{ From c48a82964028acd0f19ee17257789f7c9f5afc78 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Tue, 25 Apr 2017 09:29:43 -0700 Subject: [PATCH 335/381] Allow custom exit err handlers --- app.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app.go b/app.go index 51fc45d..5d7ed8b 100644 --- a/app.go +++ b/app.go @@ -83,6 +83,8 @@ type App struct { Writer io.Writer // ErrWriter writes error output ErrWriter io.Writer + // Execute this function to handle ExitErrors + ExitErrHandler ExitErrHandlerFunc // Other custom info Metadata map[string]interface{} // Carries a function which returns app specific info. @@ -207,7 +209,7 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) - HandleExitCoder(err) + a.handleExitCoder(err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -241,7 +243,7 @@ func (a *App) Run(arguments []string) (err error) { beforeErr := a.Before(context) if beforeErr != nil { ShowAppHelp(context) - HandleExitCoder(beforeErr) + a.handleExitCoder(beforeErr) err = beforeErr return err } @@ -263,7 +265,7 @@ func (a *App) Run(arguments []string) (err error) { // Run default Action err = HandleAction(a.Action, context) - HandleExitCoder(err) + a.handleExitCoder(err) return err } @@ -330,7 +332,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - HandleExitCoder(err) + a.handleExitCoder(err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -352,7 +354,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { defer func() { afterErr := a.After(context) if afterErr != nil { - HandleExitCoder(err) + a.handleExitCoder(err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -365,7 +367,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - HandleExitCoder(beforeErr) + a.handleExitCoder(beforeErr) err = beforeErr return err } @@ -383,7 +385,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // Run default Action err = HandleAction(a.Action, context) - HandleExitCoder(err) + a.handleExitCoder(err) return err } @@ -464,6 +466,14 @@ func (a *App) appendFlag(flag Flag) { } } +func (a *App) handleExitCoder(err error) { + if a.ExitErrHandler != nil { + a.ExitErrHandler(err) + } else { + HandleExitCoder(err) + } +} + // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name From 538742687bbd979a7b4f975468af76ce5cffb972 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Tue, 25 Apr 2017 09:31:53 -0700 Subject: [PATCH 336/381] Add ExitErrHandlerFunc type --- funcs.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/funcs.go b/funcs.go index cba5e6c..4737faf 100644 --- a/funcs.go +++ b/funcs.go @@ -26,3 +26,7 @@ type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error // FlagStringFunc is used by the help generation to display a flag, which is // expected to be a single line. type FlagStringFunc func(Flag) string + +// ExitErrHandlerFunc is executed if provided in order to handle ExitError values +// returned by Actions and Before/After functions. +type ExitErrHandlerFunc func(error) From 827da610b4bff0ffbc06cd2d92eddae552f7d1a2 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Tue, 25 Apr 2017 09:33:54 -0700 Subject: [PATCH 337/381] Add a bit more documentation --- app.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 5d7ed8b..4138f32 100644 --- a/app.go +++ b/app.go @@ -83,7 +83,8 @@ type App struct { Writer io.Writer // ErrWriter writes error output ErrWriter io.Writer - // Execute this function to handle ExitErrors + // Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to + // function as a default, so this is optional. ExitErrHandler ExitErrHandlerFunc // Other custom info Metadata map[string]interface{} From 80b09a4d1117ad69430582685e59dfe560caa948 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Tue, 25 Apr 2017 11:20:41 -0700 Subject: [PATCH 338/381] Fix how to do defaults in app.go --- app.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/app.go b/app.go index 4138f32..f1a8f27 100644 --- a/app.go +++ b/app.go @@ -121,6 +121,7 @@ func NewApp() *App { Action: helpCommand.Action, Compiled: compileTime(), Writer: os.Stdout, + ExitErrHandler: HandleExitCoder, } } @@ -210,7 +211,7 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) - a.handleExitCoder(err) + a.ExitErrHandler(err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -244,7 +245,7 @@ func (a *App) Run(arguments []string) (err error) { beforeErr := a.Before(context) if beforeErr != nil { ShowAppHelp(context) - a.handleExitCoder(beforeErr) + a.ExitErrHandler(beforeErr) err = beforeErr return err } @@ -266,7 +267,7 @@ func (a *App) Run(arguments []string) (err error) { // Run default Action err = HandleAction(a.Action, context) - a.handleExitCoder(err) + a.ExitErrHandler(err) return err } @@ -333,7 +334,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - a.handleExitCoder(err) + a.ExitErrHandler(err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -355,7 +356,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { defer func() { afterErr := a.After(context) if afterErr != nil { - a.handleExitCoder(err) + a.ExitErrHandler(err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -368,7 +369,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - a.handleExitCoder(beforeErr) + a.ExitErrHandler(beforeErr) err = beforeErr return err } @@ -386,7 +387,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // Run default Action err = HandleAction(a.Action, context) - a.handleExitCoder(err) + a.ExitErrHandler(err) return err } @@ -467,14 +468,6 @@ func (a *App) appendFlag(flag Flag) { } } -func (a *App) handleExitCoder(err error) { - if a.ExitErrHandler != nil { - a.ExitErrHandler(err) - } else { - HandleExitCoder(err) - } -} - // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name From ceee6408d5cbbb9f113157d0a62b1ffed1f2b510 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Tue, 25 Apr 2017 13:02:05 -0700 Subject: [PATCH 339/381] Revert "Fix how to do defaults in app.go" This reverts commit 8906567dc2ad52fd31c50cf02fa606505a1323ba. --- app.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/app.go b/app.go index f1a8f27..e9ed7ab 100644 --- a/app.go +++ b/app.go @@ -121,7 +121,6 @@ func NewApp() *App { Action: helpCommand.Action, Compiled: compileTime(), Writer: os.Stdout, - ExitErrHandler: HandleExitCoder, } } @@ -211,7 +210,7 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) - a.ExitErrHandler(err) + a.handleExitCoder(err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -244,8 +243,9 @@ func (a *App) Run(arguments []string) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { + fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) ShowAppHelp(context) - a.ExitErrHandler(beforeErr) + a.handleExitCoder(beforeErr) err = beforeErr return err } @@ -267,7 +267,7 @@ func (a *App) Run(arguments []string) (err error) { // Run default Action err = HandleAction(a.Action, context) - a.ExitErrHandler(err) + a.handleExitCoder(err) return err } @@ -334,7 +334,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - a.ExitErrHandler(err) + a.handleExitCoder(err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -356,7 +356,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { defer func() { afterErr := a.After(context) if afterErr != nil { - a.ExitErrHandler(err) + a.handleExitCoder(err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -369,7 +369,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - a.ExitErrHandler(beforeErr) + a.handleExitCoder(beforeErr) err = beforeErr return err } @@ -387,7 +387,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // Run default Action err = HandleAction(a.Action, context) - a.ExitErrHandler(err) + a.handleExitCoder(err) return err } @@ -468,6 +468,14 @@ func (a *App) appendFlag(flag Flag) { } } +func (a *App) handleExitCoder(err error) { + if a.ExitErrHandler != nil { + a.ExitErrHandler(err) + } else { + HandleExitCoder(err) + } +} + // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name From 9d61cbad0260bc7f2a72b07142a0120072e3800a Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Tue, 25 Apr 2017 12:45:08 -0700 Subject: [PATCH 340/381] Updated command.go to use App handleExitCoder --- command.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/command.go b/command.go index 23de294..fffa02f 100644 --- a/command.go +++ b/command.go @@ -167,7 +167,7 @@ func (c Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { err := c.OnUsageError(context, err, false) - HandleExitCoder(err) + context.App.handleExitCoder(err) return err } fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) @@ -184,7 +184,7 @@ func (c Command) Run(ctx *Context) (err error) { defer func() { afterErr := c.After(context) if afterErr != nil { - HandleExitCoder(err) + ctx.App.handleExitCoder(err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -198,7 +198,7 @@ func (c Command) Run(ctx *Context) (err error) { err = c.Before(context) if err != nil { ShowCommandHelp(context, c.Name) - HandleExitCoder(err) + context.App.handleExitCoder(err) return err } } @@ -210,7 +210,7 @@ func (c Command) Run(ctx *Context) (err error) { err = HandleAction(c.Action, context) if err != nil { - HandleExitCoder(err) + ctx.App.handleExitCoder(err) } return err } From 530df59178874f8d792d2d9cfd745464076f1eda Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Wed, 28 Jun 2017 09:52:12 -0700 Subject: [PATCH 341/381] Pass context into handleExitCoder --- app.go | 18 +++++++++--------- command.go | 8 ++++---- funcs.go | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app.go b/app.go index e9ed7ab..48de351 100644 --- a/app.go +++ b/app.go @@ -210,7 +210,7 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) - a.handleExitCoder(err) + a.handleExitCoder(context, err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -245,7 +245,7 @@ func (a *App) Run(arguments []string) (err error) { if beforeErr != nil { fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) ShowAppHelp(context) - a.handleExitCoder(beforeErr) + a.handleExitCoder(context, beforeErr) err = beforeErr return err } @@ -267,7 +267,7 @@ func (a *App) Run(arguments []string) (err error) { // Run default Action err = HandleAction(a.Action, context) - a.handleExitCoder(err) + a.handleExitCoder(context, err) return err } @@ -334,7 +334,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - a.handleExitCoder(err) + a.handleExitCoder(context, err) return err } fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -356,7 +356,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { defer func() { afterErr := a.After(context) if afterErr != nil { - a.handleExitCoder(err) + a.handleExitCoder(context, err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -369,7 +369,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - a.handleExitCoder(beforeErr) + a.handleExitCoder(context, beforeErr) err = beforeErr return err } @@ -387,7 +387,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // Run default Action err = HandleAction(a.Action, context) - a.handleExitCoder(err) + a.handleExitCoder(context, err) return err } @@ -468,9 +468,9 @@ func (a *App) appendFlag(flag Flag) { } } -func (a *App) handleExitCoder(err error) { +func (a *App) handleExitCoder(context *Context, error) { if a.ExitErrHandler != nil { - a.ExitErrHandler(err) + a.ExitErrHandler(context, err) } else { HandleExitCoder(err) } diff --git a/command.go b/command.go index fffa02f..502fc9f 100644 --- a/command.go +++ b/command.go @@ -167,7 +167,7 @@ func (c Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { err := c.OnUsageError(context, err, false) - context.App.handleExitCoder(err) + context.App.handleExitCoder(context, err) return err } fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) @@ -184,7 +184,7 @@ func (c Command) Run(ctx *Context) (err error) { defer func() { afterErr := c.After(context) if afterErr != nil { - ctx.App.handleExitCoder(err) + context.App.handleExitCoder(context, err) if err != nil { err = NewMultiError(err, afterErr) } else { @@ -198,7 +198,7 @@ func (c Command) Run(ctx *Context) (err error) { err = c.Before(context) if err != nil { ShowCommandHelp(context, c.Name) - context.App.handleExitCoder(err) + context.App.handleExitCoder(context, err) return err } } @@ -210,7 +210,7 @@ func (c Command) Run(ctx *Context) (err error) { err = HandleAction(c.Action, context) if err != nil { - ctx.App.handleExitCoder(err) + context.App.handleExitCoder(context, err) } return err } diff --git a/funcs.go b/funcs.go index 4737faf..42023e2 100644 --- a/funcs.go +++ b/funcs.go @@ -29,4 +29,4 @@ type FlagStringFunc func(Flag) string // ExitErrHandlerFunc is executed if provided in order to handle ExitError values // returned by Actions and Before/After functions. -type ExitErrHandlerFunc func(error) +type ExitErrHandlerFunc func(context *Context, error) From 172bb92059ed885c8b4249230f3ccbe9e3e1272b Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Wed, 28 Jun 2017 10:07:25 -0700 Subject: [PATCH 342/381] fix named parameter issue --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 48de351..1876b5c 100644 --- a/app.go +++ b/app.go @@ -468,7 +468,7 @@ func (a *App) appendFlag(flag Flag) { } } -func (a *App) handleExitCoder(context *Context, error) { +func (a *App) handleExitCoder(context *Context, err error) { if a.ExitErrHandler != nil { a.ExitErrHandler(context, err) } else { From 71bdf81f5a65dc253482cb727c2ae973ae3b3830 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Wed, 28 Jun 2017 10:10:11 -0700 Subject: [PATCH 343/381] sigh... fix one more named parameter issue --- funcs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funcs.go b/funcs.go index 42023e2..2274415 100644 --- a/funcs.go +++ b/funcs.go @@ -29,4 +29,4 @@ type FlagStringFunc func(Flag) string // ExitErrHandlerFunc is executed if provided in order to handle ExitError values // returned by Actions and Before/After functions. -type ExitErrHandlerFunc func(context *Context, error) +type ExitErrHandlerFunc func(context *Context, err error) From 58450552ee1bada60f4175897aff8d69f7c904a1 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Wed, 28 Jun 2017 12:52:50 -0700 Subject: [PATCH 344/381] Add Test --- app_test.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app_test.go b/app_test.go index e14ddaf..63e28c7 100644 --- a/app_test.go +++ b/app_test.go @@ -1661,6 +1661,42 @@ func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { } } +func TestHandleExitCoder_Default(t *testing.T) { + app := NewApp() + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + + ctx := NewContext(app, fs, nil) + app.handleExitCoder(ctx, errors.New("Default Behavior Error")) + + output := fakeErrWriter.String() + if !strings.Contains(output, "Default") { + t.Fatalf("Expected Default Behavior from Error Handler but got: %s", output) + } +} + +func TestHandleExitCoder_Custom(t *testing.T) { + app := NewApp() + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + + app.ExitErrHandler = func(_ *Context, _ error) { + fmt.Fprintln(ErrWriter, "I'm a Custom error handler, I print what I want!") + } + + ctx := NewContext(app, fs, nil) + app.handleExitCoder(ctx, errors.New("Default Behavior Error")) + + output := fakeErrWriter.String() + if !strings.Contains(output, "Custom") { + t.Fatalf("Expected Custom Behavior from Error Handler but got: %s", output) + } +} + func TestHandleAction_WithUnknownPanic(t *testing.T) { defer func() { refute(t, recover(), nil) }() From 5d528e2052b3e7a49293d6aa0fac245047ea61e3 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Wed, 28 Jun 2017 13:04:09 -0700 Subject: [PATCH 345/381] use exit errors in uts --- app_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app_test.go b/app_test.go index 63e28c7..9ff6246 100644 --- a/app_test.go +++ b/app_test.go @@ -1669,7 +1669,7 @@ func TestHandleExitCoder_Default(t *testing.T) { } ctx := NewContext(app, fs, nil) - app.handleExitCoder(ctx, errors.New("Default Behavior Error")) + app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42)) output := fakeErrWriter.String() if !strings.Contains(output, "Default") { @@ -1689,7 +1689,7 @@ func TestHandleExitCoder_Custom(t *testing.T) { } ctx := NewContext(app, fs, nil) - app.handleExitCoder(ctx, errors.New("Default Behavior Error")) + app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42)) output := fakeErrWriter.String() if !strings.Contains(output, "Custom") { From a6dd54e94bbb0c70ec72a144d24e24e2247b3b2e Mon Sep 17 00:00:00 2001 From: Nicolas Gailly Date: Thu, 6 Jul 2017 16:20:09 +0200 Subject: [PATCH 346/381] make the basic example build-able. --- cli.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli.go b/cli.go index 74fd101..90c07eb 100644 --- a/cli.go +++ b/cli.go @@ -12,6 +12,7 @@ // app.Usage = "say a greeting" // app.Action = func(c *cli.Context) error { // println("Greetings") +// return nil // } // // app.Run(os.Args) From 8164aa88ca94f405d0c73ddca4db3a57424bcf69 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 3 Aug 2017 15:07:56 -0400 Subject: [PATCH 347/381] Update travis config to only test on 1.8.x, and ensure we route to Ubuntu Trusty and a recent macOS image. --- .travis.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 890e185..1ee4604 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,18 @@ language: go - sudo: false +dist: trusty cache: directories: - node_modules -go: -- 1.6.x -- 1.7.x -- 1.8.x -- master +go: 1.8.x matrix: - allow_failures: - - go: master include: - - go: 1.6.x - os: osx - - go: 1.7.x - os: osx - go: 1.8.x os: osx + osx_image: xcode8.3 before_script: - go get github.com/urfave/gfmrun/... || true From 184ffb5e573ba0656a123462715d360faaa0c897 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 3 Aug 2017 15:38:19 -0400 Subject: [PATCH 348/381] Use `os` matrix instead of explicit matrix.include --- .travis.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ee4604..cf8d098 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,17 @@ language: go sudo: false dist: trusty +osx_image: xcode8.3 +go: 1.8.x + +os: +- linux +- osx cache: directories: - node_modules -go: 1.8.x - -matrix: - include: - - go: 1.8.x - os: osx - osx_image: xcode8.3 - before_script: - go get github.com/urfave/gfmrun/... || true - go get golang.org/x/tools/cmd/goimports From f59114b4100b20e092035199640f5fe8bce07daa Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 3 Aug 2017 22:59:38 -0400 Subject: [PATCH 349/381] Update os, image, golang, and python versions used in appveyor --- appveyor.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 698b188..1e1489c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,14 +1,16 @@ version: "{build}" -os: Windows Server 2012 R2 +os: Windows Server 2016 + +image: Visual Studio 2017 clone_folder: c:\gopath\src\github.com\urfave\cli environment: GOPATH: C:\gopath - GOVERSION: 1.6 - PYTHON: C:\Python27-x64 - PYTHON_VERSION: 2.7.x + GOVERSION: 1.8.x + PYTHON: C:\Python36-x64 + PYTHON_VERSION: 3.6.x PYTHON_ARCH: 64 install: From e1fa109a3195a9fedcb635841ca1907b764ada1f Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Thu, 10 Aug 2017 17:54:24 -0700 Subject: [PATCH 350/381] Define flag source precedence in README Fixes #646 --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 2bbbd8e..883cc10 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ applications in an expressive way. + [Ordering](#ordering) + [Values from the Environment](#values-from-the-environment) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + + [Precedence](#precedence) * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) * [Exit code](#exit-code) @@ -656,6 +657,15 @@ func main() { } ``` +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag + ### Subcommands Subcommands can be defined for a more git-like command line app. From cfb38830724cc34fedffe9a2a29fb54fa9169cd1 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Thu, 10 Aug 2017 18:42:03 -0700 Subject: [PATCH 351/381] Prepare CHANGELOG for v1.20.0 release --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f7546..401eae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,49 @@ ## [Unreleased] +## 1.20.0 - 2017-08-10 + +### Fixed + +* `HandleExitCoder` is now correctly iterates over all errors in + a `MultiError`. The exit code is the exit code of the last error or `1` if + there are no `ExitCoder`s in the `MultiError`. +* Fixed YAML file loading on Windows (previously would fail validate the file path) +* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly + propogated +* `ErrWriter` is now passed downwards through command structure to avoid the + need to redefine it +* Pass `Command` context into `OnUsageError` rather than parent context so that + all fields are avaiable +* Errors occuring in `Before` funcs are no longer double printed +* Use `UsageText` in the help templates for commands and subcommands if + defined; otherwise build the usage as before (was previously ignoring this + field) +* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if + a program calls `Set` or `GlobalSet` directly after flag parsing (would + previously only return `true` if the flag was set during parsing) + +### Changed + +* No longer exit the program on command/subcommand error if the error raised is + not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was + determined to be a regression in functionality. See [the + PR](https://github.com/urfave/cli/pull/595) for discussion. + +### Added + +* `CommandsByName` type was added to make it easy to sort `Command`s by name, + alphabetically +* `altsrc` now handles loading of string and int arrays from TOML +* Support for definition of custom help templates for `App` via + `CustomAppHelpTemplate` +* Support for arbitrary key/value fields on `App` to be used with + `CustomAppHelpTemplate` via `ExtraInfo` +* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be + `cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag` + interface to be used. + + ## [1.19.1] - 2016-11-21 ### Fixed From 1d334f10ce73c2b9e65c50a2290a86be3c743ff2 Mon Sep 17 00:00:00 2001 From: "Alan D. Cabrera" Date: Fri, 8 Sep 2017 10:37:48 -0700 Subject: [PATCH 352/381] Add newline before command categories The simple formatting change adds a nice blank line before each command category. Documentation in README.md is also updated to be more accurate. --- README.md | 4 ++-- help.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 883cc10..34055fe 100644 --- a/README.md +++ b/README.md @@ -761,11 +761,11 @@ func main() { }, { Name: "add", - Category: "template", + Category: "Template actions", }, { Name: "remove", - Category: "template", + Category: "Template actions", }, } diff --git a/help.go b/help.go index 57ec98d..ed084fc 100644 --- a/help.go +++ b/help.go @@ -29,6 +29,7 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} From 11d45572f9727acfbc93daa8565f379d396125d6 Mon Sep 17 00:00:00 2001 From: rliebz Date: Sat, 26 Aug 2017 07:42:25 -0400 Subject: [PATCH 353/381] Export funcs to configure flag prefix/env hints This will allow users to customize the prefix section or env hint section of the flag entries in the help menu without having to reimplement the rest of the logic required in defining FlagStringer. --- flag.go | 20 ++++++++++++++------ funcs.go | 8 ++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/flag.go b/flag.go index 877ff35..b17f5b9 100644 --- a/flag.go +++ b/flag.go @@ -37,6 +37,14 @@ var HelpFlag Flag = BoolFlag{ // to display a flag. var FlagStringer FlagStringFunc = stringifyFlag +// FlagNamePrefixer converts a full flag name and its placeholder into the help +// message flag prefix. This is used by the default FlagStringer. +var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames + +// FlagEnvHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagEnvHinter FlagEnvHintFunc = withEnvHint + // FlagsByName is a slice of Flag. type FlagsByName []Flag @@ -710,13 +718,13 @@ func stringifyFlag(f Flag) string { switch f.(type) { case IntSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return FlagEnvHinter(fv.FieldByName("EnvVar").String(), stringifyIntSliceFlag(f.(IntSliceFlag))) case Int64SliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return FlagEnvHinter(fv.FieldByName("EnvVar").String(), stringifyInt64SliceFlag(f.(Int64SliceFlag))) case StringSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return FlagEnvHinter(fv.FieldByName("EnvVar").String(), stringifyStringSliceFlag(f.(StringSliceFlag))) } @@ -744,8 +752,8 @@ func stringifyFlag(f Flag) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) - return withEnvHint(fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) + return FlagEnvHinter(fv.FieldByName("EnvVar").String(), + fmt.Sprintf("%s\t%s", FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) } func stringifyIntSliceFlag(f IntSliceFlag) string { @@ -795,5 +803,5 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) + return fmt.Sprintf("%s\t%s", FlagNamePrefixer(name, placeholder), usageWithDefault) } diff --git a/funcs.go b/funcs.go index cba5e6c..3ad3c6d 100644 --- a/funcs.go +++ b/funcs.go @@ -26,3 +26,11 @@ type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error // FlagStringFunc is used by the help generation to display a flag, which is // expected to be a single line. type FlagStringFunc func(Flag) string + +// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix +// text for a flag's full name. +type FlagNamePrefixFunc func(fullName, placeholder string) string + +// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help +// with the environment variable details. +type FlagEnvHintFunc func(envVar, str string) string From cbbe4c1a2c34e52c8ad0937c01c9c15ef407a6d5 Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Mon, 18 Sep 2017 00:44:42 -0400 Subject: [PATCH 354/381] Add tests for custom flag prefix/env hints --- flag_test.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/flag_test.go b/flag_test.go index 1ccb639..bc840b5 100644 --- a/flag_test.go +++ b/flag_test.go @@ -158,6 +158,83 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { } } +var prefixStringFlagTests = []struct { + name string + usage string + value string + prefixer FlagNamePrefixFunc + expected string +}{ + {"foo", "", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: foo, ph: value\t"}, + {"f", "", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: f, ph: value\t"}, + {"f", "The total `foo` desired", "all", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: f, ph: foo\tThe total foo desired (default: \"all\")"}, + {"test", "", "Something", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: test, ph: value\t(default: \"Something\")"}, + {"config,c", "Load configuration from `FILE`", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: config,c, ph: FILE\tLoad configuration from FILE"}, + {"config,c", "Load configuration from `CONFIG`", "config.json", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, +} + +func TestFlagNamePrefixer(t *testing.T) { + defer func() { + FlagNamePrefixer = prefixedNames + }() + + for _, test := range prefixStringFlagTests { + FlagNamePrefixer = test.prefixer + flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} + output := flag.String() + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +var envHintFlagTests = []struct { + name string + env string + hinter FlagEnvHintFunc + expected string +}{ + {"foo", "", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: , str: --foo value\t"}, + {"f", "", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: , str: -f value\t"}, + {"foo", "ENV_VAR", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: ENV_VAR, str: --foo value\t"}, + {"f", "ENV_VAR", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: ENV_VAR, str: -f value\t"}, +} + +func TestFlagEnvHinter(t *testing.T) { + defer func() { + FlagEnvHinter = withEnvHint + }() + + for _, test := range envHintFlagTests { + FlagEnvHinter = test.hinter + flag := StringFlag{Name: test.name, EnvVar: test.env} + output := flag.String() + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + var stringSliceFlagTests = []struct { name string value *StringSlice From 67ee172e6da2cdad8e48af107eef0fbfd1e85eec Mon Sep 17 00:00:00 2001 From: Sebastian Sprenger Date: Fri, 6 Oct 2017 07:28:18 +0200 Subject: [PATCH 355/381] fix misspelling issue --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index db94191..012b9b5 100644 --- a/context.go +++ b/context.go @@ -73,7 +73,7 @@ func (c *Context) IsSet(name string) bool { // change in version 2 to add `IsSet` to the Flag interface to push the // responsibility closer to where the information required to determine // whether a flag is set by non-standard means such as environment - // variables is avaliable. + // variables is available. // // See https://github.com/urfave/cli/issues/294 for additional discussion flags := c.Command.Flags From c3cc74dac756e33c2919ab998481809e8720e068 Mon Sep 17 00:00:00 2001 From: Sebastian Sprenger Date: Fri, 6 Oct 2017 07:28:43 +0200 Subject: [PATCH 356/381] fix ineffective assigns --- app_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/app_test.go b/app_test.go index e14ddaf..54e0951 100644 --- a/app_test.go +++ b/app_test.go @@ -497,7 +497,6 @@ func TestApp_Float64Flag(t *testing.T) { } func TestApp_ParseSliceFlags(t *testing.T) { - var parsedOption, firstArg string var parsedIntSlice []int var parsedStringSlice []string @@ -511,8 +510,6 @@ func TestApp_ParseSliceFlags(t *testing.T) { Action: func(c *Context) error { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") - parsedOption = c.String("option") - firstArg = c.Args().First() return nil }, } From c202606a17a763fcc1b320cac6cf584662e31364 Mon Sep 17 00:00:00 2001 From: Sebastian Sprenger Date: Fri, 6 Oct 2017 07:29:13 +0200 Subject: [PATCH 357/381] fix golint issues --- altsrc/map_input_source.go | 14 +++++++------- altsrc/toml_file_loader.go | 4 ++-- altsrc/yaml_file_loader.go | 4 ++-- app.go | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index b3169e0..a111eee 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -22,15 +22,15 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool if sections := strings.Split(name, "."); len(sections) > 1 { node := tree for _, section := range sections[:len(sections)-1] { - if child, ok := node[section]; !ok { + child, ok := node[section] + if !ok { return nil, false - } else { - if ctype, ok := child.(map[interface{}]interface{}); !ok { - return nil, false - } else { - node = ctype - } } + ctype, ok := child.(map[interface{}]interface{}) + if !ok { + return nil, false + } + node = ctype } if val, ok := node[sections[len(sections)-1]]; ok { return val, true diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 37870fc..87bb9d9 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -66,9 +66,9 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { return ret, nil } -func (self *tomlMap) UnmarshalTOML(i interface{}) error { +func (tm *tomlMap) UnmarshalTOML(i interface{}) error { if tmp, err := unmarshalMap(i); err == nil { - self.Map = tmp + tm.Map = tmp } else { return err } diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index dd808d5..202469f 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -86,7 +86,7 @@ func loadDataFrom(filePath string) ([]byte, error) { return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) } return ioutil.ReadFile(filePath) - } else { - return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) } + + return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) } diff --git a/app.go b/app.go index 51fc45d..5991226 100644 --- a/app.go +++ b/app.go @@ -491,7 +491,7 @@ func HandleAction(action interface{}, context *Context) (err error) { } else if a, ok := action.(func(*Context)); ok { // deprecated function signature a(context) return nil - } else { - return errInvalidActionType } + + return errInvalidActionType } From b44660ac3da2f8e651372c40ae803782bddea283 Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Sat, 28 Oct 2017 03:00:11 -0400 Subject: [PATCH 358/381] Consider case when sorting strings This makes sorting flags and other sections consistent with how most command line tools function, by placing both flags `-A` and `-a` before a flag `-B`. --- category.go | 2 +- command.go | 2 +- flag.go | 2 +- sort.go | 29 +++++++++++++++++++++++++++++ sort_test.go | 30 ++++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 sort.go create mode 100644 sort_test.go diff --git a/category.go b/category.go index 1a60550..bf3c73c 100644 --- a/category.go +++ b/category.go @@ -10,7 +10,7 @@ type CommandCategory struct { } func (c CommandCategories) Less(i, j int) bool { - return c[i].Name < c[j].Name + return lexicographicLess(c[i].Name, c[j].Name) } func (c CommandCategories) Len() int { diff --git a/command.go b/command.go index 502fc9f..be8f8f0 100644 --- a/command.go +++ b/command.go @@ -73,7 +73,7 @@ func (c CommandsByName) Len() int { } func (c CommandsByName) Less(i, j int) bool { - return c[i].Name < c[j].Name + return lexicographicLess(c[i].Name, c[j].Name) } func (c CommandsByName) Swap(i, j int) { diff --git a/flag.go b/flag.go index b17f5b9..53fb8bb 100644 --- a/flag.go +++ b/flag.go @@ -53,7 +53,7 @@ func (f FlagsByName) Len() int { } func (f FlagsByName) Less(i, j int) bool { - return f[i].GetName() < f[j].GetName() + return lexicographicLess(f[i].GetName(), f[j].GetName()) } func (f FlagsByName) Swap(i, j int) { diff --git a/sort.go b/sort.go new file mode 100644 index 0000000..23d1c2f --- /dev/null +++ b/sort.go @@ -0,0 +1,29 @@ +package cli + +import "unicode" + +// lexicographicLess compares strings alphabetically considering case. +func lexicographicLess(i, j string) bool { + iRunes := []rune(i) + jRunes := []rune(j) + + lenShared := len(iRunes) + if lenShared > len(jRunes) { + lenShared = len(jRunes) + } + + for index := 0; index < lenShared; index++ { + ir := iRunes[index] + jr := jRunes[index] + + if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr { + return lir < ljr + } + + if ir != jr { + return ir < jr + } + } + + return i < j +} diff --git a/sort_test.go b/sort_test.go new file mode 100644 index 0000000..662ef9b --- /dev/null +++ b/sort_test.go @@ -0,0 +1,30 @@ +package cli + +import "testing" + +var lexicographicLessTests = []struct { + i string + j string + expected bool +}{ + {"", "a", true}, + {"a", "", false}, + {"a", "a", false}, + {"a", "A", false}, + {"A", "a", true}, + {"aa", "a", false}, + {"a", "aa", true}, + {"a", "b", true}, + {"a", "B", true}, + {"A", "b", true}, + {"A", "B", true}, +} + +func TestLexicographicLess(t *testing.T) { + for _, test := range lexicographicLessTests { + actual := lexicographicLess(test.i, test.j) + if test.expected != actual { + t.Errorf(`expected string "%s" to come before "%s"`, test.i, test.j) + } + } +} From 21fcab0dee7dab6969e929cf1740306bae1e16ad Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Fri, 31 Mar 2017 16:24:15 +0900 Subject: [PATCH 359/381] ability to load variable from file --- context.go | 29 +++-- flag.go | 251 ++++++++++++++++---------------------------- flag_generated.go | 53 ++++++---- flag_test.go | 20 ++++ generate-flag-types | 1 + 5 files changed, 166 insertions(+), 188 deletions(-) diff --git a/context.go b/context.go index 012b9b5..552ee74 100644 --- a/context.go +++ b/context.go @@ -3,6 +3,7 @@ package cli import ( "errors" "flag" + "os" "reflect" "strings" "syscall" @@ -93,18 +94,26 @@ func (c *Context) IsSet(name string) bool { val = val.Elem() } - envVarValue := val.FieldByName("EnvVar") - if !envVarValue.IsValid() { - return + filePathValue := val.FieldByName("FilePath") + if filePathValue.IsValid() { + eachName(filePathValue.String(), func(filePath string) { + if _, err := os.Stat(filePath); err == nil { + c.setFlags[name] = true + return + } + }) } - eachName(envVarValue.String(), func(envVar string) { - envVar = strings.TrimSpace(envVar) - if _, ok := syscall.Getenv(envVar); ok { - c.setFlags[name] = true - return - } - }) + envVarValue := val.FieldByName("EnvVar") + if envVarValue.IsValid() { + eachName(envVarValue.String(), func(envVar string) { + envVar = strings.TrimSpace(envVar) + if _, ok := syscall.Getenv(envVar); ok { + c.setFlags[name] = true + return + } + }) + } }) } } diff --git a/flag.go b/flag.go index 53fb8bb..4042093 100644 --- a/flag.go +++ b/flag.go @@ -3,6 +3,7 @@ package cli import ( "flag" "fmt" + "io/ioutil" "reflect" "runtime" "strconv" @@ -120,15 +121,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { // provided by the user for parsing by the flag func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { val := f.Value - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if err := val.Set(envVal); err != nil { - return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) - } - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if err := val.Set(envVal); err != nil { + return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) } } @@ -171,21 +166,15 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &StringSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) } } + f.Value = newVal } eachName(f.Name, func(name string) { @@ -234,21 +223,15 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &IntSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) } } + f.Value = newVal } eachName(f.Name, func(name string) { @@ -297,21 +280,15 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &Int64Slice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &Int64Slice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) } } + f.Value = newVal } eachName(f.Name, func(name string) { @@ -332,23 +309,15 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { val := false - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - val = false - break - } - - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - - val = envValBool - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if envVal == "" { + val = false + } else { + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) } + val = envValBool } } @@ -372,23 +341,16 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { val := true - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - val = false - break - } - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - - val = envValBool - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if envVal == "" { + val = false + } else { + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) } + val = envValBool } } @@ -411,14 +373,8 @@ func (f StringFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - f.Value = envVal - break - } - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + f.Value = envVal } eachName(f.Name, func(name string) { @@ -440,18 +396,12 @@ func (f IntFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } - f.Value = int(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) } + f.Value = int(envValInt) } eachName(f.Name, func(name string) { @@ -473,19 +423,13 @@ func (f Int64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValInt - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) } + + f.Value = envValInt } eachName(f.Name, func(name string) { @@ -507,19 +451,13 @@ func (f UintFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) } + + f.Value = uint(envValInt) } eachName(f.Name, func(name string) { @@ -541,19 +479,13 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint64(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) } + + f.Value = uint64(envValInt) } eachName(f.Name, func(name string) { @@ -575,19 +507,13 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValDuration, err := time.ParseDuration(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValDuration - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValDuration, err := time.ParseDuration(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) } + + f.Value = envValDuration } eachName(f.Name, func(name string) { @@ -609,19 +535,13 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValFloat, err := strconv.ParseFloat(envVal, 10) - if err != nil { - return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = float64(envValFloat) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err != nil { + return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) } + + f.Value = float64(envValFloat) } eachName(f.Name, func(name string) { @@ -805,3 +725,18 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) return fmt.Sprintf("%s\t%s", FlagNamePrefixer(name, placeholder), usageWithDefault) } + +func flagFromFileEnv(filePath, envName string) (val string, ok bool) { + for _, envVar := range strings.Split(envName, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + return envVal, true + } + } + if filePath != "" { + if data, err := ioutil.ReadFile(filePath); err == nil { + return string(data), true + } + } + return +} diff --git a/flag_generated.go b/flag_generated.go index 491b619..001576c 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -13,6 +13,7 @@ type BoolFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Destination *bool } @@ -60,6 +61,7 @@ type BoolTFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Destination *bool } @@ -107,6 +109,7 @@ type DurationFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value time.Duration Destination *time.Duration @@ -155,6 +158,7 @@ type Float64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value float64 Destination *float64 @@ -200,11 +204,12 @@ func lookupFloat64(name string, set *flag.FlagSet) float64 { // GenericFlag is a flag with type Generic type GenericFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value Generic + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value Generic } // String returns a readable representation of this value @@ -250,6 +255,7 @@ type Int64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value int64 Destination *int64 @@ -298,6 +304,7 @@ type IntFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value int Destination *int @@ -343,11 +350,12 @@ func lookupInt(name string, set *flag.FlagSet) int { // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *IntSlice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *IntSlice } // String returns a readable representation of this value @@ -390,11 +398,12 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int { // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *Int64Slice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *Int64Slice } // String returns a readable representation of this value @@ -440,6 +449,7 @@ type StringFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value string Destination *string @@ -485,11 +495,12 @@ func lookupString(name string, set *flag.FlagSet) string { // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *StringSlice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *StringSlice } // String returns a readable representation of this value @@ -535,6 +546,7 @@ type Uint64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value uint64 Destination *uint64 @@ -583,6 +595,7 @@ type UintFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value uint Destination *uint diff --git a/flag_test.go b/flag_test.go index bc840b5..66836f3 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1290,3 +1290,23 @@ func TestParseGenericFromEnvCascade(t *testing.T) { } a.Run([]string{"run"}) } + +// func TestFlagFromFile(t *testing.T) { +// temp, err := ioutil.TempFile("", "urfave_cli_test") +// if err != nil { +// t.Error(err) +// return +// } +// io.WriteString(temp, "abc") +// temp.Close() +// defer func() { +// os.Remove(temp.Name()) +// }() +// +// if want, got := flagFromFileEnv("file-does-not-exist", "123"), "123"; want != got { +// t.Errorf("Did not expect %v - Got %v", want, got) +// } +// if want, got := flagFromFile(temp.Name(), "123"), "abc"; want != got { +// t.Errorf("Did not expect %v - Got %v", want, got) +// } +// } diff --git a/generate-flag-types b/generate-flag-types index 7147381..1358857 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -142,6 +142,7 @@ def _write_cli_flag_types(outfile, types): Name string Usage string EnvVar string + FilePath string Hidden bool """.format(**typedef)) From c698b821b896e9723d53c4ad1e81680f39a8cdc1 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Sat, 1 Apr 2017 12:37:06 +0900 Subject: [PATCH 360/381] unit tests for load from file --- flag_test.go | 55 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/flag_test.go b/flag_test.go index 66836f3..98c2fb9 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2,6 +2,8 @@ package cli import ( "fmt" + "io" + "io/ioutil" "os" "reflect" "regexp" @@ -1291,22 +1293,37 @@ func TestParseGenericFromEnvCascade(t *testing.T) { a.Run([]string{"run"}) } -// func TestFlagFromFile(t *testing.T) { -// temp, err := ioutil.TempFile("", "urfave_cli_test") -// if err != nil { -// t.Error(err) -// return -// } -// io.WriteString(temp, "abc") -// temp.Close() -// defer func() { -// os.Remove(temp.Name()) -// }() -// -// if want, got := flagFromFileEnv("file-does-not-exist", "123"), "123"; want != got { -// t.Errorf("Did not expect %v - Got %v", want, got) -// } -// if want, got := flagFromFile(temp.Name(), "123"), "abc"; want != got { -// t.Errorf("Did not expect %v - Got %v", want, got) -// } -// } +func TestFlagFromFile(t *testing.T) { + os.Clearenv() + os.Setenv("APP_FOO", "123") + + temp, err := ioutil.TempFile("", "urfave_cli_test") + if err != nil { + t.Error(err) + return + } + io.WriteString(temp, "abc") + temp.Close() + defer func() { + os.Remove(temp.Name()) + }() + + var filePathTests = []struct { + path string + name string + expected string + }{ + {"file-does-not-exist", "APP_BAR", ""}, + {"file-does-not-exist", "APP_FOO", "123"}, + {"file-does-not-exist", "APP_FOO,APP_BAR", "123"}, + {temp.Name(), "APP_FOO", "123"}, + {temp.Name(), "APP_BAR", "abc"}, + } + + for _, filePathTest := range filePathTests { + got, _ := flagFromFileEnv(filePathTest.path, filePathTest.name) + if want := filePathTest.expected; got != want { + t.Errorf("Did not expect %v - Want %v", got, want) + } + } +} From 4cc453ba6792515a8013340f8919e6c4b44851b7 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Sat, 1 Apr 2017 12:55:46 +0900 Subject: [PATCH 361/381] document field in README --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 34055fe..aa8f2cb 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ applications in an expressive way. + [Alternate Names](#alternate-names) + [Ordering](#ordering) + [Values from the Environment](#values-from-the-environment) + + [Values from files](#values-from-files) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + [Precedence](#precedence) * [Subcommands](#subcommands) @@ -587,6 +588,38 @@ func main() { } ``` +#### Values from Files + +You can also have the default value set from file via `FilePath`. e.g. + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "password, p", + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + } + + app.Run(os.Args) +} +``` + #### Values from alternate input sources (YAML, TOML, and others) There is a separate package altsrc that adds support for getting flag values From 18a556e1927fbe11c31fae47a7e3acf275ef6ae4 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Mon, 10 Apr 2017 16:45:51 +0200 Subject: [PATCH 362/381] fix FilePath documentation in README.md --- README.md | 7 ++++-- flag.go | 62 +++++++++++++++++++++++++++++++++++++++++----------- flag_test.go | 2 +- funcs.go | 3 +++ 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index aa8f2cb..a2fd41d 100644 --- a/README.md +++ b/README.md @@ -588,13 +588,13 @@ func main() { } ``` -#### Values from Files +#### Values from files You can also have the default value set from file via `FilePath`. e.g. ``` go package main @@ -620,6 +620,9 @@ func main() { } ``` +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the enviornment (e.g. `EnvVar`). + #### Values from alternate input sources (YAML, TOML, and others) There is a separate package altsrc that adds support for getting flag values diff --git a/flag.go b/flag.go index 4042093..68942ae 100644 --- a/flag.go +++ b/flag.go @@ -46,6 +46,10 @@ var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames // details. This is used by the default FlagStringer. var FlagEnvHinter FlagEnvHintFunc = withEnvHint +// FlagFileHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagFileHinter FlagFileHintFunc = withFileHint + // FlagsByName is a slice of Flag. type FlagsByName []Flag @@ -625,6 +629,14 @@ func withEnvHint(envVar, str string) string { return str + envText } +func withFileHint(filePath, str string) string { + fileText := "" + if filePath != "" { + fileText = fmt.Sprintf(" [%s]", filePath) + } + return str + fileText +} + func flagValue(f Flag) reflect.Value { fv := reflect.ValueOf(f) for fv.Kind() == reflect.Ptr { @@ -638,14 +650,33 @@ func stringifyFlag(f Flag) string { switch f.(type) { case IntSliceFlag: - return FlagEnvHinter(fv.FieldByName("EnvVar").String(), - stringifyIntSliceFlag(f.(IntSliceFlag))) + return FlagFileHinter( + fv.FieldByName("FilePath").String(), + FlagEnvHinter( + fv.FieldByName("EnvVar").String(), + stringifyIntSliceFlag(f.(IntSliceFlag)), + ), + ) case Int64SliceFlag: - return FlagEnvHinter(fv.FieldByName("EnvVar").String(), - stringifyInt64SliceFlag(f.(Int64SliceFlag))) + // return FlagEnvHinter(fv.FieldByName("EnvVar").String(), + // stringifyInt64SliceFlag(f.(Int64SliceFlag))) + return FlagFileHinter( + fv.FieldByName("FilePath").String(), + FlagEnvHinter( + fv.FieldByName("EnvVar").String(), + stringifyInt64SliceFlag(f.(Int64SliceFlag)), + ), + ) case StringSliceFlag: - return FlagEnvHinter(fv.FieldByName("EnvVar").String(), - stringifyStringSliceFlag(f.(StringSliceFlag))) + // return FlagEnvHinter(fv.FieldByName("EnvVar").String(), + // stringifyStringSliceFlag(f.(StringSliceFlag))) + return FlagFileHinter( + fv.FieldByName("FilePath").String(), + FlagEnvHinter( + fv.FieldByName("EnvVar").String(), + stringifyStringSliceFlag(f.(StringSliceFlag)), + ), + ) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) @@ -672,8 +703,13 @@ func stringifyFlag(f Flag) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) - return FlagEnvHinter(fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s\t%s", FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) + return FlagFileHinter( + fv.FieldByName("FilePath").String(), + FlagEnvHinter( + fv.FieldByName("EnvVar").String(), + fmt.Sprintf("%s\t%s", FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder), usageWithDefault), + ), + ) } func stringifyIntSliceFlag(f IntSliceFlag) string { @@ -727,16 +763,16 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { } func flagFromFileEnv(filePath, envName string) (val string, ok bool) { + if filePath != "" { + if data, err := ioutil.ReadFile(filePath); err == nil { + return string(data), true + } + } for _, envVar := range strings.Split(envName, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { return envVal, true } } - if filePath != "" { - if data, err := ioutil.ReadFile(filePath); err == nil { - return string(data), true - } - } return } diff --git a/flag_test.go b/flag_test.go index 98c2fb9..fb9163b 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1316,7 +1316,7 @@ func TestFlagFromFile(t *testing.T) { {"file-does-not-exist", "APP_BAR", ""}, {"file-does-not-exist", "APP_FOO", "123"}, {"file-does-not-exist", "APP_FOO,APP_BAR", "123"}, - {temp.Name(), "APP_FOO", "123"}, + {temp.Name(), "APP_FOO", "abc"}, {temp.Name(), "APP_BAR", "abc"}, } diff --git a/funcs.go b/funcs.go index b335dbf..0036b11 100644 --- a/funcs.go +++ b/funcs.go @@ -39,3 +39,6 @@ type FlagNamePrefixFunc func(fullName, placeholder string) string // with the environment variable details. type FlagEnvHintFunc func(envVar, str string) string +// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help +// with the file path details. +type FlagFileHintFunc func(filePath, str string) string From f971fca2b2664c4dec0cee24225dc3c415211498 Mon Sep 17 00:00:00 2001 From: Jacob McCann Date: Thu, 26 Oct 2017 13:08:03 -0500 Subject: [PATCH 363/381] Allow FilePath to take []string --- flag.go | 22 +++++++++------------- flag_test.go | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/flag.go b/flag.go index 68942ae..f8d4f51 100644 --- a/flag.go +++ b/flag.go @@ -125,9 +125,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { // provided by the user for parsing by the flag func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { val := f.Value - if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { - if err := val.Set(envVal); err != nil { - return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) + if fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if err := val.Set(fileEnvVal); err != nil { + return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err) } } @@ -658,8 +658,6 @@ func stringifyFlag(f Flag) string { ), ) case Int64SliceFlag: - // return FlagEnvHinter(fv.FieldByName("EnvVar").String(), - // stringifyInt64SliceFlag(f.(Int64SliceFlag))) return FlagFileHinter( fv.FieldByName("FilePath").String(), FlagEnvHinter( @@ -668,8 +666,6 @@ func stringifyFlag(f Flag) string { ), ) case StringSliceFlag: - // return FlagEnvHinter(fv.FieldByName("EnvVar").String(), - // stringifyStringSliceFlag(f.(StringSliceFlag))) return FlagFileHinter( fv.FieldByName("FilePath").String(), FlagEnvHinter( @@ -763,16 +759,16 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { } func flagFromFileEnv(filePath, envName string) (val string, ok bool) { - if filePath != "" { - if data, err := ioutil.ReadFile(filePath); err == nil { - return string(data), true - } - } for _, envVar := range strings.Split(envName, ",") { envVar = strings.TrimSpace(envVar) if envVal, ok := syscall.Getenv(envVar); ok { return envVal, true } } - return + for _, fileVar := range strings.Split(filePath, ",") { + if data, err := ioutil.ReadFile(fileVar); err == nil { + return string(data), true + } + } + return "", false } diff --git a/flag_test.go b/flag_test.go index fb9163b..98c2fb9 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1316,7 +1316,7 @@ func TestFlagFromFile(t *testing.T) { {"file-does-not-exist", "APP_BAR", ""}, {"file-does-not-exist", "APP_FOO", "123"}, {"file-does-not-exist", "APP_FOO,APP_BAR", "123"}, - {temp.Name(), "APP_FOO", "abc"}, + {temp.Name(), "APP_FOO", "123"}, {temp.Name(), "APP_BAR", "abc"}, } From 43c8c02cf5a10196e5a4c458fdbfee90a561e97c Mon Sep 17 00:00:00 2001 From: zhuchensong Date: Mon, 17 Apr 2017 00:47:04 +0800 Subject: [PATCH 364/381] Support POSIX-style short flag combining --- command.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index be8f8f0..189209d 100644 --- a/command.go +++ b/command.go @@ -142,7 +142,19 @@ func (c Command) Run(ctx *Context) (err error) { flagArgs = args[firstFlagIndex:] } - err = set.Parse(append(flagArgs, regularArgs...)) + // separate combined flags + var flagArgsSeparated []string + for _, flagArg := range flagArgs { + if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) >2 { + for _, flagChar := range flagArg[1:] { + flagArgsSeparated = append(flagArgsSeparated, "-" + string(flagChar)) + } + } else { + flagArgsSeparated = append(flagArgsSeparated, flagArg) + } + } + + err = set.Parse(append(flagArgsSeparated, regularArgs...)) } else { err = set.Parse(ctx.Args().Tail()) } From 9838c8bcaa19fdb33259f6e0f9740d9fd3cbe13c Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 11 Nov 2017 16:23:24 -0800 Subject: [PATCH 365/381] Update README examples to check for errors To encourage good practices. --- README.md | 138 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index a2fd41d..ca5c0cf 100644 --- a/README.md +++ b/README.md @@ -140,13 +140,17 @@ discovery. So a cli app can be as little as one line of code in `main()`. package main import ( + "log" "os" "github.com/urfave/cli" ) func main() { - cli.NewApp().Run(os.Args) + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -161,6 +165,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -175,7 +180,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -199,6 +207,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -213,7 +222,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -262,6 +274,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -275,7 +288,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -291,6 +307,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -320,7 +337,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -334,6 +354,7 @@ scanned. package main import ( + "log" "os" "fmt" @@ -367,7 +388,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -388,6 +412,7 @@ For example this: package main import ( + "log" "os" "github.com/urfave/cli" @@ -403,7 +428,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -429,6 +457,7 @@ list for the `Name`. e.g. package main import ( + "log" "os" "github.com/urfave/cli" @@ -445,7 +474,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -469,6 +501,7 @@ For example this: package main import ( + "log" "os" "sort" @@ -512,7 +545,10 @@ func main() { sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.CommandsByName(app.Commands)) - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -535,6 +571,7 @@ You can also have the default value set from the environment via `EnvVar`. e.g. package main import ( + "log" "os" "github.com/urfave/cli" @@ -552,7 +589,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -567,6 +607,7 @@ environment variable that resolves is used as the default. package main import ( + "log" "os" "github.com/urfave/cli" @@ -584,7 +625,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -600,6 +644,7 @@ You can also have the default value set from file via `FilePath`. e.g. package main import ( + "log" "os" "github.com/urfave/cli" @@ -616,7 +661,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -667,6 +715,7 @@ package notmain import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -689,7 +738,10 @@ func main() { app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) app.Flags = flags - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -715,6 +767,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -767,7 +820,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -783,6 +839,7 @@ E.g. package main import ( + "log" "os" "github.com/urfave/cli" @@ -805,7 +862,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -831,6 +891,7 @@ may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a package main import ( + "log" "os" "github.com/urfave/cli" @@ -851,7 +912,10 @@ func main() { return nil } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -871,6 +935,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -902,7 +967,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -942,6 +1010,7 @@ The default bash completion flag (`--generate-bash-completion`) is defined as package main import ( + "log" "os" "github.com/urfave/cli" @@ -960,7 +1029,10 @@ func main() { Name: "wat", }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -986,6 +1058,7 @@ package main import ( "fmt" + "log" "io" "os" @@ -1029,7 +1102,10 @@ VERSION: fmt.Println("Ha HA. I pwnd the help!!1") } - cli.NewApp().Run(os.Args) + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -1044,6 +1120,7 @@ setting `cli.HelpFlag`, e.g.: package main import ( + "log" "os" "github.com/urfave/cli" @@ -1056,7 +1133,10 @@ func main() { EnvVar: "SHOW_HALP,HALPPLZ", } - cli.NewApp().Run(os.Args) + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -1079,6 +1159,7 @@ setting `cli.VersionFlag`, e.g.: package main import ( + "log" "os" "github.com/urfave/cli" @@ -1093,7 +1174,10 @@ func main() { app := cli.NewApp() app.Name = "partay" app.Version = "19.99.0" - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -1108,6 +1192,7 @@ package main import ( "fmt" + "log" "os" "github.com/urfave/cli" @@ -1125,7 +1210,10 @@ func main() { app := cli.NewApp() app.Name = "partay" app.Version = "19.99.0" - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -1387,7 +1475,7 @@ func main() { ec := cli.NewExitError("ohwell", 86) fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) fmt.Printf("made it!\n") - return ec + return nil } if os.Getenv("HEXY") != "" { @@ -1401,7 +1489,9 @@ func main() { "whatever-values": 19.99, } - app.Run(os.Args) + + // ignore error so we don't exit non-zero and break gfmrun README example tests + _ = app.Run(os.Args) } func wopAction(c *cli.Context) error { From fd5382e7a539858cc19d7eed7755f7102bae5da9 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 13 Nov 2017 15:28:23 -0600 Subject: [PATCH 366/381] Combine bool short names Adds the ability to allow the combination of bool short-name options. For example, cmd foobar -ov This is done through a bool "UseShortOptionHandler" set in the command struct. Built upon PR #621 Signed-off-by: baude --- README.md | 21 +++++++++++++++++++++ command.go | 26 ++++++++++++++++---------- command_test.go | 36 ++++++++++++++++++++---------------- flag_test.go | 25 +++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index a2fd41d..6096701 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ applications in an expressive way. * [Version Flag](#version-flag) + [Customization](#customization-2) + [Full API Example](#full-api-example) + * [Combining short Bool options](#combining-short-bool-options) - [Contribution Guidelines](#contribution-guidelines) @@ -1410,6 +1411,26 @@ func wopAction(c *cli.Context) error { } ``` +### Combining short Bool options + +Traditional use of boolean options using their shortnames look like this: +``` +# cmd foobar -s -o +``` + +Suppose you want users to be able to combine your bool options with their shortname. This +can be done using the **UseShortOptionHandling** bool in your commands. Suppose your program +has a two bool flags such as *serve* and *option* with the short options of *-o* and +*-s* respectively. With **UseShortOptionHandling** set to *true*, a user can use a syntax +like: +``` +# cmd foobar -so +``` + +If you enable the **UseShortOptionHandling*, then you must not use any flags that have a single +leading *-* or this will result in failures. For example, **-option** can no longer be used. Flags +with two leading dashes (such as **--options**) are still valid. + ## Contribution Guidelines Feel free to put up a pull request to fix a bug or maybe add a feature. I will diff --git a/command.go b/command.go index 189209d..b559811 100644 --- a/command.go +++ b/command.go @@ -55,6 +55,10 @@ type Command struct { HideHelp bool // Boolean to hide this command from help or completion Hidden bool + // Boolean to enable short-option handling so user can combine several + // single-character bool arguements into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool // Full name of command for help, defaults to full command name, including parent commands. HelpName string @@ -141,20 +145,22 @@ func (c Command) Run(ctx *Context) (err error) { } else { flagArgs = args[firstFlagIndex:] } - // separate combined flags - var flagArgsSeparated []string - for _, flagArg := range flagArgs { - if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) >2 { - for _, flagChar := range flagArg[1:] { - flagArgsSeparated = append(flagArgsSeparated, "-" + string(flagChar)) + if c.UseShortOptionHandling { + var flagArgsSeparated []string + for _, flagArg := range flagArgs { + if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 { + for _, flagChar := range flagArg[1:] { + flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar)) + } + } else { + flagArgsSeparated = append(flagArgsSeparated, flagArg) } - } else { - flagArgsSeparated = append(flagArgsSeparated, flagArg) } + err = set.Parse(append(flagArgsSeparated, regularArgs...)) + } else { + err = set.Parse(append(flagArgs, regularArgs...)) } - - err = set.Parse(append(flagArgsSeparated, regularArgs...)) } else { err = set.Parse(ctx.Args().Tail()) } diff --git a/command_test.go b/command_test.go index 4ad994c..d9d7094 100644 --- a/command_test.go +++ b/command_test.go @@ -11,20 +11,23 @@ import ( func TestCommandFlagParsing(t *testing.T) { cases := []struct { - testArgs []string - skipFlagParsing bool - skipArgReorder bool - expectedErr error + testArgs []string + skipFlagParsing bool + skipArgReorder bool + expectedErr error + UseShortOptionHandling bool }{ // Test normal "not ignoring flags" flow - {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break")}, + {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break"), false}, // Test no arg reorder - {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil}, + {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false}, + + {[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags + {[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg + {[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg + {[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling - {[]string{"test-cmd", "blah", "blah"}, true, false, nil}, // Test SkipFlagParsing without any args that look like flags - {[]string{"test-cmd", "blah", "-break"}, true, false, nil}, // Test SkipFlagParsing with random flag arg - {[]string{"test-cmd", "blah", "-help"}, true, false, nil}, // Test SkipFlagParsing with "special" help flag arg } for _, c := range cases { @@ -36,13 +39,14 @@ func TestCommandFlagParsing(t *testing.T) { context := NewContext(app, set, nil) command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) error { return nil }, - SkipFlagParsing: c.skipFlagParsing, - SkipArgReorder: c.skipArgReorder, + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) error { return nil }, + SkipFlagParsing: c.skipFlagParsing, + SkipArgReorder: c.skipArgReorder, + UseShortOptionHandling: c.UseShortOptionHandling, } err := command.Run(context) diff --git a/flag_test.go b/flag_test.go index 98c2fb9..da9fd73 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1048,6 +1048,31 @@ func TestParseMultiBool(t *testing.T) { a.Run([]string{"run", "--serve"}) } +func TestParseBoolShortOptionHandle(t *testing.T) { + a := App{ + Commands: []Command{ + { + Name: "foobar", + UseShortOptionHandling: true, + Action: func(ctx *Context) error { + if ctx.Bool("serve") != true { + t.Errorf("main name not set") + } + if ctx.Bool("option") != true { + t.Errorf("short name not set") + } + return nil + }, + Flags: []Flag{ + BoolFlag{Name: "serve, s"}, + BoolFlag{Name: "option, o"}, + }, + }, + }, + } + a.Run([]string{"run", "foobar", "-so"}) +} + func TestParseDestinationBool(t *testing.T) { var dest bool a := App{ From 37b7abb1c491c8c3630a2a98bb02a7051efbcc06 Mon Sep 17 00:00:00 2001 From: Joshua Rubin Date: Tue, 21 Nov 2017 15:21:31 -0700 Subject: [PATCH 367/381] dont clobber slices with envvar Signed-off-by: Joshua Rubin --- flag.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/flag.go b/flag.go index f8d4f51..d4a4d41 100644 --- a/flag.go +++ b/flag.go @@ -178,7 +178,11 @@ func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) } } - f.Value = newVal + if f.Value == nil { + f.Value = newVal + } else { + *f.Value = *newVal + } } eachName(f.Name, func(name string) { @@ -235,7 +239,11 @@ func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) } } - f.Value = newVal + if f.Value == nil { + f.Value = newVal + } else { + *f.Value = *newVal + } } eachName(f.Name, func(name string) { @@ -292,7 +300,11 @@ func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) } } - f.Value = newVal + if f.Value == nil { + f.Value = newVal + } else { + *f.Value = *newVal + } } eachName(f.Name, func(name string) { From ceaac7c9152121e6ba0f3b492b3254d61346f92a Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 20 Nov 2017 09:32:03 -0600 Subject: [PATCH 368/381] Handle ShortOptions and SkipArgReorder There was a bug in parsing when both ShortOptions and SkipArgReorder were being used together. Signed-off-by: baude --- command.go | 129 ++++++++++++++++++++++++++++++++---------------- command_test.go | 1 + 2 files changed, 87 insertions(+), 43 deletions(-) diff --git a/command.go b/command.go index b559811..7d0357b 100644 --- a/command.go +++ b/command.go @@ -115,57 +115,29 @@ func (c Command) Run(ctx *Context) (err error) { return err } set.SetOutput(ioutil.Discard) - + firstFlagIndex, terminatorIndex := getIndexes(ctx) + flagArgs, regularArgs := getAllArgs(ctx.Args(), firstFlagIndex, terminatorIndex) + if c.UseShortOptionHandling { + flagArgs = translateShortOptions(flagArgs) + } if c.SkipFlagParsing { err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) } else if !c.SkipArgReorder { - firstFlagIndex := -1 - terminatorIndex := -1 - for index, arg := range ctx.Args() { - if arg == "--" { - terminatorIndex = index - break - } else if arg == "-" { - // Do nothing. A dash alone is not really a flag. - continue - } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { - firstFlagIndex = index - } - } - if firstFlagIndex > -1 { - args := ctx.Args() - regularArgs := make([]string, len(args[1:firstFlagIndex])) - copy(regularArgs, args[1:firstFlagIndex]) - - var flagArgs []string - if terminatorIndex > -1 { - flagArgs = args[firstFlagIndex:terminatorIndex] - regularArgs = append(regularArgs, args[terminatorIndex:]...) - } else { - flagArgs = args[firstFlagIndex:] - } - // separate combined flags - if c.UseShortOptionHandling { - var flagArgsSeparated []string - for _, flagArg := range flagArgs { - if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 { - for _, flagChar := range flagArg[1:] { - flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar)) - } - } else { - flagArgsSeparated = append(flagArgsSeparated, flagArg) - } - } - err = set.Parse(append(flagArgsSeparated, regularArgs...)) - } else { - err = set.Parse(append(flagArgs, regularArgs...)) - } + err = set.Parse(append(flagArgs, regularArgs...)) } else { err = set.Parse(ctx.Args().Tail()) } + } else if c.UseShortOptionHandling { + if terminatorIndex == -1 && firstFlagIndex > -1 { + // Handle shortname AND no options + err = set.Parse(append(regularArgs, flagArgs...)) + } else { + // Handle shortname and options + err = set.Parse(flagArgs) + } } else { - err = set.Parse(ctx.Args().Tail()) + err = set.Parse(append(regularArgs, flagArgs...)) } nerr := normalizeFlags(c.Flags, set) @@ -233,6 +205,77 @@ func (c Command) Run(ctx *Context) (err error) { return err } +func getIndexes(ctx *Context) (int, int) { + firstFlagIndex := -1 + terminatorIndex := -1 + for index, arg := range ctx.Args() { + if arg == "--" { + terminatorIndex = index + break + } else if arg == "-" { + // Do nothing. A dash alone is not really a flag. + continue + } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { + firstFlagIndex = index + } + } + if len(ctx.Args()) > 0 && !strings.HasPrefix(ctx.Args()[0], "-") && firstFlagIndex == -1 { + return -1, -1 + } + + return firstFlagIndex, terminatorIndex + +} + +// copyStringslice takes a string slice and copies it +func copyStringSlice(slice []string, start, end int) []string { + newSlice := make([]string, end-start) + copy(newSlice, slice[start:end]) + return newSlice +} + +// getAllArgs extracts and returns two string slices representing +// regularArgs and flagArgs +func getAllArgs(args []string, firstFlagIndex, terminatorIndex int) ([]string, []string) { + var regularArgs []string + // if there are no options, the we set the index to 1 manually + if firstFlagIndex == -1 { + firstFlagIndex = 1 + regularArgs = copyStringSlice(args, 0, len(args)) + } else { + regularArgs = copyStringSlice(args, 1, firstFlagIndex) + } + var flagArgs []string + // a flag terminatorIndex was found in the input. we need to collect + // flagArgs based on it. + if terminatorIndex > -1 { + flagArgs = copyStringSlice(args, firstFlagIndex, terminatorIndex) + additionalRegularArgs := copyStringSlice(args, terminatorIndex, len(args)) + regularArgs = append(regularArgs, additionalRegularArgs...) + for _, i := range additionalRegularArgs { + regularArgs = append(regularArgs, i) + } + } else { + flagArgs = args[firstFlagIndex:] + } + return flagArgs, regularArgs +} + +func translateShortOptions(flagArgs Args) []string { + // separate combined flags + var flagArgsSeparated []string + for _, flagArg := range flagArgs { + if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 { + for _, flagChar := range flagArg[1:] { + flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar)) + } + } else { + flagArgsSeparated = append(flagArgsSeparated, flagArg) + } + } + return flagArgsSeparated +} + // Names returns the names including short names and aliases. func (c Command) Names() []string { names := []string{c.Name} diff --git a/command_test.go b/command_test.go index d9d7094..e69750a 100644 --- a/command_test.go +++ b/command_test.go @@ -22,6 +22,7 @@ func TestCommandFlagParsing(t *testing.T) { // Test no arg reorder {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false}, + {[]string{"test-cmd", "blah", "blah", "-break", "ls", "-l"}, false, true, nil, true}, {[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags {[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg From c6eb2a051026c083d4e33591f8d6e95d5f4189dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Thu, 30 Nov 2017 19:43:12 -0500 Subject: [PATCH 369/381] Correct go vet for Go tip https://travis-ci.org/cloudflare/logshare/jobs/309796141#L646 --- app.go | 1 - 1 file changed, 1 deletion(-) diff --git a/app.go b/app.go index 60599b0..9add067 100644 --- a/app.go +++ b/app.go @@ -453,7 +453,6 @@ func (a *App) hasFlag(flag Flag) bool { } func (a *App) errWriter() io.Writer { - // When the app ErrWriter is nil use the package level one. if a.ErrWriter == nil { return ErrWriter From df562bf1a8626f2d16f91fcbf7230a5bdca3d592 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 3 Dec 2017 13:38:50 -0800 Subject: [PATCH 370/381] Refactor flag handling logic Refactor logic introduced by #686 --- command.go | 134 +++++++++++++++++++++-------------------------------- 1 file changed, 52 insertions(+), 82 deletions(-) diff --git a/command.go b/command.go index 7d0357b..66a58b5 100644 --- a/command.go +++ b/command.go @@ -1,6 +1,7 @@ package cli import ( + "flag" "fmt" "io/ioutil" "sort" @@ -110,43 +111,7 @@ func (c Command) Run(ctx *Context) (err error) { ) } - set, err := flagSet(c.Name, c.Flags) - if err != nil { - return err - } - set.SetOutput(ioutil.Discard) - firstFlagIndex, terminatorIndex := getIndexes(ctx) - flagArgs, regularArgs := getAllArgs(ctx.Args(), firstFlagIndex, terminatorIndex) - if c.UseShortOptionHandling { - flagArgs = translateShortOptions(flagArgs) - } - if c.SkipFlagParsing { - err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) - } else if !c.SkipArgReorder { - if firstFlagIndex > -1 { - err = set.Parse(append(flagArgs, regularArgs...)) - } else { - err = set.Parse(ctx.Args().Tail()) - } - } else if c.UseShortOptionHandling { - if terminatorIndex == -1 && firstFlagIndex > -1 { - // Handle shortname AND no options - err = set.Parse(append(regularArgs, flagArgs...)) - } else { - // Handle shortname and options - err = set.Parse(flagArgs) - } - } else { - err = set.Parse(append(regularArgs, flagArgs...)) - } - - nerr := normalizeFlags(c.Flags, set) - if nerr != nil { - fmt.Fprintln(ctx.App.Writer, nerr) - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return nerr - } + set, err := c.parseFlags(ctx.Args().Tail()) context := NewContext(ctx.App, set, ctx) context.Command = c @@ -205,60 +170,65 @@ func (c Command) Run(ctx *Context) (err error) { return err } -func getIndexes(ctx *Context) (int, int) { - firstFlagIndex := -1 - terminatorIndex := -1 - for index, arg := range ctx.Args() { - if arg == "--" { - terminatorIndex = index - break - } else if arg == "-" { - // Do nothing. A dash alone is not really a flag. - continue - } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { - firstFlagIndex = index - } +func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { + set, err := flagSet(c.Name, c.Flags) + if err != nil { + return nil, err } - if len(ctx.Args()) > 0 && !strings.HasPrefix(ctx.Args()[0], "-") && firstFlagIndex == -1 { - return -1, -1 + set.SetOutput(ioutil.Discard) + + if c.SkipFlagParsing { + return set, set.Parse(append([]string{c.Name, "--"}, args...)) } - return firstFlagIndex, terminatorIndex + if c.UseShortOptionHandling { + args = translateShortOptions(args) + } -} + if !c.SkipArgReorder { + args = reorderArgs(args) + } -// copyStringslice takes a string slice and copies it -func copyStringSlice(slice []string, start, end int) []string { - newSlice := make([]string, end-start) - copy(newSlice, slice[start:end]) - return newSlice -} + err = set.Parse(args) + if err != nil { + return nil, err + } -// getAllArgs extracts and returns two string slices representing -// regularArgs and flagArgs -func getAllArgs(args []string, firstFlagIndex, terminatorIndex int) ([]string, []string) { - var regularArgs []string - // if there are no options, the we set the index to 1 manually - if firstFlagIndex == -1 { - firstFlagIndex = 1 - regularArgs = copyStringSlice(args, 0, len(args)) - } else { - regularArgs = copyStringSlice(args, 1, firstFlagIndex) + err = normalizeFlags(c.Flags, set) + if err != nil { + return nil, err } - var flagArgs []string - // a flag terminatorIndex was found in the input. we need to collect - // flagArgs based on it. - if terminatorIndex > -1 { - flagArgs = copyStringSlice(args, firstFlagIndex, terminatorIndex) - additionalRegularArgs := copyStringSlice(args, terminatorIndex, len(args)) - regularArgs = append(regularArgs, additionalRegularArgs...) - for _, i := range additionalRegularArgs { - regularArgs = append(regularArgs, i) + + return set, nil +} + +// reorderArgs moves all flags before arguments as this is what flag expects +func reorderArgs(args []string) []string { + var nonflags, flags []string + + readFlagValue := false + for i, arg := range args { + if arg == "--" { + nonflags = append(nonflags, args[i:]...) + break + } + + if readFlagValue { + readFlagValue = false + flags = append(flags, arg) + continue + } + + if arg != "-" && strings.HasPrefix(arg, "-") { + flags = append(flags, arg) + + readFlagValue = !strings.Contains(arg, "=") + } else { + nonflags = append(nonflags, arg) } - } else { - flagArgs = args[firstFlagIndex:] } - return flagArgs, regularArgs + + return append(flags, nonflags...) } func translateShortOptions(flagArgs Args) []string { From 0671b166dcacb3dc1215ba65bf986dab194581dc Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 4 Dec 2017 09:23:40 -0800 Subject: [PATCH 371/381] Add tests for flag reordering --- command_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/command_test.go b/command_test.go index e69750a..c235e4a 100644 --- a/command_test.go +++ b/command_test.go @@ -243,3 +243,41 @@ func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { t.Fatal(err) } } + +func TestCommandFlagReordering(t *testing.T) { + cases := []struct { + testArgs []string + expectedValue string + expectedArgs []string + expectedErr error + }{ + {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, "foo", []string{"some-arg"}, nil}, + {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, "foo", []string{"some-arg"}, nil}, + {[]string{"some-exec", "some-command", "--flag=foo", "some-arg"}, "foo", []string{"some-arg"}, nil}, + } + + for _, c := range cases { + value := "" + args := []string{} + app := &App{ + Commands: []Command{ + { + Name: "some-command", + Flags: []Flag{ + StringFlag{Name: "flag"}, + }, + Action: func(c *Context) { + fmt.Printf("%+v\n", c.String("flag")) + value = c.String("flag") + args = c.Args() + }, + }, + }, + } + + err := app.Run(c.testArgs) + expect(t, err, c.expectedErr) + expect(t, value, c.expectedValue) + expect(t, args, c.expectedArgs) + } +} From e38e4ae2d05acf5b5164c160a67fb7048e1358b0 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Fri, 29 Dec 2017 13:38:18 -0500 Subject: [PATCH 372/381] Fix regression of SkipFlagParsing behavior Introduced by df562bf1a8626f2d16f91fcbf7230a5bdca3d592 Was mistakenly prepending the command name. --- command.go | 2 +- command_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 66a58b5..ed4a81a 100644 --- a/command.go +++ b/command.go @@ -178,7 +178,7 @@ func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { set.SetOutput(ioutil.Discard) if c.SkipFlagParsing { - return set, set.Parse(append([]string{c.Name, "--"}, args...)) + return set, set.Parse(append([]string{"--"}, args...)) } if c.UseShortOptionHandling { diff --git a/command_test.go b/command_test.go index c235e4a..c84b762 100644 --- a/command_test.go +++ b/command_test.go @@ -281,3 +281,39 @@ func TestCommandFlagReordering(t *testing.T) { expect(t, args, c.expectedArgs) } } + +func TestCommandSkipFlagParsing(t *testing.T) { + cases := []struct { + testArgs []string + expectedArgs []string + expectedErr error + }{ + {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, []string{"some-arg", "--flag", "foo"}, nil}, + {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, []string{"some-arg", "--flag=foo"}, nil}, + } + + for _, c := range cases { + value := "" + args := []string{} + app := &App{ + Commands: []Command{ + { + SkipFlagParsing: true, + Name: "some-command", + Flags: []Flag{ + StringFlag{Name: "flag"}, + }, + Action: func(c *Context) { + fmt.Printf("%+v\n", c.String("flag")) + value = c.String("flag") + args = c.Args() + }, + }, + }, + } + + err := app.Run(c.testArgs) + expect(t, err, c.expectedErr) + expect(t, args, c.expectedArgs) + } +} From d7555e172994da8d058334aa1fe69533b1685924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= Date: Fri, 26 Jan 2018 21:14:34 +0100 Subject: [PATCH 373/381] Fix unnecessary uses of Sprintf - use strconv directly - use concatenation for "%s%s" --- flag.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/flag.go b/flag.go index d4a4d41..b0cffc0 100644 --- a/flag.go +++ b/flag.go @@ -636,7 +636,7 @@ func withEnvHint(envVar, str string) string { suffix = "%" sep = "%, %" } - envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) + envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]" } return str + envText } @@ -709,13 +709,13 @@ func stringifyFlag(f Flag) string { placeholder = defaultPlaceholder } - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) + usageWithDefault := strings.TrimSpace(usage + defaultValueString) return FlagFileHinter( fv.FieldByName("FilePath").String(), FlagEnvHinter( fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s\t%s", FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder), usageWithDefault), + FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault, ), ) } @@ -724,7 +724,7 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + defaultVals = append(defaultVals, strconv.Itoa(i)) } } @@ -735,7 +735,7 @@ func stringifyInt64SliceFlag(f Int64SliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) } } @@ -747,7 +747,7 @@ func stringifyStringSliceFlag(f StringSliceFlag) string { if f.Value != nil && len(f.Value.Value()) > 0 { for _, s := range f.Value.Value() { if len(s) > 0 { - defaultVals = append(defaultVals, fmt.Sprintf("%q", s)) + defaultVals = append(defaultVals, strconv.Quote(s)) } } } @@ -766,8 +766,8 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) } - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", FlagNamePrefixer(name, placeholder), usageWithDefault) + usageWithDefault := strings.TrimSpace(usage + defaultVal) + return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault } func flagFromFileEnv(filePath, envName string) (val string, ok bool) { From 3a87b13b01ac8628694f1e1b20bdb452cc0f54d2 Mon Sep 17 00:00:00 2001 From: Nico Windler Date: Sat, 10 Feb 2018 13:35:23 +0100 Subject: [PATCH 374/381] Fix args reordering when bool flags are present --- app_test.go | 33 +++++++++++++++++++++++++++++++++ command.go | 3 ++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/app_test.go b/app_test.go index 5db9882..42fb149 100644 --- a/app_test.go +++ b/app_test.go @@ -329,6 +329,39 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { expect(t, firstArg, "my-arg") } +func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) { + var parsedOption, parsedSecondOption, firstArg string + var parsedBool, parsedSecondBool bool + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + StringFlag{Name: "option", Value: "", Usage: "some option"}, + StringFlag{Name: "secondOption", Value: "", Usage: "another option"}, + BoolFlag{Name: "boolflag", Usage: "some bool"}, + BoolFlag{Name: "b", Usage: "another bool"}, + }, + Action: func(c *Context) error { + parsedOption = c.String("option") + parsedSecondOption = c.String("secondOption") + parsedBool = c.Bool("boolflag") + parsedSecondBool = c.Bool("b") + firstArg = c.Args().First() + return nil + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"}) + + expect(t, parsedOption, "my-option") + expect(t, parsedSecondOption, "fancy-option") + expect(t, parsedBool, true) + expect(t, parsedSecondBool, true) + expect(t, firstArg, "my-arg") +} + func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context diff --git a/command.go b/command.go index ed4a81a..2acb976 100644 --- a/command.go +++ b/command.go @@ -213,11 +213,12 @@ func reorderArgs(args []string) []string { break } - if readFlagValue { + if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") { readFlagValue = false flags = append(flags, arg) continue } + readFlagValue = false if arg != "-" && strings.HasPrefix(arg, "-") { flags = append(flags, arg) From 45289ea7a0de564a71532e13b9916961a38abc8e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 20 Feb 2018 12:40:43 -0500 Subject: [PATCH 375/381] Adjust contribution and maintainer prose per current reality --- CODE_OF_CONDUCT.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 19 ++++++++++++ MAINTAINERS.md | 1 + README.md | 20 +++---------- 4 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 MAINTAINERS.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..41ba294 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be +reviewed and investigated and will result in a response that is deemed necessary +and appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..329195e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,19 @@ +## Contributing + +**NOTE**: the primary maintainer(s) may be found in +[./MAINTAINERS.md](./MAINTAINERS.md). + +Feel free to put up a pull request to fix a bug or maybe add a feature. I will +give it a code review and make sure that it does not break backwards +compatibility. If I or any other collaborators agree that it is in line with +the vision of the project, we will work with you to get the code into +a mergeable state and merge it into the master branch. + +If you have contributed something significant to the project, we 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 feel like you have contributed to the project but have not yet been added +as a collaborator, we probably forgot to add you :sweat_smile:. Please open an +issue! diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..f6bdd99 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1 @@ +- @meatballhat diff --git a/README.md b/README.md index 6096701..7750de7 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ cli [![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / [![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) -**Notice:** This is the library formerly known as -`github.com/codegangsta/cli` -- Github will automatically redirect requests -to this repository, but we recommend updating your references for clarity. +This is the library formerly known as `github.com/codegangsta/cli` -- Github +will automatically redirect requests to this repository, but we recommend +updating your references for clarity. cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line @@ -1433,16 +1433,4 @@ with two leading dashes (such as **--options**) are still valid. ## Contribution Guidelines -Feel free to put up a pull request to fix a bug or maybe add a feature. I will -give it a code review and make sure that it does not break backwards -compatibility. If I or any other collaborators agree that it is in line with -the vision of the project, we will work with you to get the code into -a mergeable state and merge it into the master branch. - -If you have contributed something significant to the project, we 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 feel like you have contributed to the project but have not yet been -added as a collaborator, we probably forgot to add you, please open an issue. +See [./CONTRIBUTING.md](./CONTRIBUTING.md) From c23dfba7018a4666892af705d89150a5f1ac8293 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Thu, 28 Jun 2018 16:41:02 +0200 Subject: [PATCH 376/381] short opt handling: fix parsing Only split a given string (e.g., "-abc") into short options (e.g., "-a", "-b", "-c") if all those are flags. To further avoid mistakenly transform common arguments, catch "flag provided but not defined" errors to iteratively transform short options. Signed-off-by: Valentin Rothberg Fixes: https://github.com/projectatomic/libpod/issues/714 --- command.go | 57 ++++++++++++++++++++++++++++++++++++++++++++----- command_test.go | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/command.go b/command.go index 2acb976..56b633c 100644 --- a/command.go +++ b/command.go @@ -181,16 +181,49 @@ func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { return set, set.Parse(append([]string{"--"}, args...)) } - if c.UseShortOptionHandling { - args = translateShortOptions(args) - } - if !c.SkipArgReorder { args = reorderArgs(args) } +PARSE: err = set.Parse(args) if err != nil { + if c.UseShortOptionHandling { + // To enable short-option handling (e.g., "-it" vs "-i -t") + // we have to iteratively catch parsing errors. This way + // we achieve LR parsing without transforming any arguments. + // Otherwise, there is no way we can discriminate combined + // short options from common arguments that should be left + // untouched. + errStr := err.Error() + trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: ") + if errStr == trimmed { + return nil, err + } + // regenerate the initial args with the split short opts + newArgs := Args{} + for i, arg := range args { + if arg != trimmed { + newArgs = append(newArgs, arg) + continue + } + shortOpts := translateShortOptions(set, Args{trimmed}) + if len(shortOpts) == 1 { + return nil, err + } + // add each short option and all remaining arguments + newArgs = append(newArgs, shortOpts...) + newArgs = append(newArgs, args[i+1:]...) + args = newArgs + // now reset the flagset parse again + set, err = flagSet(c.Name, c.Flags) + if err != nil { + return nil, err + } + set.SetOutput(ioutil.Discard) + goto PARSE + } + } return nil, err } @@ -232,11 +265,25 @@ func reorderArgs(args []string) []string { return append(flags, nonflags...) } -func translateShortOptions(flagArgs Args) []string { +func translateShortOptions(set *flag.FlagSet, flagArgs Args) []string { + allCharsFlags := func (s string) bool { + for i := range s { + f := set.Lookup(string(s[i])) + if f == nil { + return false + } + } + return true + } + // separate combined flags var flagArgsSeparated []string for _, flagArg := range flagArgs { if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 { + if !allCharsFlags(flagArg[1:]) { + flagArgsSeparated = append(flagArgsSeparated, flagArg) + continue + } for _, flagChar := range flagArg[1:] { flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar)) } diff --git a/command_test.go b/command_test.go index c84b762..9388f6a 100644 --- a/command_test.go +++ b/command_test.go @@ -57,6 +57,52 @@ func TestCommandFlagParsing(t *testing.T) { } } +func TestParseAndRunShortOpts(t *testing.T) { + cases := []struct { + testArgs []string + expectedErr error + expectedArgs []string + }{ + {[]string{"foo", "test", "-a"}, nil, []string{}}, + {[]string{"foo", "test", "-c", "arg1", "arg2"}, nil, []string{"arg1", "arg2"}}, + {[]string{"foo", "test", "-f"}, nil, []string{}}, + {[]string{"foo", "test", "-ac", "--fgh"}, nil, []string{}}, + {[]string{"foo", "test", "-af"}, nil, []string{}}, + {[]string{"foo", "test", "-cf"}, nil, []string{}}, + {[]string{"foo", "test", "-acf"}, nil, []string{}}, + {[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), []string{}}, + {[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1" ,"-invalid"}}, + } + + var args []string + cmd := Command{ + Name: "test", + Usage: "this is for testing", + Description: "testing", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + SkipArgReorder: true, + UseShortOptionHandling: true, + Flags: []Flag{ + BoolFlag{Name: "abc, a"}, + BoolFlag{Name: "cde, c"}, + BoolFlag{Name: "fgh, f"}, + }, + } + + for _, c := range cases { + app := NewApp() + app.Commands = []Command{cmd} + + err := app.Run(c.testArgs) + + expect(t, err, c.expectedErr) + expect(t, args, c.expectedArgs) + } +} + func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() app.Commands = []Command{ From 3e5a935ed3cafadcddc6f5ab2fe7ddd2aa0c3cea Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Tue, 21 Aug 2018 08:33:42 +0200 Subject: [PATCH 377/381] fix `go vet` warning command_test.go:342:3 value declared but not used Signed-off-by: Valentin Rothberg --- command_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/command_test.go b/command_test.go index 9388f6a..8c2650e 100644 --- a/command_test.go +++ b/command_test.go @@ -339,7 +339,6 @@ func TestCommandSkipFlagParsing(t *testing.T) { } for _, c := range cases { - value := "" args := []string{} app := &App{ Commands: []Command{ @@ -351,7 +350,6 @@ func TestCommandSkipFlagParsing(t *testing.T) { }, Action: func(c *Context) { fmt.Printf("%+v\n", c.String("flag")) - value = c.String("flag") args = c.Args() }, }, From d7c3be82673f869fed4ea77a0c5e3f13bd65ba89 Mon Sep 17 00:00:00 2001 From: Agis Anastasopoulos <827224+agis@users.noreply.github.com> Date: Tue, 21 Aug 2018 11:19:37 +0300 Subject: [PATCH 378/381] Fix README typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2baef4..6eb2996 100644 --- a/README.md +++ b/README.md @@ -670,7 +670,7 @@ func main() { ``` Note that default values set from file (e.g. `FilePath`) take precedence over -default values set from the enviornment (e.g. `EnvVar`). +default values set from the environment (e.g. `EnvVar`). #### Values from alternate input sources (YAML, TOML, and others) From 9587fc27bd923141975eac8c34288bcf8de5cca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Thu, 18 Oct 2018 20:56:13 -0400 Subject: [PATCH 379/381] Correct typo --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index 56b633c..3d44404 100644 --- a/command.go +++ b/command.go @@ -57,7 +57,7 @@ type Command struct { // Boolean to hide this command from help or completion Hidden bool // Boolean to enable short-option handling so user can combine several - // single-character bool arguements into one + // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool From 769f6d543bd3c9b36b98e3a46ad646cf63769120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Thu, 18 Oct 2018 21:00:02 -0400 Subject: [PATCH 380/381] Bring Go version current --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cf8d098..8bb0e9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: go sudo: false dist: trusty osx_image: xcode8.3 -go: 1.8.x +go: 1.11.x os: - linux From 5b83c895a70b7714548f0aa4f43deb3fa5fc1601 Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Tue, 29 Jan 2019 22:51:02 +0300 Subject: [PATCH 381/381] use type switch instead of if/else This reduces the syntax noise of the code by removing excessive type assertions. Signed-off-by: Iskander Sharipov --- app.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 9add067..b54e1ab 100644 --- a/app.go +++ b/app.go @@ -495,11 +495,12 @@ func (a Author) String() string { // it's an ActionFunc or a func with the legacy signature for Action, the func // is run! func HandleAction(action interface{}, context *Context) (err error) { - if a, ok := action.(ActionFunc); ok { + switch a := action.(type) { + case ActionFunc: return a(context) - } else if a, ok := action.(func(*Context) error); ok { + case func(*Context) error: return a(context) - } else if a, ok := action.(func(*Context)); ok { // deprecated function signature + case func(*Context): // deprecated function signature a(context) return nil }