diff --git a/.travis.yml b/.travis.yml index bb886b8..657e96a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,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 @@ -22,7 +24,7 @@ matrix: - ./runtests test before_script: -- go get github.com/meatballhat/gfmxr/... +- go get github.com/urfave/gfmxr/... script: - ./runtests vet diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c20a2..248181e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ ## [Unreleased] - (1.x series) ### Added - `./runtests` test runner with coverage tracking by default +- testing on OS X +- testing on Windows ### Fixed - Printing of command aliases in help text @@ -57,7 +59,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 @@ -317,28 +319,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 de73458..11a3ea4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ -[![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) +[![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) +[![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 +30,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: @@ -33,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 @@ -44,13 +55,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 +73,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 +93,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -102,7 +113,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -136,7 +147,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -205,7 +216,7 @@ Setting and querying flags is simple. ``` go ... app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -232,7 +243,7 @@ You can also set a destination variable for a flag, to which the content will be ... var language string app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -254,7 +265,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 @@ -264,9 +275,10 @@ indicated with back quotes. For example this: ```go -cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", +&cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", } ``` @@ -284,10 +296,11 @@ You can set alternate (or short) names for flags by providing a comma-delimited ``` go app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", }, } ``` @@ -296,28 +309,30 @@ That flag can then be set with `--lang spanish` or `-l spanish`. Note that givin #### Values from the Environment -You can also have the default value set from the environment via `EnvVar`. e.g. +You can also have the default value set from the environment via `EnvVars`. e.g. ``` go app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "APP_LANG", + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, }, } ``` -The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. +If `EnvVars` contains more than one string, 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", + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, }, } ``` @@ -329,7 +344,7 @@ There is a separate package altsrc that adds support for getting flag values fro 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"}) + 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. @@ -358,8 +373,8 @@ Here is a more complete sample of a command using YAML support: return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -371,7 +386,7 @@ Subcommands can be defined for a more git-like command line app. ```go ... -app.Commands = []cli.Command{ +app.Commands = []*cli.Command{ { Name: "add", Aliases: []string{"a"}, @@ -394,7 +409,7 @@ app.Commands = []cli.Command{ Name: "template", Aliases: []string{"r"}, Usage: "options for task templates", - Subcommands: []cli.Command{ + Subcommands: []*cli.Command{ { Name: "add", Usage: "add a new template", @@ -427,7 +442,7 @@ E.g. ```go ... - app.Commands = []cli.Command{ + app.Commands = []*cli.Command{ { Name: "noop", }, @@ -469,13 +484,13 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { app := cli.NewApp() app.Flags = []cli.Flag{ - cli.BoolFlag{ + &cli.BoolFlag{ Name: "ginger-crouton", Value: true, Usage: "is it in the soup?", @@ -483,7 +498,7 @@ func main() { } app.Action = func(ctx *cli.Context) error { if !ctx.Bool("ginger-crouton") { - return cli.NewExitError("it is not in the soup", 86) + return cli.Exit("it is not in the soup", 86) } return nil } @@ -504,7 +519,7 @@ the App or its subcommands. var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} app := cli.NewApp() app.EnableBashCompletion = true -app.Commands = []cli.Command{ +app.Commands = []*cli.Command{ { Name: "complete", Aliases: []string{"c"}, @@ -569,7 +584,7 @@ import ( "io" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -618,8 +633,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 aea142e..f14ea1c 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -5,9 +5,8 @@ import ( "fmt" "os" "strconv" - "strings" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // FlagInputSourceExtension is an extension interface of cli.Flag that @@ -63,30 +62,30 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context } } -// GenericFlag is the flag type that wraps cli.GenericFlag to allow +// GenericFlag is the flag type that wraps *cli.GenericFlag to allow // for other values to be specified type GenericFlag struct { - cli.GenericFlag + *cli.GenericFlag set *flag.FlagSet } // NewGenericFlag creates a new GenericFlag -func NewGenericFlag(flag cli.GenericFlag) *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) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { 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()) - }) + for _, name := range f.Names() { + f.set.Set(name, value.String()) + } } } } @@ -101,34 +100,34 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) { f.GenericFlag.Apply(set) } -// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow +// StringSliceFlag is the flag type that wraps *cli.StringSliceFlag to allow // for other values to be specified type StringSliceFlag struct { - cli.StringSliceFlag + *cli.StringSliceFlag set *flag.FlagSet } // NewStringSliceFlag creates a new StringSliceFlag -func NewStringSliceFlag(flag cli.StringSliceFlag) *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) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.StringSlice(f.StringSliceFlag.Name) if err != nil { return err } if value != nil { var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...)) - eachName(f.Name, func(name string) { - underlyingFlag := f.set.Lookup(f.Name) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } - }) + } } } } @@ -142,34 +141,34 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) { f.StringSliceFlag.Apply(set) } -// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow +// IntSliceFlag is the flag type that wraps *cli.IntSliceFlag to allow // for other values to be specified type IntSliceFlag struct { - cli.IntSliceFlag + *cli.IntSliceFlag set *flag.FlagSet } // NewIntSliceFlag creates a new IntSliceFlag -func NewIntSliceFlag(flag cli.IntSliceFlag) *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) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.IntSlice(f.IntSliceFlag.Name) if err != nil { return err } if value != nil { var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...)) - eachName(f.Name, func(name string) { - underlyingFlag := f.set.Lookup(f.Name) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } - }) + } } } } @@ -183,30 +182,30 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) { f.IntSliceFlag.Apply(set) } -// BoolFlag is the flag type that wraps cli.BoolFlag to allow +// BoolFlag is the flag type that wraps *cli.BoolFlag to allow // for other values to be specified type BoolFlag struct { - cli.BoolFlag + *cli.BoolFlag set *flag.FlagSet } // NewBoolFlag creates a new BoolFlag -func NewBoolFlag(flag cli.BoolFlag) *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) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { 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)) - }) + for _, name := range f.Names() { + f.set.Set(name, strconv.FormatBool(value)) + } } } } @@ -220,30 +219,30 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) { f.BoolFlag.Apply(set) } -// StringFlag is the flag type that wraps cli.StringFlag to allow +// StringFlag is the flag type that wraps *cli.StringFlag to allow // for other values to be specified type StringFlag struct { - cli.StringFlag + *cli.StringFlag set *flag.FlagSet } // NewStringFlag creates a new StringFlag -func NewStringFlag(flag cli.StringFlag) *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) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { 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) - }) + for _, name := range f.Names() { + f.set.Set(name, value) + } } } } @@ -258,30 +257,30 @@ func (f *StringFlag) Apply(set *flag.FlagSet) { f.StringFlag.Apply(set) } -// IntFlag is the flag type that wraps cli.IntFlag to allow +// IntFlag is the flag type that wraps *cli.IntFlag to allow // for other values to be specified type IntFlag struct { - cli.IntFlag + *cli.IntFlag set *flag.FlagSet } // NewIntFlag creates a new IntFlag -func NewIntFlag(flag cli.IntFlag) *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) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { 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)) - }) + for _, name := range f.Names() { + f.set.Set(name, strconv.FormatInt(int64(value), 10)) + } } } } @@ -295,30 +294,30 @@ func (f *IntFlag) Apply(set *flag.FlagSet) { f.IntFlag.Apply(set) } -// DurationFlag is the flag type that wraps cli.DurationFlag to allow +// DurationFlag is the flag type that wraps *cli.DurationFlag to allow // for other values to be specified type DurationFlag struct { - cli.DurationFlag + *cli.DurationFlag set *flag.FlagSet } // NewDurationFlag creates a new DurationFlag -func NewDurationFlag(flag cli.DurationFlag) *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) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { 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()) - }) + for _, name := range f.Names() { + f.set.Set(name, value.String()) + } } } } @@ -333,31 +332,31 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) { f.DurationFlag.Apply(set) } -// Float64Flag is the flag type that wraps cli.Float64Flag to allow +// Float64Flag is the flag type that wraps *cli.Float64Flag to allow // for other values to be specified type Float64Flag struct { - cli.Float64Flag + *cli.Float64Flag set *flag.FlagSet } // NewFloat64Flag creates a new Float64Flag -func NewFloat64Flag(flag cli.Float64Flag) *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) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Float64(f.Float64Flag.Name) if err != nil { return err } if value > 0 { floatStr := float64ToString(value) - eachName(f.Name, func(name string) { - f.set.Set(f.Name, floatStr) - }) + for _, name := range f.Names() { + f.set.Set(name, floatStr) + } } } } @@ -372,9 +371,8 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) { f.Float64Flag.Apply(set) } -func isEnvVarSet(envVars string) bool { - for _, envVar := range strings.Split(envVars, ",") { - envVar = strings.TrimSpace(envVar) +func isEnvVarSet(envVars []string) bool { + for _, envVar := range envVars { if envVal := os.Getenv(envVar); envVal != "" { // TODO: Can't use this for bools as // set means that it was true or false based on @@ -391,11 +389,3 @@ func isEnvVarSet(envVars string) bool { 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/altsrc/flag_test.go b/altsrc/flag_test.go index be7166f..e5b4c05 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 { @@ -26,7 +26,7 @@ type testApplyInputSource struct { func TestGenericApplyInputSourceValue(t *testing.T) { v := &Parser{"abc", "def"} c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}), FlagName: "test", MapValue: v, }) @@ -36,7 +36,7 @@ func TestGenericApplyInputSourceValue(t *testing.T) { func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { p := &Parser{"abc", "def"} c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}), FlagName: "test", MapValue: &Parser{"efg", "hig"}, ContextValueString: p.String(), @@ -46,7 +46,11 @@ func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}), + Flag: NewGenericFlag(&cli.GenericFlag{ + Name: "test", + Value: &Parser{}, + EnvVars: []string{"TEST"}, + }), FlagName: "test", MapValue: &Parser{"efg", "hij"}, EnvVarName: "TEST", @@ -57,7 +61,7 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestStringSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), FlagName: "test", MapValue: []string{"hello", "world"}, }) @@ -66,7 +70,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) { func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), FlagName: "test", MapValue: []string{"hello", "world"}, ContextValueString: "ohno", @@ -76,7 +80,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []string{"hello", "world"}, EnvVarName: "TEST", @@ -87,7 +91,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestIntSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), FlagName: "test", MapValue: []int{1, 2}, }) @@ -96,7 +100,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) { func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), FlagName: "test", MapValue: []int{1, 2}, ContextValueString: "3", @@ -106,7 +110,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []int{1, 2}, EnvVarName: "TEST", @@ -117,7 +121,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestBoolApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}), FlagName: "test", MapValue: true, }) @@ -126,7 +130,7 @@ func TestBoolApplyInputSourceMethodSet(t *testing.T) { func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}), FlagName: "test", MapValue: false, ContextValueString: "true", @@ -136,7 +140,7 @@ func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: false, EnvVarName: "TEST", @@ -147,7 +151,7 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestStringApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", MapValue: "hello", }) @@ -156,7 +160,7 @@ func TestStringApplyInputSourceMethodSet(t *testing.T) { func TestStringApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", MapValue: "hello", ContextValueString: "goodbye", @@ -166,7 +170,7 @@ func TestStringApplyInputSourceMethodContextSet(t *testing.T) { func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: "hello", EnvVarName: "TEST", @@ -177,7 +181,7 @@ func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestIntApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: 15, }) @@ -186,7 +190,7 @@ func TestIntApplyInputSourceMethodSet(t *testing.T) { func TestIntApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: 15, ContextValueString: "7", @@ -196,7 +200,7 @@ func TestIntApplyInputSourceMethodContextSet(t *testing.T) { func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 15, EnvVarName: "TEST", @@ -207,7 +211,7 @@ func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestDurationApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: time.Duration(30 * time.Second), }) @@ -216,7 +220,7 @@ func TestDurationApplyInputSourceMethodSet(t *testing.T) { func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: time.Duration(30 * time.Second), ContextValueString: time.Duration(15 * time.Second).String(), @@ -226,7 +230,7 @@ func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: time.Duration(30 * time.Second), EnvVarName: "TEST", @@ -237,7 +241,7 @@ func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: 1.3, }) @@ -246,7 +250,7 @@ func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: 1.3, ContextValueString: fmt.Sprintf("%v", 1.4), @@ -256,7 +260,7 @@ func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 1.3, EnvVarName: "TEST", diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 0f391b2..56603cf 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 a7fc628..68a749c 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..d5082af 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) { @@ -35,8 +35,8 @@ func TestCommandYamlFileTest(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -68,8 +68,8 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -103,8 +103,8 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -135,8 +135,8 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -168,8 +168,8 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -200,8 +200,8 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -233,8 +233,8 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -268,8 +268,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -303,8 +303,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *tes return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) 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 066ea7a..b16f991 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "sort" "time" ) @@ -26,7 +27,7 @@ type App struct { // Version of the program Version string // List of commands to execute - Commands []Command + Commands []*Command // List of flags to parse Flags []Flag // Boolean to enable bash completion commands @@ -35,8 +36,8 @@ 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 through method Categories() - categories CommandCategories + // Categories contains the categorized commands and is populated on app startup + Categories CommandCategories // An action to execute when the bash-completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -54,7 +55,7 @@ type App struct { // Compilation date Compiled time.Time // List of all authors who contributed - Authors []Author + Authors []*Author // Copyright of the binary if any Copyright string // Writer writer to write output to @@ -103,7 +104,7 @@ func (a *App) Setup() { a.didSetup = true - newCmds := []Command{} + newCmds := []*Command{} for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) @@ -112,16 +113,17 @@ func (a *App) Setup() { } a.Commands = newCmds - a.categories = CommandCategories{} + a.Categories = newCommandCategories() for _, command := range a.Commands { - a.categories = a.categories.AddCommand(command.Category, command) + a.Categories.AddCommand(command.Category, command) } - sort.Sort(a.categories) + sort.Sort(a.Categories.(*commandCategories)) // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } @@ -163,7 +165,7 @@ func (a *App) Run(arguments []string) (err error) { HandleExitCoder(err) return err } - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + fmt.Fprintf(a.Writer, "Incorrect Usage: %s\n\n", err) ShowAppHelp(context) return err } @@ -182,7 +184,7 @@ func (a *App) Run(arguments []string) (err error) { defer func() { if afterErr := a.After(context); afterErr != nil { if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -223,14 +225,15 @@ 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 { - a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } } - newCmds := []Command{} + newCmds := []*Command{} for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) @@ -272,7 +275,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + fmt.Fprintf(a.Writer, "Incorrect Usage: %s\n\n", err) ShowSubcommandHelp(context) return err } @@ -293,7 +296,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if afterErr != nil { HandleExitCoder(err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -330,28 +333,21 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { func (a *App) Command(name string) *Command { for _, c := range a.Commands { if c.HasName(name) { - return &c + return c } } return nil } -// 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 - } +func (a *App) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + for _, category := range a.Categories.Categories() { + if visible := func() CommandCategory { + if len(category.VisibleCommands()) > 0 { + return category } return nil }(); visible != nil { @@ -362,8 +358,8 @@ func (a *App) VisibleCategories() []*CommandCategory { } // VisibleCommands returns a slice of the Commands with Hidden=false -func (a *App) VisibleCommands() []Command { - ret := []Command{} +func (a *App) VisibleCommands() []*Command { + ret := []*Command{} for _, command := range a.Commands { if !command.Hidden { ret = append(ret, command) @@ -379,7 +375,7 @@ func (a *App) VisibleFlags() []Flag { func (a *App) hasFlag(flag Flag) bool { for _, f := range a.Flags { - if flag == f { + if reflect.DeepEqual(flag, f) { return true } } @@ -397,9 +393,15 @@ func (a *App) errWriter() io.Writer { return a.ErrWriter } -func (a *App) appendFlag(flag Flag) { - if !a.hasFlag(flag) { - a.Flags = append(a.Flags, flag) +func (a *App) appendFlag(fl Flag) { + if !hasFlag(a.Flags, fl) { + a.Flags = append(a.Flags, fl) + } +} + +func (a *App) appendCommand(c *Command) { + if !hasCommand(a.Commands, c) { + a.Commands = append(a.Commands, c) } } @@ -410,7 +412,7 @@ type Author struct { } // String makes Author comply to the Stringer interface, to allow an easy print in the templating process -func (a Author) String() string { +func (a *Author) String() string { e := "" if a.Email != "" { e = "<" + a.Email + "> " diff --git a/app_test.go b/app_test.go index 8d81a5d..99bd6a3 100644 --- a/app_test.go +++ b/app_test.go @@ -24,14 +24,14 @@ func ExampleApp_Run() { app := NewApp() app.Name = "greet" app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } app.Action = func(c *Context) error { fmt.Printf("Hello %v\n", c.String("name")) return nil } app.UsageText = "app [first_arg] [second_arg]" - app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} + app.Authors = []*Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} app.Run(os.Args) // Output: // Hello Jeremy @@ -42,20 +42,20 @@ func ExampleApp_Run_subcommand() { os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} app := NewApp() app.Name = "say" - app.Commands = []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: []Command{ + Subcommands: []*Command{ { Name: "english", Aliases: []string{"en"}, Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "name", Value: "Bob", Usage: "Name of the person to greet", @@ -82,9 +82,9 @@ func ExampleApp_Run_help() { app := NewApp() app.Name = "greet" app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "describeit", Aliases: []string{"d"}, @@ -115,7 +115,7 @@ func ExampleApp_Run_bashComplete() { app := NewApp() app.Name = "greet" app.EnableBashCompletion = true - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "describeit", Aliases: []string{"d"}, @@ -175,9 +175,9 @@ var commandAppTests = []struct { func TestApp_Command(t *testing.T) { app := NewApp() - fooCommand := Command{Name: "foobar", Aliases: []string{"f"}} - batCommand := Command{Name: "batbaz", Aliases: []string{"b"}} - app.Commands = []Command{ + fooCommand := &Command{Name: "foobar", Aliases: []string{"f"}} + batCommand := &Command{Name: "batbaz", Aliases: []string{"b"}} + app.Commands = []*Command{ fooCommand, batCommand, } @@ -191,7 +191,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context a := NewApp() - a.Commands = []Command{ + a.Commands = []*Command{ { Name: "foo", Action: func(c *Context) error { @@ -199,7 +199,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { return nil }, Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -216,13 +216,13 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string - var args []string + var args Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - StringFlag{Name: "option", Value: "", Usage: "some option"}, + &StringFlag{Name: "option", Value: "", Usage: "some option"}, }, Action: func(c *Context) error { parsedOption = c.String("option") @@ -230,58 +230,58 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}) expect(t, parsedOption, "my-option") - expect(t, args[0], "my-arg") - expect(t, args[1], "--") - expect(t, args[2], "--notARealFlag") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "--") + expect(t, args.Get(2), "--notARealFlag") } func TestApp_CommandWithDash(t *testing.T) { - var args []string + var args Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Action: func(c *Context) error { args = c.Args() return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "my-arg", "-"}) - expect(t, args[0], "my-arg") - expect(t, args[1], "-") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "-") } func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { - var args []string + var args Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Action: func(c *Context) error { args = c.Args() return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) - expect(t, args[0], "my-arg") - expect(t, args[1], "--") - expect(t, args[2], "notAFlagAtAll") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "--") + expect(t, args.Get(2), "notAFlagAtAll") } func TestApp_VisibleCommands(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "frob", HelpName: "foo frob", @@ -296,7 +296,7 @@ func TestApp_VisibleCommands(t *testing.T) { } app.Setup() - expected := []Command{ + expected := []*Command{ app.Commands[0], app.Commands[2], // help } @@ -310,14 +310,22 @@ func TestApp_VisibleCommands(t *testing.T) { 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() { + // nil out funcs, as they cannot be compared + // (https://github.com/golang/go/issues/8554) + expectedAction := expectedCommand.Action + actualAction := actualCommand.Action + defer func() { + expectedCommand.Action = expectedAction + actualCommand.Action = actualAction + }() + expectedCommand.Action = nil + actualCommand.Action = nil + + if !reflect.DeepEqual(expectedCommand, actualCommand) { + t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand) + } + }() } } @@ -326,7 +334,7 @@ func TestApp_Float64Flag(t *testing.T) { app := NewApp() app.Flags = []Flag{ - Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, + &Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, } app.Action = func(c *Context) error { meters = c.Float64("height") @@ -343,11 +351,11 @@ func TestApp_ParseSliceFlags(t *testing.T) { var parsedStringSlice []string app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"}, - StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"}, + &IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"}, + &StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"}, }, Action: func(c *Context) error { parsedIntSlice = c.IntSlice("p") @@ -357,7 +365,7 @@ func TestApp_ParseSliceFlags(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"}) @@ -401,11 +409,11 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { var parsedStringSlice []string app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - IntSliceFlag{Name: "a", Usage: "set numbers"}, - StringSliceFlag{Name: "str", Usage: "set strings"}, + &IntSliceFlag{Name: "a", Usage: "set numbers"}, + &StringSliceFlag{Name: "str", Usage: "set strings"}, }, Action: func(c *Context) error { parsedIntSlice = c.IntSlice("a") @@ -413,7 +421,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"}) @@ -491,7 +499,7 @@ func TestApp_BeforeFunc(t *testing.T) { return nil } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "sub", Action: func(c *Context) error { @@ -503,7 +511,7 @@ func TestApp_BeforeFunc(t *testing.T) { } app.Flags = []Flag{ - StringFlag{Name: "opt"}, + &StringFlag{Name: "opt"}, } // run with the Before() func succeeding @@ -583,7 +591,7 @@ func TestApp_AfterFunc(t *testing.T) { return nil } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "sub", Action: func(c *Context) error { @@ -595,7 +603,7 @@ func TestApp_AfterFunc(t *testing.T) { } app.Flags = []Flag{ - StringFlag{Name: "opt"}, + &StringFlag{Name: "opt"}, } // run with the After() func succeeding @@ -639,7 +647,7 @@ func TestAppNoHelpFlag(t *testing.T) { HelpFlag = oldFlag }() - HelpFlag = BoolFlag{} + HelpFlag = nil app := NewApp() app.Writer = ioutil.Discard @@ -698,7 +706,7 @@ func TestApp_CommandNotFound(t *testing.T) { counts.CommandNotFound = counts.Total } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Action: func(c *Context) error { @@ -765,7 +773,7 @@ func TestApp_OrderOfOperations(t *testing.T) { } app.After = afterNoError - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Action: func(c *Context) error { @@ -871,21 +879,21 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf - subCmdBar := Command{ + subCmdBar := &Command{ Name: "bar", Usage: "does bar things", } - subCmdBaz := Command{ + subCmdBaz := &Command{ Name: "baz", Usage: "does baz things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "descriptive wall of text about how it does foo things", - Subcommands: []Command{subCmdBar, subCmdBaz}, + Subcommands: []*Command{subCmdBar, subCmdBaz}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run(flagSet) if err != nil { @@ -916,16 +924,16 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -933,11 +941,14 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "command foo bar - does bar things") { - t.Errorf("expected full path to subcommand: %s", output) + expected := "command foo bar - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "command foo bar [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "command foo bar [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } @@ -946,17 +957,17 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", HelpName: "custom", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -964,11 +975,15 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "custom - does bar things") { - t.Errorf("expected HelpName for subcommand: %s", output) + + expected := "custom - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "custom [arguments...]") { - t.Errorf("expected HelpName to subcommand: %s", output) + + expected = "custom [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } @@ -977,17 +992,17 @@ func TestApp_Run_CommandHelpName(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", HelpName: "custom", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -995,11 +1010,15 @@ func TestApp_Run_CommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "command foo bar - does bar things") { - t.Errorf("expected full path to subcommand: %s", output) + + expected := "command foo bar - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "command foo bar [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "command foo bar [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } @@ -1008,17 +1027,17 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf app.Name = "base" - subCmd := Command{ + subCmd := &Command{ Name: "bar", HelpName: "custom", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "--help"}) if err != nil { @@ -1026,11 +1045,15 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "base foo - foo commands") { - t.Errorf("expected full path to subcommand: %s", output) + + expected := "base foo - foo commands" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %q", expected, output) } - if !strings.Contains(output, "base foo command [command options] [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "base foo command [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %q", expected, output) } } @@ -1100,7 +1123,7 @@ func TestApp_Run_Version(t *testing.T) { func TestApp_Run_Categories(t *testing.T) { app := NewApp() app.Name = "categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1119,23 +1142,24 @@ func TestApp_Run_Categories(t *testing.T) { app.Run([]string{"categories"}) - expect := CommandCategories{ - &CommandCategory{ - Name: "1", - Commands: []Command{ + expect := commandCategories([]*commandCategory{ + { + name: "1", + commands: []*Command{ app.Commands[0], app.Commands[1], }, }, - &CommandCategory{ - Name: "2", - Commands: []Command{ + { + name: "2", + commands: []*Command{ app.Commands[2], }, }, - } - if !reflect.DeepEqual(app.Categories(), expect) { - t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) + }) + + if !reflect.DeepEqual(app.Categories, &expect) { + t.Fatalf("expected categories %#v, to equal %#v", app.Categories, &expect) } output := buf.String() @@ -1149,7 +1173,7 @@ func TestApp_Run_Categories(t *testing.T) { func TestApp_VisibleCategories(t *testing.T) { app := NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1168,16 +1192,16 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected := []*CommandCategory{ - { - Name: "2", - Commands: []Command{ + expected := []CommandCategory{ + &commandCategory{ + name: "2", + commands: []*Command{ app.Commands[1], }, }, - { - Name: "3", - Commands: []Command{ + &commandCategory{ + name: "3", + commands: []*Command{ app.Commands[2], }, }, @@ -1188,7 +1212,7 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1208,10 +1232,10 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected = []*CommandCategory{ - { - Name: "3", - Commands: []Command{ + expected = []CommandCategory{ + &commandCategory{ + name: "3", + commands: []*Command{ app.Commands[2], }, }, @@ -1222,7 +1246,7 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1243,10 +1267,8 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected = []*CommandCategory{} - app.Setup() - expect(t, expected, app.VisibleCategories()) + expect(t, []CommandCategory{}, app.VisibleCategories()) } func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { @@ -1270,9 +1292,9 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { - Subcommands: []Command{ + Subcommands: []*Command{ { Name: "sub", }, @@ -1299,7 +1321,7 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() app.Flags = []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, } app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { if isSubcommand { @@ -1310,7 +1332,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { } return errors.New("intercepted: " + err.Error()) } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", }, @@ -1329,7 +1351,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { app := NewApp() app.Flags = []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, } app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { if isSubcommand { @@ -1340,7 +1362,7 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { } return errors.New("intercepted: " + err.Error()) } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", }, diff --git a/appveyor.yml b/appveyor.yml index 3ca7afa..173086e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,15 +2,24 @@ version: "{build}" os: Windows Server 2012 R2 -install: - - go version - - go env +clone_folder: c:\gopath\src\github.com\urfave\cli -build_script: - - cd %APPVEYOR_BUILD_FOLDER% - - go vet ./... - - go test -v ./... +environment: + GOPATH: C:\gopath + GOVERSION: 1.6 + PYTHON: C:\Python27-x64 + PYTHON_VERSION: 2.7.x + PYTHON_ARCH: 64 + GFMXR_DEBUG: 1 -test: off +install: +- set PATH=%GOPATH%\bin;C:\go\bin;%PATH% +- go version +- go env +- go get github.com/urfave/gfmxr/... +- go get -v -t ./... -deploy: off +build_script: +- python runtests vet +- python runtests test +- python runtests gfmxr diff --git a/args.go b/args.go new file mode 100644 index 0000000..5618a47 --- /dev/null +++ b/args.go @@ -0,0 +1,60 @@ +package cli + +import "errors" + +var ( + argsRangeErr = errors.New("index out of range") +) + +type Args interface { + // Get returns the nth argument, or else a blank string + Get(n int) string + // First returns the first argument, or else a blank string + First() string + // Tail returns the rest of the arguments (not the first one) + // or else an empty string slice + Tail() []string + // Len returns the length of the wrapped slice + Len() int + // Present checks if there are any arguments present + Present() bool + // Slice returns a copy of the internal slice + Slice() []string +} + +type args []string + +func (a *args) Get(n int) string { + if len(*a) > n { + return (*a)[n] + } + return "" +} + +func (a *args) First() string { + return a.Get(0) +} + +func (a *args) Tail() []string { + if a.Len() >= 2 { + tail := []string((*a)[1:]) + ret := make([]string, len(tail)) + copy(ret, tail) + return ret + } + return []string{} +} + +func (a *args) Len() int { + return len(*a) +} + +func (a *args) Present() bool { + return a.Len() != 0 +} + +func (a *args) Slice() []string { + ret := make([]string, len(*a)) + copy(ret, []string(*a)) + return ret +} diff --git a/category.go b/category.go index 1a60550..3b405c0 100644 --- a/category.go +++ b/category.go @@ -1,41 +1,82 @@ package cli -// CommandCategories is a slice of *CommandCategory. -type CommandCategories []*CommandCategory +type CommandCategories interface { + // AddCommand adds a command to a category, creating a new category if necessary. + AddCommand(category string, command *Command) + // Categories returns a copy of the category slice + Categories() []CommandCategory +} -// CommandCategory is a category containing commands. -type CommandCategory struct { - Name string - Commands Commands +type commandCategories []*commandCategory + +func newCommandCategories() CommandCategories { + ret := commandCategories([]*commandCategory{}) + return &ret } -func (c CommandCategories) Less(i, j int) bool { - return c[i].Name < c[j].Name +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) Len() int { + return len(*c) } -func (c CommandCategories) Swap(i, j int) { - c[i], c[j] = c[j], c[i] +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 { - commandCategory.Commands = append(commandCategory.Commands, command) - return c +func (c *commandCategories) AddCommand(category string, command *Command) { + for _, commandCategory := range []*commandCategory(*c) { + if commandCategory.name == category { + commandCategory.commands = append(commandCategory.commands, command) + return } } - return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) + newVal := commandCategories(append(*c, + &commandCategory{name: category, commands: []*Command{command}})) + (*c) = newVal +} + +func (c *commandCategories) Categories() []CommandCategory { + ret := make([]CommandCategory, len(*c)) + for i, cat := range *c { + ret[i] = cat + } + return ret +} + +// CommandCategory is a category containing commands. +type CommandCategory interface { + // Name returns the category name string + Name() string + // VisibleCommands returns a slice of the Commands with Hidden=false + VisibleCommands() []*Command } -// VisibleCommands returns a slice of the Commands with Hidden=false -func (c *CommandCategory) VisibleCommands() []Command { - ret := []Command{} - for _, command := range c.Commands { +type commandCategory struct { + name string + commands []*Command +} + +func newCommandCategory(name string) *commandCategory { + return &commandCategory{ + name: name, + commands: []*Command{}, + } +} + +func (c *commandCategory) Name() string { + return c.name +} + +func (c *commandCategory) VisibleCommands() []*Command { + if c.commands == nil { + c.commands = []*Command{} + } + + ret := []*Command{} + for _, command := range c.commands { if !command.Hidden { ret = append(ret, command) } diff --git a/command.go b/command.go index 7a77953..f05f1e2 100644 --- a/command.go +++ b/command.go @@ -36,7 +36,7 @@ type Command struct { // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc // List of child commands - Subcommands Commands + Subcommands []*Command // List of flags to parse Flags []Flag // Treat all flags as normal arguments if true @@ -53,32 +53,26 @@ type Command struct { // 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 { +func (c *Command) FullName() string { if c.commandNamePath == nil { return c.Name } return strings.Join(c.commandNamePath, " ") } -// Commands is a slice of Command -type Commands []Command - // Run invokes the command given the context, parses ctx.Args() to generate command-specific flags -func (c Command) Run(ctx *Context) (err error) { +func (c *Command) Run(ctx *Context) (err error) { if len(c.Subcommands) > 0 { return c.startApp(ctx) } - if !c.HideHelp && (HelpFlag != BoolFlag{}) { + if !c.HideHelp && HelpFlag != nil { // append help to flags - c.Flags = append( - c.Flags, - HelpFlag, - ) + c.appendFlag(HelpFlag) } if ctx.App.EnableBashCompletion { - c.Flags = append(c.Flags, BashCompletionFlag) + c.appendFlag(BashCompletionFlag) } set := flagSet(c.Name, c.Flags) @@ -96,7 +90,7 @@ func (c Command) Run(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintf(ctx.App.Writer, "Incorrect Usage: %s\n\n", err) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) return err @@ -126,7 +120,7 @@ func (c Command) Run(ctx *Context) (err error) { if afterErr != nil { HandleExitCoder(err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -155,13 +149,12 @@ func (c Command) Run(ctx *Context) (err error) { } // Names returns the names including short names and aliases. -func (c Command) Names() []string { - names := []string{c.Name} - return append(names, c.Aliases...) +func (c *Command) Names() []string { + return append([]string{c.Name}, c.Aliases...) } // HasName returns true if Command.Name matches given name -func (c Command) HasName(name string) bool { +func (c *Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { return true @@ -170,7 +163,7 @@ func (c Command) HasName(name string) bool { return false } -func (c Command) startApp(ctx *Context) error { +func (c *Command) startApp(ctx *Context) error { app := NewApp() app.Metadata = ctx.App.Metadata // set the name and usage @@ -200,12 +193,12 @@ func (c Command) startApp(ctx *Context) error { app.Compiled = ctx.App.Compiled app.Writer = ctx.App.Writer - app.categories = CommandCategories{} + app.Categories = newCommandCategories() for _, command := range c.Subcommands { - app.categories = app.categories.AddCommand(command.Category, command) + app.Categories.AddCommand(command.Category, command) } - sort.Sort(app.categories) + sort.Sort(app.Categories.(*commandCategories)) // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion @@ -230,6 +223,22 @@ func (c Command) startApp(ctx *Context) error { } // VisibleFlags returns a slice of the Flags with Hidden=false -func (c Command) VisibleFlags() []Flag { +func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } + +func (c *Command) appendFlag(fl Flag) { + if !hasFlag(c.Flags, fl) { + c.Flags = append(c.Flags, fl) + } +} + +func hasCommand(commands []*Command, command *Command) bool { + for _, existing := range commands { + if command == existing { + return true + } + } + + return false +} diff --git a/command_test.go b/command_test.go index a03f8bb..033149f 100644 --- a/command_test.go +++ b/command_test.go @@ -42,13 +42,13 @@ func TestCommandFlagParsing(t *testing.T) { err := command.Run(context) expect(t, err, c.expectedErr) - expect(t, []string(context.Args()), c.testArgs) + expect(t, context.Args().Slice(), c.testArgs) } } func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Before: func(c *Context) error { @@ -75,11 +75,11 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Flags: []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, }, OnUsageError: func(c *Context, err error, _ bool) error { if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { diff --git a/context.go b/context.go index 45face1..b43c957 100644 --- a/context.go +++ b/context.go @@ -10,11 +10,11 @@ import ( // Context is a type that is passed through to // each Handler action in a cli application. Context -// can be used to retrieve context-specific Args and +// can be used to retrieve context-specific args and // parsed command-line options. type Context struct { App *App - Command Command + Command *Command flagSet *flag.FlagSet parentContext *Context @@ -147,54 +147,15 @@ func (c *Context) Lineage() []*Context { return lineage } -// Args contains apps console arguments -type Args []string - // Args returns the command line arguments associated with the context. func (c *Context) Args() Args { - args := Args(c.flagSet.Args()) - return args + ret := args(c.flagSet.Args()) + return &ret } // NArg returns the number of the command line arguments. func (c *Context) NArg() int { - return len(c.Args()) -} - -// Get returns the nth argument, or else a blank string -func (a Args) Get(n int) string { - if len(a) > n { - return a[n] - } - return "" -} - -// First returns the first argument, or else a blank string -func (a Args) First() string { - return a.Get(0) -} - -// 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 { - return []string(a)[1:] - } - return []string{} -} - -// Present checks if there are any arguments present -func (a Args) Present() bool { - return len(a) != 0 -} - -// 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") - } - a[from], a[to] = a[to], a[from] - return nil + return c.Args().Len() } func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { @@ -310,7 +271,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { visited[f.Name] = true }) for _, f := range flags { - parts := strings.Split(f.GetName(), ",") + parts := f.Names() if len(parts) == 1 { continue } diff --git a/context_test.go b/context_test.go index 8147965..229275c 100644 --- a/context_test.go +++ b/context_test.go @@ -15,7 +15,7 @@ func TestNewContext(t *testing.T) { globalSet.Int("myflag", 42, "doc") globalSet.Float64("myflag64", float64(47), "doc") globalCtx := NewContext(nil, globalSet, nil) - command := Command{Name: "mycommand"} + command := &Command{Name: "mycommand"} c := NewContext(nil, set, globalCtx) c.Command = command expect(t, c.Int("myflag"), 12) @@ -63,7 +63,7 @@ func TestContext_Args(t *testing.T) { set.Bool("myflag", false, "doc") c := NewContext(nil, set, nil) set.Parse([]string{"--myflag", "bat", "baz"}) - expect(t, len(c.Args()), 2) + expect(t, c.Args().Len(), 2) expect(t, c.Bool("myflag"), true) } diff --git a/errors.go b/errors.go index ea551be..ba01537 100644 --- a/errors.go +++ b/errors.go @@ -15,25 +15,39 @@ var OsExiter = os.Exit var ErrWriter io.Writer = os.Stderr // MultiError is an error that wraps multiple errors. -type MultiError struct { - Errors []error +type MultiError interface { + error + // Errors returns a copy of the errors slice + Errors() []error } // NewMultiError creates a new MultiError. Pass in one or more errors. -func NewMultiError(err ...error) MultiError { - return MultiError{Errors: err} +func newMultiError(err ...error) MultiError { + ret := multiError(err) + return &ret } -// Error implents the error interface. -func (m MultiError) Error() string { - errs := make([]string, len(m.Errors)) - for i, err := range m.Errors { +type multiError []error + +// Error implements the error interface. +func (m *multiError) Error() string { + errs := make([]string, len(*m)) + for i, err := range *m { errs[i] = err.Error() } return strings.Join(errs, "\n") } +// Errors returns a copy of the errors slice +func (m *multiError) Errors() []error { + errs := make([]error, len(*m)) + for _, err := range *m { + errs = append(errs, err) + } + return errs +} + // ExitCoder is the interface checked by `App` and `Command` for a custom exit // code type ExitCoder interface { @@ -41,29 +55,25 @@ type ExitCoder interface { ExitCode() int } -// ExitError fulfills both the builtin `error` interface and `ExitCoder` -type ExitError struct { +type exitError struct { exitCode int message string } -// NewExitError makes a new *ExitError -func NewExitError(message string, exitCode int) *ExitError { - return &ExitError{ +// Exit wraps a message and exit code into an ExitCoder suitable for handling by +// HandleExitCoder +func Exit(message string, exitCode int) ExitCoder { + return &exitError{ exitCode: exitCode, message: message, } } -// Error returns the string message, fulfilling the interface required by -// `error` -func (ee *ExitError) Error() string { +func (ee *exitError) Error() string { return ee.message } -// ExitCode returns the exit code, fulfilling the interface required by -// `ExitCoder` -func (ee *ExitError) ExitCode() int { +func (ee *exitError) ExitCode() int { return ee.exitCode } @@ -85,7 +95,7 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.Errors { + for _, merr := range multiErr.Errors() { HandleExitCoder(merr) } } diff --git a/errors_test.go b/errors_test.go index 8f5f284..5b4981f 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 perimeter breach", 9)) + HandleExitCoder(Exit("galactic perimeter breach", 9)) expect(t, exitCode, 9) expect(t, called, true) @@ -51,8 +51,8 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - exitErr := NewExitError("galactic perimeter breach", 9) - err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) + exitErr := Exit("galactic perimeter breach", 9) + err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) HandleExitCoder(err) expect(t, exitCode, 9) diff --git a/flag.go b/flag.go index b5241e0..e5f9e0a 100644 --- a/flag.go +++ b/flag.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "reflect" + "regexp" "runtime" "strconv" "strings" @@ -14,26 +15,32 @@ import ( const defaultPlaceholder = "value" -var slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) +var ( + slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) + + commaWhitespace = regexp.MustCompile("[, ]+.*") +) // BashCompletionFlag enables bash-completion for all commands and subcommands -var BashCompletionFlag = BoolFlag{ +var BashCompletionFlag = &BoolFlag{ Name: "generate-bash-completion", Hidden: true, } // VersionFlag prints the version for the application -var VersionFlag = BoolFlag{ - Name: "version, v", - Usage: "print the version", +var VersionFlag = &BoolFlag{ + Name: "version", + Aliases: []string{"v"}, + Usage: "print the version", } -// 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{ - Name: "help, h", - Usage: "show help", +// HelpFlag prints the help for all commands and subcommands. +// Set to nil to disable the flag. The subcommand +// will still be added unless HideHelp is set to true. +var HelpFlag = &BoolFlag{ + Name: "help", + Aliases: []string{"h"}, + Usage: "show help", } // FlagStringer converts a flag definition to a string. This is used by help @@ -52,7 +59,7 @@ type Flag interface { fmt.Stringer // Apply Flag settings to the given flag set Apply(*flag.FlagSet) - GetName() string + Names() []string } func flagSet(name string, flags []Flag) *flag.FlagSet { @@ -64,14 +71,6 @@ func flagSet(name string, flags []Flag) *flag.FlagSet { return set } -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} - // Generic is a generic parseable type identified by a specific flag type Generic interface { Set(value string) error @@ -80,27 +79,27 @@ type Generic interface { // GenericFlag is the flag type for types implementing Generic type GenericFlag struct { - Name string - Value Generic - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value Generic + Usage string + EnvVars []string + Hidden bool } // String returns the string representation of the generic flag to display the // help text to the user (uses the String() method of the generic flag to show // the value) -func (f GenericFlag) String() string { +func (f *GenericFlag) String() string { return FlagStringer(f) } // 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) { +func (f *GenericFlag) Apply(set *flag.FlagSet) { val := f.Value - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { val.Set(envVal) break @@ -108,14 +107,14 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { - set.Var(f.Value, name, f.Usage) - }) + for _, name := range f.Names() { + set.Var(val, name, f.Usage) + } } -// GetName returns the name of a flag. -func (f GenericFlag) GetName() string { - return f.Name +// Names returns the names of a flag. +func (f *GenericFlag) Names() []string { + return flagNames(f) } // StringSlice wraps a []string to satisfy flag.Value @@ -166,23 +165,23 @@ func (f *StringSlice) Value() []string { // StringSliceFlag is a string flag that can be specified multiple times on the // command-line type StringSliceFlag struct { - Name string - Value *StringSlice - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value *StringSlice + Usage string + EnvVars []string + Hidden bool } // String returns the usage -func (f StringSliceFlag) String() string { +func (f *StringSliceFlag) String() string { return FlagStringer(f) } // 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, ",") { - envVar = strings.TrimSpace(envVar) +func (f *StringSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { newVal := NewStringSlice() for _, s := range strings.Split(envVal, ",") { @@ -199,14 +198,14 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { f.Value = NewStringSlice() } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of a flag. -func (f StringSliceFlag) GetName() string { - return f.Name +// Names returns the name of a flag. +func (f *StringSliceFlag) Names() []string { + return flagNames(f) } // IntSlice wraps an []int to satisfy flag.Value @@ -272,23 +271,23 @@ func (i *IntSlice) Value() []int { // IntSliceFlag is an int flag that can be specified multiple times on the // command-line type IntSliceFlag struct { - Name string - Value *IntSlice - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value *IntSlice + Usage string + EnvVars []string + Hidden bool } // String returns the usage -func (f IntSliceFlag) String() string { +func (f *IntSliceFlag) String() string { return FlagStringer(f) } // 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, ",") { - envVar = strings.TrimSpace(envVar) +func (f *IntSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { newVal := NewIntSlice() for _, s := range strings.Split(envVal, ",") { @@ -308,36 +307,36 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { f.Value = NewIntSlice() } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f IntSliceFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f *IntSliceFlag) Names() []string { + return flagNames(f) } // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string + Aliases []string Value bool Usage string - EnvVar string + EnvVars []string Destination *bool Hidden bool } // String returns a readable representation of this value (for usage defaults) -func (f BoolFlag) String() string { +func (f *BoolFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f BoolFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) +func (f *BoolFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { envValBool, err := strconv.ParseBool(envVal) if err == nil { @@ -348,40 +347,40 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.BoolVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Bool(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f BoolFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f *BoolFlag) Names() []string { + return flagNames(f) } // StringFlag represents a flag that takes as string value type StringFlag struct { Name string + Aliases []string Value string Usage string - EnvVar string + EnvVars []string Destination *string Hidden bool } // String returns the usage -func (f StringFlag) String() string { +func (f *StringFlag) String() string { return FlagStringer(f) } // 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, ",") { - envVar = strings.TrimSpace(envVar) +func (f *StringFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { f.Value = envVal break @@ -389,41 +388,41 @@ func (f StringFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.StringVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.String(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f StringFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f *StringFlag) Names() []string { + return flagNames(f) } // IntFlag is a flag that takes an integer // Errors if the value provided cannot be parsed type IntFlag struct { Name string + Aliases []string Value int Usage string - EnvVar string + EnvVars []string Destination *int Hidden bool } // String returns the usage -func (f IntFlag) String() string { +func (f *IntFlag) String() string { return FlagStringer(f) } // 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, ",") { - envVar = strings.TrimSpace(envVar) +func (f *IntFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err == nil { @@ -434,41 +433,41 @@ func (f IntFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.IntVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Int(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f IntFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f *IntFlag) Names() []string { + return flagNames(f) } // 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 + Aliases []string Value time.Duration Usage string - EnvVar string + EnvVars []string Destination *time.Duration Hidden bool } // String returns a readable representation of this value (for usage defaults) -func (f DurationFlag) String() string { +func (f *DurationFlag) String() string { return FlagStringer(f) } // 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, ",") { - envVar = strings.TrimSpace(envVar) +func (f *DurationFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { envValDuration, err := time.ParseDuration(envVal) if err == nil { @@ -479,41 +478,41 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.DurationVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Duration(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f DurationFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f *DurationFlag) Names() []string { + return flagNames(f) } // Float64Flag is a flag that takes an float value // Errors if the value provided cannot be parsed type Float64Flag struct { Name string + Aliases []string Value float64 Usage string - EnvVar string + EnvVars []string Destination *float64 Hidden bool } // String returns the usage -func (f Float64Flag) String() string { +func (f *Float64Flag) String() string { return FlagStringer(f) } // 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, ",") { - envVar = strings.TrimSpace(envVar) +func (f *Float64Flag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { envValFloat, err := strconv.ParseFloat(envVal, 10) if err == nil { @@ -523,24 +522,24 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.Float64Var(f.Destination, name, f.Value, f.Usage) - return + continue } set.Float64(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f Float64Flag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f *Float64Flag) Names() []string { + return flagNames(f) } func visibleFlags(fl []Flag) []Flag { visible := []Flag{} for _, flag := range fl { - if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { + if !flagValue(flag).FieldByName("Hidden").Bool() { visible = append(visible, flag) } } @@ -574,25 +573,27 @@ func unquoteUsage(usage string) (string, string) { return "", usage } -func prefixedNames(fullName, placeholder string) string { +func prefixedNames(names []string, placeholder string) string { var prefixed string - parts := strings.Split(fullName, ",") - for i, name := range parts { - name = strings.Trim(name, " ") + for i, name := range names { + if name == "" { + continue + } + prefixed += prefixFor(name) + name if placeholder != "" { prefixed += " " + placeholder } - if i < len(parts)-1 { + if i < len(names)-1 { prefixed += ", " } } return prefixed } -func withEnvHint(envVar, str string) string { +func withEnvHint(envVars []string, str string) string { envText := "" - if envVar != "" { + if envVars != nil && len(envVars) > 0 { prefix := "$" suffix := "" sep := ", $" @@ -601,21 +602,66 @@ func withEnvHint(envVar, str string) string { suffix = "%" sep = "%, %" } - envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix) } return str + envText } -func stringifyFlag(f Flag) string { +func flagNames(f Flag) []string { + ret := []string{} + + name := flagStringField(f, "Name") + aliases := flagStringSliceField(f, "Aliases") + + for _, part := range append([]string{name}, aliases...) { + // v1 -> v2 migration warning zone: + // Strip off anything after the first found comma or space, which + // *hopefully* makes it a tiny bit more obvious that unexpected behavior is + // caused by using the v1 form of stringly typed "Name". + ret = append(ret, commaWhitespace.ReplaceAllString(part, "")) + } + + return ret +} + +func flagStringSliceField(f Flag, name string) []string { + fv := flagValue(f) + field := fv.FieldByName(name) + + if field.IsValid() { + return field.Interface().([]string) + } + + return []string{} +} + +func flagStringField(f Flag, name string) string { + fv := flagValue(f) + field := fv.FieldByName(name) + + if field.IsValid() { + return field.String() + } + + return "" +} + +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: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyIntSliceFlag(f.(IntSliceFlag))) - case StringSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyStringSliceFlag(f.(StringSliceFlag))) + case *IntSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(*IntSliceFlag))) + case *StringSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(*StringSliceFlag))) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) @@ -643,11 +689,11 @@ 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 withEnvHint(flagStringSliceField(f, "EnvVars"), + fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } -func stringifyIntSliceFlag(f IntSliceFlag) string { +func stringifyIntSliceFlag(f *IntSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { @@ -655,10 +701,10 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) } -func stringifyStringSliceFlag(f StringSliceFlag) string { +func stringifyStringSliceFlag(f *StringSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, s := range f.Value.Value() { @@ -668,10 +714,10 @@ func stringifyStringSliceFlag(f StringSliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) } -func stringifySliceFlag(usage, name string, defaultVals []string) string { +func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { placeholder = defaultPlaceholder @@ -683,5 +729,15 @@ 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", prefixedNames(names, placeholder), usageWithDefault) +} + +func hasFlag(flags []Flag, fl Flag) bool { + for _, existing := range flags { + if fl == existing { + return true + } + } + + return false } diff --git a/flag_test.go b/flag_test.go index ce786b7..b510240 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1,6 +1,7 @@ package cli import ( + "flag" "fmt" "os" "reflect" @@ -20,7 +21,7 @@ var boolFlagTests = []struct { func TestBoolFlagHelpOutput(t *testing.T) { for _, test := range boolFlagTests { - flag := BoolFlag{Name: test.name} + flag := &BoolFlag{Name: test.name} output := flag.String() if output != test.expected { @@ -29,23 +30,35 @@ func TestBoolFlagHelpOutput(t *testing.T) { } } +func TestBoolFlagApply_SetsAllNames(t *testing.T) { + v := false + fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--wat", "-W", "--huh"}) + expect(t, err, nil) + expect(t, v, true) +} + var stringFlagTests = []struct { name string + aliases []string usage string value string expected string }{ - {"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\")"}, + {"foo", nil, "", "", "--foo value\t"}, + {"f", nil, "", "", "-f value\t"}, + {"f", nil, "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"}, + {"test", nil, "", "Something", "--test value\t(default: \"Something\")"}, + {"config", []string{"c"}, "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, + {"config", []string{"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} + flag := &StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} output := flag.String() if output != test.expected { @@ -58,7 +71,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_FOO", "derp") for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} + flag := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} output := flag.String() expectedSuffix := " [$APP_FOO]" @@ -71,21 +84,33 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestStringFlagApply_SetsAllNames(t *testing.T) { + v := "mmm" + fl := StringFlag{Name: "hay", Aliases: []string{"H", "hayyy"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--hay", "u", "-H", "yuu", "--hayyy", "YUUUU"}) + expect(t, err, nil) + expect(t, v, "YUUUU") +} + var stringSliceFlagTests = []struct { name string + aliases []string value *StringSlice expected string }{ - {"foo", NewStringSlice(""), "--foo value\t"}, - {"f", NewStringSlice(""), "-f value\t"}, - {"f", NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, - {"test", NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, - {"d, dee", NewStringSlice("Inka", "Dinka", "dooo"), "-d value, --dee value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, + {"foo", nil, NewStringSlice(""), "--foo value\t"}, + {"f", nil, NewStringSlice(""), "-f value\t"}, + {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, + {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, + {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Value: test.value} + flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -98,7 +123,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_QWWX", "11,4") for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} + flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} output := flag.String() expectedSuffix := " [$APP_QWWX]" @@ -111,6 +136,15 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestStringSliceFlagApply_SetsAllNames(t *testing.T) { + fl := StringSliceFlag{Name: "goat", Aliases: []string{"G", "gooots"}} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--goat", "aaa", "-G", "bbb", "--gooots", "eeeee"}) + expect(t, err, nil) +} + var intFlagTests = []struct { name string expected string @@ -121,7 +155,7 @@ var intFlagTests = []struct { func TestIntFlagHelpOutput(t *testing.T) { for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, Value: 9} + flag := &IntFlag{Name: test.name, Value: 9} output := flag.String() if output != test.expected { @@ -134,7 +168,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -147,6 +181,17 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestIntFlagApply_SetsAllNames(t *testing.T) { + v := 3 + fl := IntFlag{Name: "banana", Aliases: []string{"B", "banannanana"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--banana", "1", "-B", "2", "--banannanana", "5"}) + expect(t, err, nil) + expect(t, v, 5) +} + var durationFlagTests = []struct { name string expected string @@ -157,7 +202,7 @@ var durationFlagTests = []struct { func TestDurationFlagHelpOutput(t *testing.T) { for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, Value: 1 * time.Second} + flag := &DurationFlag{Name: test.name, Value: 1 * time.Second} output := flag.String() if output != test.expected { @@ -170,7 +215,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2h3m6s") for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -183,19 +228,31 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestDurationFlagApply_SetsAllNames(t *testing.T) { + v := time.Second * 20 + fl := DurationFlag{Name: "howmuch", Aliases: []string{"H", "whyyy"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--howmuch", "30s", "-H", "5m", "--whyyy", "30h"}) + expect(t, err, nil) + expect(t, v, time.Hour*30) +} + var intSliceFlagTests = []struct { name string + aliases []string value *IntSlice expected string }{ - {"heads", NewIntSlice(), "--heads value\t"}, - {"H", NewIntSlice(), "-H value\t"}, - {"H, heads", NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"}, + {"heads", nil, NewIntSlice(), "--heads value\t"}, + {"H", nil, NewIntSlice(), "-H value\t"}, + {"H", []string{"heads"}, NewIntSlice(9, 3), "-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} + flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -208,7 +265,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_SMURF", "42,3") for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} + flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() expectedSuffix := " [$APP_SMURF]" @@ -221,6 +278,15 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { + fl := IntSliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + var float64FlagTests = []struct { name string expected string @@ -231,7 +297,7 @@ var float64FlagTests = []struct { func TestFloat64FlagHelpOutput(t *testing.T) { for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, Value: float64(0.1)} + flag := &Float64Flag{Name: test.name, Value: float64(0.1)} output := flag.String() if output != test.expected { @@ -244,7 +310,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAZ", "99.4") for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} + flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} output := flag.String() expectedSuffix := " [$APP_BAZ]" @@ -257,6 +323,17 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestFloat64FlagApply_SetsAllNames(t *testing.T) { + v := float64(99.1) + fl := Float64Flag{Name: "noodles", Aliases: []string{"N", "nurbles"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--noodles", "1.3", "-N", "11", "--nurbles", "43.33333"}) + expect(t, err, nil) + expect(t, v, float64(43.33333)) +} + var genericFlagTests = []struct { name string value Generic @@ -268,7 +345,7 @@ var genericFlagTests = []struct { func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { - flag := 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 { @@ -281,7 +358,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_ZAP", "3") for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} + flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} output := flag.String() expectedSuffix := " [$APP_ZAP]" @@ -294,10 +371,19 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestGenericFlagApply_SetsAllNames(t *testing.T) { + fl := GenericFlag{Name: "orbs", Aliases: []string{"O", "obrs"}, Value: &Parser{}} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--orbs", "eleventy,3", "-O", "4,bloop", "--obrs", "19,s"}) + expect(t, err, nil) +} + func TestParseMultiString(t *testing.T) { (&App{ Flags: []Flag{ - StringFlag{Name: "serve, s"}, + &StringFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.String("serve") != "10" { @@ -315,7 +401,7 @@ func TestParseDestinationString(t *testing.T) { var dest string a := App{ Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "dest", Destination: &dest, }, @@ -335,7 +421,7 @@ func TestParseMultiStringFromEnv(t *testing.T) { os.Setenv("APP_COUNT", "20") (&App{ Flags: []Flag{ - StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, + &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -354,7 +440,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { os.Setenv("APP_COUNT", "20") (&App{ Flags: []Flag{ - StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, + &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -371,7 +457,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice()}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -389,7 +475,7 @@ func TestParseMultiStringSlice(t *testing.T) { func TestParseMultiStringSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -407,7 +493,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { @@ -427,7 +513,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "APP_INTERVALS"}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -447,7 +533,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "APP_INTERVALS"}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -467,7 +553,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -487,7 +573,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -504,7 +590,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { func TestParseMultiInt(t *testing.T) { a := App{ Flags: []Flag{ - IntFlag{Name: "serve, s"}, + &IntFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Int("serve") != 10 { @@ -523,7 +609,7 @@ func TestParseDestinationInt(t *testing.T) { var dest int a := App{ Flags: []Flag{ - IntFlag{ + &IntFlag{ Name: "dest", Destination: &dest, }, @@ -543,7 +629,7 @@ func TestParseMultiIntFromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := App{ Flags: []Flag{ - IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -563,7 +649,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := App{ Flags: []Flag{ - IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -581,7 +667,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { func TestParseMultiIntSlice(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice()}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -598,7 +684,7 @@ func TestParseMultiIntSlice(t *testing.T) { func TestParseMultiIntSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -615,7 +701,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { @@ -635,7 +721,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "APP_INTERVALS"}, + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -655,7 +741,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(1, 2, 5), EnvVar: "APP_INTERVALS"}, + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(1, 2, 5), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -675,7 +761,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -692,7 +778,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { func TestParseMultiFloat64(t *testing.T) { a := App{ Flags: []Flag{ - Float64Flag{Name: "serve, s"}, + &Float64Flag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Float64("serve") != 10.2 { @@ -711,7 +797,7 @@ func TestParseDestinationFloat64(t *testing.T) { var dest float64 a := App{ Flags: []Flag{ - Float64Flag{ + &Float64Flag{ Name: "dest", Destination: &dest, }, @@ -731,7 +817,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := App{ Flags: []Flag{ - Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { @@ -751,7 +837,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := App{ Flags: []Flag{ - Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { @@ -769,7 +855,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { func TestParseMultiBool(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "serve, s"}, + &BoolFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { @@ -788,7 +874,7 @@ func TestParseDestinationBool(t *testing.T) { var dest bool a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Destination: &dest, }, @@ -808,7 +894,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := App{ Flags: []Flag{ - BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -828,7 +914,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := App{ Flags: []Flag{ - BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -846,7 +932,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { func TestParseMultiBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "implode, i", Value: true}, + &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, }, Action: func(ctx *Context) error { if ctx.Bool("implode") { @@ -866,7 +952,7 @@ func TestParseDestinationBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Value: true, Destination: &dest, @@ -887,10 +973,11 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "0") a := App{ Flags: []Flag{ - BoolFlag{ - Name: "debug, d", - Value: true, - EnvVar: "APP_DEBUG", + &BoolFlag{ + Name: "debug", + Aliases: []string{"d"}, + Value: true, + EnvVars: []string{"APP_DEBUG"}, }, }, Action: func(ctx *Context) error { @@ -911,10 +998,11 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "0") a := App{ Flags: []Flag{ - BoolFlag{ - Name: "debug, d", - Value: true, - EnvVar: "COMPAT_DEBUG,APP_DEBUG", + &BoolFlag{ + Name: "debug", + Aliases: []string{"d"}, + Value: true, + EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}, }, }, Action: func(ctx *Context) error { @@ -951,7 +1039,7 @@ func (p *Parser) String() string { func TestParseGeneric(t *testing.T) { a := App{ Flags: []Flag{ - GenericFlag{Name: "serve, s", Value: &Parser{}}, + &GenericFlag{Name: "serve", Aliases: []string{"s"}, Value: &Parser{}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { @@ -971,7 +1059,12 @@ func TestParseGenericFromEnv(t *testing.T) { os.Setenv("APP_SERVE", "20,30") a := App{ Flags: []Flag{ - GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, + &GenericFlag{ + Name: "serve", + Aliases: []string{"s"}, + Value: &Parser{}, + EnvVars: []string{"APP_SERVE"}, + }, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { @@ -991,7 +1084,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) { os.Setenv("APP_FOO", "99,2000") a := App{ Flags: []Flag{ - GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, + &GenericFlag{ + Name: "foos", + Value: &Parser{}, + EnvVars: []string{"COMPAT_FOO", "APP_FOO"}, + }, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { diff --git a/help.go b/help.go index 893b1be..c21639d 100644 --- a/help.go +++ b/help.go @@ -74,7 +74,7 @@ OPTIONS: {{end}}{{end}} ` -var helpCommand = Command{ +var helpCommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -90,7 +90,7 @@ var helpCommand = Command{ }, } -var helpSubcommand = Command{ +var helpSubcommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -149,7 +149,7 @@ func ShowCommandHelp(ctx *Context, command string) error { } if ctx.App.CommandNotFound == nil { - return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3) + return Exit(fmt.Sprintf("No help topic for '%v'", command), 3) } ctx.App.CommandNotFound(ctx, command) @@ -158,7 +158,15 @@ func ShowCommandHelp(ctx *Context, command string) error { // ShowSubcommandHelp prints help for the given subcommand func ShowSubcommandHelp(c *Context) error { - return ShowCommandHelp(c, c.Command.Name) + if c == nil { + return nil + } + + if c.Command != nil { + return ShowCommandHelp(c, c.Command.Name) + } + + return ShowCommandHelp(c, "") } // ShowVersion prints the version number of the App @@ -193,26 +201,39 @@ func printHelp(out io.Writer, templ string, data interface{}) { w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + + errDebug := os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" + + defer func() { + if r := recover(); r != nil { + if errDebug { + fmt.Fprintf(ErrWriter, "CLI TEMPLATE PANIC: %#v\n", r) + } + if os.Getenv("CLI_TEMPLATE_REPANIC") != "" { + panic(r) + } + } + }() + 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. - if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { + if errDebug { fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) } return } + w.Flush() } func checkVersion(c *Context) bool { found := false if VersionFlag.Name != "" { - eachName(VersionFlag.Name, func(name string) { + for _, name := range VersionFlag.Names() { if c.Bool(name) { found = true } - }) + } } return found } @@ -220,11 +241,11 @@ func checkVersion(c *Context) bool { func checkHelp(c *Context) bool { found := false if HelpFlag.Name != "" { - eachName(HelpFlag.Name, func(name string) { + for _, name := range HelpFlag.Names() { if c.Bool(name) { found = true } - }) + } } return found } diff --git a/help_test.go b/help_test.go index d19bc4c..b81701c 100644 --- a/help_test.go +++ b/help_test.go @@ -59,14 +59,15 @@ func Test_Help_Custom_Flags(t *testing.T) { HelpFlag = oldFlag }() - HelpFlag = BoolFlag{ - Name: "help, x", - Usage: "show help", + HelpFlag = &BoolFlag{ + Name: "help", + Aliases: []string{"x"}, + Usage: "show help", } app := App{ Flags: []Flag{ - BoolFlag{Name: "foo, h"}, + &BoolFlag{Name: "foo", Aliases: []string{"h"}}, }, Action: func(ctx *Context) error { if ctx.Bool("h") != true { @@ -89,14 +90,15 @@ func Test_Version_Custom_Flags(t *testing.T) { VersionFlag = oldFlag }() - VersionFlag = BoolFlag{ - Name: "version, V", - Usage: "show version", + VersionFlag = &BoolFlag{ + Name: "version", + Aliases: []string{"V"}, + Usage: "show version", } app := App{ Flags: []Flag{ - BoolFlag{Name: "foo, v"}, + &BoolFlag{Name: "foo", Aliases: []string{"v"}}, }, Action: func(ctx *Context) error { if ctx.Bool("v") != true { @@ -127,9 +129,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*ExitError) + exitErr, ok := err.(*exitError) if !ok { - t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { @@ -155,9 +157,9 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*ExitError) + exitErr, ok := err.(*exitError) if !ok { - t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { @@ -171,7 +173,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { func TestShowAppHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob"}, @@ -193,7 +195,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) { func TestShowCommandHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob", "bork"}, @@ -219,7 +221,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) { func TestShowSubcommandHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob", "bork"}, @@ -241,7 +243,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Action: func(ctx *Context) error { diff --git a/helpers_test.go b/helpers_test.go index 109ea7a..bcfa46b 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -12,6 +12,10 @@ var ( wd, _ = os.Getwd() ) +func init() { + os.Setenv("CLI_TEMPLATE_REPANIC", "1") +} + func expect(t *testing.T, a interface{}, b interface{}) { _, fn, line, _ := runtime.Caller(1) fn = strings.Replace(fn, wd+"/", "", -1) diff --git a/runtests b/runtests index 9288f11..72c1f0d 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' ) @@ -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__':