Merge remote-tracking branch 'origin/v1' into merging-from-v1

This commit is contained in:
Dan Buch 2017-08-04 11:59:27 -04:00
commit a61867e5e6
Signed by: meatballhat
GPG Key ID: 9685130D8B763EA7
28 changed files with 1322 additions and 245 deletions

View File

@ -1,35 +1,23 @@
language: go language: go
sudo: false sudo: false
dist: trusty
osx_image: xcode8.3
go: 1.8.x
os:
- linux
- osx
cache: cache:
directories: directories:
- node_modules - node_modules
go:
- 1.2.x
- 1.3.x
- 1.4.2
- 1.5.x
- 1.6.x
- 1.7.x
- master
env: pip_install="pip install --user"
matrix:
allow_failures:
- go: master
include:
- go: 1.6.x
os: osx
env: pip_install="sudo pip install"
- go: 1.7.x
os: osx
env: pip_install="sudo pip install"
before_script: before_script:
- $pip_install flake8 - if [[ $(uname) == Darwin ]]; then
sudo pip install flake8;
else
pip install --user flake8;
fi
- mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave - mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave
- rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 - rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2
- rm -rvf ${GOPATH%%:*}/pkg/*/gopkg.in/urfave/cli.v2.a - rm -rvf ${GOPATH%%:*}/pkg/*/gopkg.in/urfave/cli.v2.a

View File

@ -34,13 +34,70 @@
## [Unreleased] - (1.x series) ## [Unreleased] - (1.x series)
### Added ### Added
### Changed
### Removed
### Fixed
### Deprecated
### Security
## [1.19.1] - 2016-11-21
### Fixed
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
the `Action` for a command would cause it to error rather than calling the
function. Should not have a affected declarative cases using `func(c
*cli.Context) err)`.
- Shell completion now handles the case where the user specifies
`--generate-bash-completion` immediately after a flag that takes an argument.
Previously it call the application with `--generate-bash-completion` as the
flag value.
## [1.19.0] - 2016-11-19
### Added
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
- A `Description` field was added to `App` for a more detailed description of
the application (similar to the existing `Description` field on `Command`)
- Flag type code generation via `go generate` - Flag type code generation via `go generate`
- Write to stderr and exit 1 if action returns non-nil error - Write to stderr and exit 1 if action returns non-nil error
- Added support for TOML to the `altsrc` loader - Added support for TOML to the `altsrc` loader
- `SkipArgReorder` was added to allow users to skip the argument reordering.
This is useful if you want to consider all "flags" after an argument as
arguments rather than flags (the default behavior of the stdlib `flag`
library). This is backported functionality from the [removal of the flag
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version
2
- For formatted errors (those implementing `ErrorFormatter`), the errors will
be formatted during output. Compatible with `pkg/errors`.
### Changed ### Changed
- Raise minimum tested/supported Go version to 1.2+ - Raise minimum tested/supported Go version to 1.2+
### Fixed
- Consider empty environment variables as set (previously environment variables
with the equivalent of `""` would be skipped rather than their value used).
- Return an error if the value in a given environment variable cannot be parsed
as the flag type. Previously these errors were silently swallowed.
- Print full error when an invalid flag is specified (which includes the invalid flag)
- `App.Writer` defaults to `stdout` when `nil`
- If no action is specified on a command or app, the help is now printed instead of `panic`ing
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized)
- Correctly show help message if `-h` is provided to a subcommand
- `context.(Global)IsSet` now respects environment variables. Previously it
would return `false` if a flag was specified in the environment rather than
as an argument
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well
as `altsrc` where Go would complain that the types didn't match
## [1.18.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported)
## [1.18.0] - 2016-06-27 ## [1.18.0] - 2016-06-27
### Added ### Added
- `./runtests` test runner with coverage tracking by default - `./runtests` test runner with coverage tracking by default
@ -59,6 +116,10 @@
- No longer swallows `panic`s that occur within the `Action`s themselves when - No longer swallows `panic`s that occur within the `Action`s themselves when
detecting the signature of the `Action` field detecting the signature of the `Action` field
## [1.17.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
## [1.17.0] - 2016-05-09 ## [1.17.0] - 2016-05-09
### Added ### Added
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` - Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
@ -80,6 +141,10 @@
- cleanups based on [Go Report Card - cleanups based on [Go Report Card
feedback](https://goreportcard.com/report/github.com/urfave/cli) feedback](https://goreportcard.com/report/github.com/urfave/cli)
## [1.16.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
## [1.16.0] - 2016-05-02 ## [1.16.0] - 2016-05-02
### Added ### Added
- `Hidden` field on all flag struct types to omit from generated help text - `Hidden` field on all flag struct types to omit from generated help text

View File

@ -461,13 +461,13 @@ error.
Flags for the application and commands are shown in the order they are defined. Flags for the application and commands are shown in the order they are defined.
However, it's possible to sort them from outside this library by using `FlagsByName` However, it's possible to sort them from outside this library by using `FlagsByName`
with `sort`. or `CommandsByName` with `sort`.
For example this: For example this:
<!-- { <!-- {
"args": ["&#45;&#45;help"], "args": ["&#45;&#45;help"],
"output": "Load configuration from FILE\n.*Language for the greeting.*" "output": "add a task to the list\n.*complete a task on the list\n.*\n\n.*\n.*Load configuration from FILE\n.*Language for the greeting.*"
} --> } -->
``` go ``` go
package main package main
@ -494,7 +494,27 @@ func main() {
}, },
} }
app.Commands = []cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) error {
return nil
},
},
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) error {
return nil
},
},
}
sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
app.Run(os.Args) app.Run(os.Args)
} }
@ -1001,16 +1021,13 @@ SUPPORT: support@awesometown.example.com
cli.AppHelpTemplate = `NAME: cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
[command options]{{end}} {{if
.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if len .Authors}} {{if len .Authors}}
AUTHOR(S): AUTHOR:
{{range .Authors}}{{ . }}{{end}} {{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}} {{end}}{{if .Commands}}
COMMANDS: COMMANDS:
{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t" {{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS: GLOBAL OPTIONS:
{{range .VisibleFlags}}{{.}} {{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{if .Copyright }} {{end}}{{end}}{{if .Copyright }}

View File

@ -2,8 +2,8 @@ package altsrc
import ( import (
"fmt" "fmt"
"os"
"strconv" "strconv"
"syscall"
"gopkg.in/urfave/cli.v2" "gopkg.in/urfave/cli.v2"
) )
@ -217,13 +217,11 @@ func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourc
func isEnvVarSet(envVars []string) bool { func isEnvVarSet(envVars []string) bool {
for _, envVar := range envVars { for _, envVar := range envVars {
if envVal := os.Getenv(envVar); envVal != "" { if _, ok := syscall.Getenv(envVar); ok {
// TODO: Can't use this for bools as // TODO: Can't use this for bools as
// set means that it was true or false based on // set means that it was true or false based on
// Bool flag type, should work for other types // Bool flag type, should work for other types
if len(envVal) > 0 { return true
return true
}
} }
} }

View File

@ -27,6 +27,13 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) {
f.BoolFlag.Apply(set) f.BoolFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped BoolFlag.ApplyWithError
func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.BoolFlag.ApplyWithError(set)
}
// 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 // for other values to be specified
type DurationFlag struct { type DurationFlag struct {
@ -46,6 +53,13 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) {
f.DurationFlag.Apply(set) f.DurationFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped DurationFlag.ApplyWithError
func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.DurationFlag.ApplyWithError(set)
}
// Float64Flag is the flag type that wraps cli.Float64Flag to allow // Float64Flag is the flag type that wraps cli.Float64Flag to allow
// for other values to be specified // for other values to be specified
type Float64Flag struct { type Float64Flag struct {
@ -65,6 +79,13 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) {
f.Float64Flag.Apply(set) f.Float64Flag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Float64Flag.ApplyWithError
func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Float64Flag.ApplyWithError(set)
}
// GenericFlag is the flag type that wraps cli.GenericFlag to allow // GenericFlag is the flag type that wraps cli.GenericFlag to allow
// for other values to be specified // for other values to be specified
type GenericFlag struct { type GenericFlag struct {
@ -84,6 +105,13 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) {
f.GenericFlag.Apply(set) f.GenericFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped GenericFlag.ApplyWithError
func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.GenericFlag.ApplyWithError(set)
}
// Int64Flag is the flag type that wraps cli.Int64Flag to allow // Int64Flag is the flag type that wraps cli.Int64Flag to allow
// for other values to be specified // for other values to be specified
type Int64Flag struct { type Int64Flag struct {
@ -103,6 +131,13 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) {
f.Int64Flag.Apply(set) f.Int64Flag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Int64Flag.ApplyWithError
func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Int64Flag.ApplyWithError(set)
}
// IntFlag is the flag type that wraps cli.IntFlag to allow // IntFlag is the flag type that wraps cli.IntFlag to allow
// for other values to be specified // for other values to be specified
type IntFlag struct { type IntFlag struct {
@ -122,6 +157,13 @@ func (f *IntFlag) Apply(set *flag.FlagSet) {
f.IntFlag.Apply(set) f.IntFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped IntFlag.ApplyWithError
func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.IntFlag.ApplyWithError(set)
}
// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow // IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow
// for other values to be specified // for other values to be specified
type IntSliceFlag struct { type IntSliceFlag struct {
@ -141,6 +183,13 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
f.IntSliceFlag.Apply(set) f.IntSliceFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped IntSliceFlag.ApplyWithError
func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.IntSliceFlag.ApplyWithError(set)
}
// Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow // Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow
// for other values to be specified // for other values to be specified
type Int64SliceFlag struct { type Int64SliceFlag struct {
@ -160,6 +209,13 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
f.Int64SliceFlag.Apply(set) f.Int64SliceFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Int64SliceFlag.ApplyWithError
func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Int64SliceFlag.ApplyWithError(set)
}
// Float64SliceFlag is the flag type that wraps cli.Float64SliceFlag to allow // Float64SliceFlag is the flag type that wraps cli.Float64SliceFlag to allow
// for other values to be specified // for other values to be specified
type Float64SliceFlag struct { type Float64SliceFlag struct {
@ -179,6 +235,13 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) {
f.Float64SliceFlag.Apply(set) f.Float64SliceFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Float64SliceFlag.ApplyWithError
func (f *Float64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Float64SliceFlag.ApplyWithError(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 // for other values to be specified
type StringFlag struct { type StringFlag struct {
@ -198,6 +261,13 @@ func (f *StringFlag) Apply(set *flag.FlagSet) {
f.StringFlag.Apply(set) f.StringFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped StringFlag.ApplyWithError
func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.StringFlag.ApplyWithError(set)
}
// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow // StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow
// for other values to be specified // for other values to be specified
type StringSliceFlag struct { type StringSliceFlag struct {
@ -217,6 +287,13 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
f.StringSliceFlag.Apply(set) f.StringSliceFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped StringSliceFlag.ApplyWithError
func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.StringSliceFlag.ApplyWithError(set)
}
// Uint64Flag is the flag type that wraps cli.Uint64Flag to allow // Uint64Flag is the flag type that wraps cli.Uint64Flag to allow
// for other values to be specified // for other values to be specified
type Uint64Flag struct { type Uint64Flag struct {
@ -236,6 +313,13 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) {
f.Uint64Flag.Apply(set) f.Uint64Flag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Uint64Flag.ApplyWithError
func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Uint64Flag.ApplyWithError(set)
}
// UintFlag is the flag type that wraps cli.UintFlag to allow // UintFlag is the flag type that wraps cli.UintFlag to allow
// for other values to be specified // for other values to be specified
type UintFlag struct { type UintFlag struct {
@ -254,3 +338,10 @@ func (f *UintFlag) Apply(set *flag.FlagSet) {
f.set = set f.set = set
f.UintFlag.Apply(set) f.UintFlag.Apply(set)
} }
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped UintFlag.ApplyWithError
func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.UintFlag.ApplyWithError(set)
}

View File

@ -63,7 +63,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []interface{}{"hello", "world"},
}) })
expect(t, c.StringSlice("test"), []string{"hello", "world"}) expect(t, c.StringSlice("test"), []string{"hello", "world"})
} }
@ -72,7 +72,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []interface{}{"hello", "world"},
ContextValueString: "ohno", ContextValueString: "ohno",
}) })
expect(t, c.StringSlice("test"), []string{"ohno"}) expect(t, c.StringSlice("test"), []string{"ohno"})
@ -82,7 +82,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []interface{}{"hello", "world"},
EnvVarName: "TEST", EnvVarName: "TEST",
EnvVarValue: "oh,no", EnvVarValue: "oh,no",
}) })
@ -93,7 +93,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []interface{}{1, 2},
}) })
expect(t, c.IntSlice("test"), []int{1, 2}) expect(t, c.IntSlice("test"), []int{1, 2})
} }
@ -102,7 +102,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []interface{}{1, 2},
ContextValueString: "3", ContextValueString: "3",
}) })
expect(t, c.IntSlice("test"), []int{3}) expect(t, c.IntSlice("test"), []int{3})
@ -112,7 +112,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []interface{}{1, 2},
EnvVarName: "TEST", EnvVarName: "TEST",
EnvVarValue: "3,4", EnvVarValue: "3,4",
}) })

View File

@ -130,45 +130,59 @@ func (fsm *MapInputSource) String(name string) (string, error) {
// StringSlice returns an []string from the map if it exists otherwise returns nil // StringSlice returns an []string from the map if it exists otherwise returns nil
func (fsm *MapInputSource) StringSlice(name string) ([]string, error) { func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
otherGenericValue, exists := fsm.valueMap[name] otherGenericValue, exists := fsm.valueMap[name]
if exists { if !exists {
otherValue, isType := otherGenericValue.([]string) otherGenericValue, exists = nestedVal(name, fsm.valueMap)
if !isType { if !exists {
return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue) return nil, nil
} }
return otherValue, nil
}
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
if exists {
otherValue, isType := nestedGenericValue.([]string)
if !isType {
return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue)
}
return otherValue, nil
} }
return nil, nil otherValue, isType := otherGenericValue.([]interface{})
if !isType {
return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue)
}
var stringSlice = make([]string, 0, len(otherValue))
for i, v := range otherValue {
stringValue, isType := v.(string)
if !isType {
return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "string", v)
}
stringSlice = append(stringSlice, stringValue)
}
return stringSlice, nil
} }
// IntSlice returns an []int from the map if it exists otherwise returns nil // IntSlice returns an []int from the map if it exists otherwise returns nil
func (fsm *MapInputSource) IntSlice(name string) ([]int, error) { func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
otherGenericValue, exists := fsm.valueMap[name] otherGenericValue, exists := fsm.valueMap[name]
if exists { if !exists {
otherValue, isType := otherGenericValue.([]int) otherGenericValue, exists = nestedVal(name, fsm.valueMap)
if !isType { if !exists {
return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue) return nil, nil
} }
return otherValue, nil
}
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
if exists {
otherValue, isType := nestedGenericValue.([]int)
if !isType {
return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue)
}
return otherValue, nil
} }
return nil, nil otherValue, isType := otherGenericValue.([]interface{})
if !isType {
return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue)
}
var intSlice = make([]int, 0, len(otherValue))
for i, v := range otherValue {
intValue, isType := v.(int)
if !isType {
return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "int", v)
}
intSlice = append(intSlice, intValue)
}
return intSlice, nil
} }
// Generic returns an cli.Generic from the map if it exists otherwise returns nil // Generic returns an cli.Generic from the map if it exists otherwise returns nil

View File

@ -57,8 +57,8 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
} else { } else {
return nil, err return nil, err
} }
case reflect.Array: case reflect.Array, reflect.Slice:
fallthrough // [todo] - Support array type ret[key] = val.([]interface{})
default: default:
return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind()) return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind())
} }

View File

@ -11,6 +11,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"runtime"
"strings"
"gopkg.in/urfave/cli.v2" "gopkg.in/urfave/cli.v2"
@ -78,6 +80,12 @@ func loadDataFrom(filePath string) ([]byte, error) {
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
} }
return ioutil.ReadFile(filePath) return ioutil.ReadFile(filePath)
} else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") {
// on Windows systems u.Path is always empty, so we need to check the string directly.
if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil {
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
}
return ioutil.ReadFile(filePath)
} else { } else {
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
} }

36
app.go
View File

@ -65,6 +65,12 @@ type App struct {
ErrWriter io.Writer ErrWriter io.Writer
// Other custom info // Other custom info
Metadata map[string]interface{} Metadata map[string]interface{}
// Carries a function which returns app specific info.
ExtraInfo func() map[string]string
// CustomAppHelpTemplate the text template for app help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomAppHelpTemplate string
didSetup bool didSetup bool
} }
@ -156,6 +162,10 @@ func (a *App) Setup() {
if a.Metadata == nil { if a.Metadata == nil {
a.Metadata = make(map[string]interface{}) a.Metadata = make(map[string]interface{})
} }
if a.Writer == nil {
a.Writer = os.Stdout
}
} }
// Run is the entry point to the cli app. Parses the arguments slice and routes // Run is the entry point to the cli app. Parses the arguments slice and routes
@ -163,8 +173,20 @@ func (a *App) Setup() {
func (a *App) Run(arguments []string) (err error) { func (a *App) Run(arguments []string) (err error) {
a.Setup() a.Setup()
// handle the completion flag separately from the flagset since
// completion could be attempted after a flag, but before its value was put
// on the command line. this causes the flagset to interpret the completion
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
// parse flags // parse flags
set := flagSet(a.Name, a.Flags) set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:]) err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
@ -174,6 +196,7 @@ func (a *App) Run(arguments []string) (err error) {
ShowAppHelp(context) ShowAppHelp(context)
return nerr return nerr
} }
context.shellComplete = shellComplete
if checkCompletions(context) { if checkCompletions(context) {
return nil return nil
@ -223,7 +246,6 @@ func (a *App) Run(arguments []string) (err error) {
if a.Before != nil { if a.Before != nil {
beforeErr := a.Before(context) beforeErr := a.Before(context)
if beforeErr != nil { if beforeErr != nil {
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
ShowAppHelp(context) ShowAppHelp(context)
HandleExitCoder(beforeErr) HandleExitCoder(beforeErr)
err = beforeErr err = beforeErr
@ -240,6 +262,10 @@ func (a *App) Run(arguments []string) (err error) {
} }
} }
if a.Action == nil {
a.Action = helpCommand.Action
}
// Run default Action // Run default Action
err = a.Action(context) err = a.Action(context)
@ -278,7 +304,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
} }
// parse flags // parse flags
set := flagSet(a.Name, a.Flags) set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail()) err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)

View File

@ -181,6 +181,49 @@ func ExampleApp_Run_commandHelp() {
// This is how we describe describeit the function // This is how we describe describeit the function
} }
func ExampleApp_Run_noAction() {
app := App{}
app.Name = "greet"
app.Run([]string{"greet"})
// Output:
// NAME:
// greet
//
// USAGE:
// [global options] command [command options] [arguments...]
//
// COMMANDS:
// help, h Shows a list of commands or help for one command
//
// GLOBAL OPTIONS:
// --help, -h show help
// --version, -v print the version
}
func ExampleApp_Run_subcommandNoAction() {
app := App{}
app.Name = "greet"
app.Commands = []Command{
{
Name: "describeit",
Aliases: []string{"d"},
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
},
}
app.Run([]string{"greet", "describeit"})
// Output:
// NAME:
// describeit - use it to see a description
//
// USAGE:
// describeit [arguments...]
//
// DESCRIPTION:
// This is how we describe describeit the function
}
func ExampleApp_Run_shellComplete() { func ExampleApp_Run_shellComplete() {
// set args for examples sake // set args for examples sake
os.Args = []string{"greet", "--generate-completion"} os.Args = []string{"greet", "--generate-completion"}
@ -262,6 +305,35 @@ func TestApp_Command(t *testing.T) {
} }
} }
func TestApp_Setup_defaultsWriter(t *testing.T) {
app := &App{}
app.Setup()
expect(t, app.Writer, os.Stdout)
}
func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
var parsedOption, firstArg string
app := NewApp()
command := Command{
Name: "cmd",
Flags: []Flag{
StringFlag{Name: "option", Value: "", Usage: "some option"},
},
Action: func(c *Context) error {
parsedOption = c.String("option")
firstArg = c.Args().First()
return nil
},
}
app.Commands = []Command{command}
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
expect(t, parsedOption, "my-option")
expect(t, firstArg, "my-arg")
}
func TestApp_RunAsSubcommandParseFlags(t *testing.T) { func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
var context *Context var context *Context
@ -1485,3 +1557,224 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
t.Errorf("Expect an intercepted error, but got \"%v\"", err) t.Errorf("Expect an intercepted error, but got \"%v\"", err)
} }
} }
// A custom flag that conforms to the relevant interfaces, but has none of the
// fields that the other flag types do.
type customBoolFlag struct {
Nombre string
}
// Don't use the normal FlagStringer
func (c *customBoolFlag) String() string {
return "***" + c.Nombre + "***"
}
func (c *customBoolFlag) GetName() string {
return c.Nombre
}
func (c *customBoolFlag) Apply(set *flag.FlagSet) {
set.String(c.Nombre, c.Nombre, "")
}
func TestCustomFlagsUnused(t *testing.T) {
app := NewApp()
app.Flags = []Flag{&customBoolFlag{"custom"}}
err := app.Run([]string{"foo"})
if err != nil {
t.Errorf("Run returned unexpected error: %v", err)
}
}
func TestCustomFlagsUsed(t *testing.T) {
app := NewApp()
app.Flags = []Flag{&customBoolFlag{"custom"}}
err := app.Run([]string{"foo", "--custom=bar"})
if err != nil {
t.Errorf("Run returned unexpected error: %v", err)
}
}
func TestCustomHelpVersionFlags(t *testing.T) {
app := NewApp()
// Be sure to reset the global flags
defer func(helpFlag Flag, versionFlag Flag) {
HelpFlag = helpFlag
VersionFlag = versionFlag
}(HelpFlag, VersionFlag)
HelpFlag = &customBoolFlag{"help-custom"}
VersionFlag = &customBoolFlag{"version-custom"}
err := app.Run([]string{"foo", "--help-custom=bar"})
if err != nil {
t.Errorf("Run returned unexpected error: %v", err)
}
}
func TestHandleAction_WithNonFuncAction(t *testing.T) {
app := NewApp()
app.Action = 42
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))
if err == nil {
t.Fatalf("expected to receive error from Run, got none")
}
exitErr, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected to receive a *ExitError")
}
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") {
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
}
if exitErr.ExitCode() != 2 {
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
}
}
func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
app := NewApp()
app.Action = func() string { return "" }
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))
if err == nil {
t.Fatalf("expected to receive error from Run, got none")
}
exitErr, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected to receive a *ExitError")
}
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
}
if exitErr.ExitCode() != 2 {
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
}
}
func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
app := NewApp()
app.Action = func(_ *Context) (int, error) { return 0, nil }
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))
if err == nil {
t.Fatalf("expected to receive error from Run, got none")
}
exitErr, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected to receive a *ExitError")
}
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error())
}
if exitErr.ExitCode() != 2 {
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
}
}
func TestHandleAction_WithUnknownPanic(t *testing.T) {
defer func() { refute(t, recover(), nil) }()
var fn ActionFunc
app := NewApp()
app.Action = func(ctx *Context) error {
fn(ctx)
return nil
}
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
HandleAction(app.Action, NewContext(app, fs, nil))
}
func TestShellCompletionForIncompleteFlags(t *testing.T) {
app := NewApp()
app.Flags = []Flag{
IntFlag{
Name: "test-completion",
},
}
app.EnableBashCompletion = true
app.BashComplete = func(ctx *Context) {
for _, command := range ctx.App.Commands {
if command.Hidden {
continue
}
for _, name := range command.Names() {
fmt.Fprintln(ctx.App.Writer, name)
}
}
for _, flag := range ctx.App.Flags {
for _, name := range strings.Split(flag.GetName(), ",") {
if name == BashCompletionFlag.GetName() {
continue
}
switch name = strings.TrimSpace(name); len(name) {
case 0:
case 1:
fmt.Fprintln(ctx.App.Writer, "-"+name)
default:
fmt.Fprintln(ctx.App.Writer, "--"+name)
}
}
}
}
app.Action = func(ctx *Context) error {
return fmt.Errorf("should not get here")
}
err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.GetName()})
if err != nil {
t.Errorf("app should not return an error: %s", err)
}
}
func TestHandleActionActuallyWorksWithActions(t *testing.T) {
var f ActionFunc
called := false
f = func(c *Context) error {
called = true
return nil
}
err := HandleAction(f, nil)
if err != nil {
t.Errorf("Should not have errored: %v", err)
}
if !called {
t.Errorf("Function was not called")
}
}

View File

@ -1,6 +1,8 @@
version: "{build}" version: "{build}"
os: Windows Server 2012 R2 os: Windows Server 2016
image: Visual Studio 2017
clone_folder: c:\gopath\src\github.com\urfave\cli clone_folder: c:\gopath\src\github.com\urfave\cli
@ -9,9 +11,9 @@ cache:
environment: environment:
GOPATH: C:\gopath GOPATH: C:\gopath
GOVERSION: 1.6 GOVERSION: 1.8.x
PYTHON: C:\Python27-x64 PYTHON: C:\Python36-x64
PYTHON_VERSION: 2.7.x PYTHON_VERSION: 3.6.x
PYTHON_ARCH: 64 PYTHON_ARCH: 64
install: install:

20
autocomplete/bash_autocomplete Normal file → Executable file
View File

@ -3,12 +3,14 @@
: ${PROG:=$(basename ${BASH_SOURCE})} : ${PROG:=$(basename ${BASH_SOURCE})}
_cli_bash_autocomplete() { _cli_bash_autocomplete() {
local cur opts base local cur opts base
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0 return 0
} }
complete -F _cli_bash_autocomplete $PROG complete -F _cli_bash_autocomplete $PROG
unset PROG

1
cli.go
View File

@ -12,6 +12,7 @@
// Usage: "say a greeting", // Usage: "say a greeting",
// Action: func(c *cli.Context) error { // Action: func(c *cli.Context) error {
// println("Greetings") // println("Greetings")
// return nil
// }, // },
// } // }
// //

View File

@ -49,6 +49,25 @@ type Command struct {
// Full name of command for help, defaults to full command name, including parent commands. // Full name of command for help, defaults to full command name, including parent commands.
HelpName string HelpName string
commandNamePath []string commandNamePath []string
// CustomHelpTemplate the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomHelpTemplate string
}
type CommandsByName []Command
func (c CommandsByName) Len() int {
return len(c)
}
func (c CommandsByName) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c CommandsByName) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
} }
// FullName returns the full name of the command. // FullName returns the full name of the command.
@ -75,7 +94,10 @@ func (c *Command) Run(ctx *Context) (err error) {
c.appendFlag(GenerateCompletionFlag) c.appendFlag(GenerateCompletionFlag)
} }
set := flagSet(c.Name, c.Flags) set, err := flagSet(c.Name, c.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
if c.SkipFlagParsing { if c.SkipFlagParsing {
@ -84,18 +106,6 @@ func (c *Command) Run(ctx *Context) (err error) {
err = set.Parse(ctx.Args().Tail()) err = set.Parse(ctx.Args().Tail())
} }
if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(ctx, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
}
nerr := normalizeFlags(c.Flags, set) nerr := normalizeFlags(c.Flags, set)
if nerr != nil { if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr) fmt.Fprintln(ctx.App.Writer, nerr)
@ -105,11 +115,23 @@ func (c *Command) Run(ctx *Context) (err error) {
} }
context := NewContext(ctx.App, set, ctx) context := NewContext(ctx.App, set, ctx)
context.Command = c
if checkCommandCompletions(context, c.Name) { if checkCommandCompletions(context, c.Name) {
return nil return nil
} }
if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(context, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(context.App.Writer)
ShowCommandHelp(context, c.Name)
return err
}
if checkCommandHelp(context, c.Name) { if checkCommandHelp(context, c.Name) {
return nil return nil
} }
@ -131,9 +153,7 @@ func (c *Command) Run(ctx *Context) (err error) {
if c.Before != nil { if c.Before != nil {
err = c.Before(context) err = c.Before(context)
if err != nil { if err != nil {
fmt.Fprintln(ctx.App.Writer, err) ShowCommandHelp(context, c.Name)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
@ -175,14 +195,13 @@ func (c *Command) startApp(ctx *Context) error {
app.HelpName = app.Name app.HelpName = app.Name
} }
if c.Description != "" { app.Usage = c.Usage
app.Usage = c.Description app.Description = c.Description
} else { app.ArgsUsage = c.ArgsUsage
app.Usage = c.Usage
}
// set CommandNotFound // set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound app.CommandNotFound = ctx.App.CommandNotFound
app.CustomAppHelpTemplate = c.CustomHelpTemplate
// set the flags and commands // set the flags and commands
app.Commands = c.Subcommands app.Commands = c.Subcommands
@ -193,6 +212,7 @@ func (c *Command) startApp(ctx *Context) error {
app.HideVersion = ctx.App.HideVersion app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled app.Compiled = ctx.App.Compiled
app.Writer = ctx.App.Writer app.Writer = ctx.App.Writer
app.ErrWriter = ctx.App.ErrWriter
app.Categories = newCommandCategories() app.Categories = newCommandCategories()
for _, command := range c.Subcommands { for _, command := range c.Subcommands {
@ -215,6 +235,7 @@ func (c *Command) startApp(ctx *Context) error {
} else { } else {
app.Action = helpSubcommand.Action app.Action = helpSubcommand.Action
} }
app.OnUsageError = c.OnUsageError
for index, cc := range app.Commands { for index, cc := range app.Commands {
app.Commands[index].commandNamePath = []string{c.Name, cc.Name} app.Commands[index].commandNamePath = []string{c.Name, cc.Name}

View File

@ -123,6 +123,30 @@ func TestCommand_Run_BeforeSavesMetadata(t *testing.T) {
} }
} }
func TestCommand_OnUsageError_hasCommandContext(t *testing.T) {
app := NewApp()
app.Commands = []Command{
{
Name: "bar",
Flags: []Flag{
IntFlag{Name: "flag"},
},
OnUsageError: func(c *Context, err error, _ bool) error {
return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error())
},
},
}
err := app.Run([]string{"foo", "bar", "--flag=wrong"})
if err == nil {
t.Fatalf("expected to receive error from Run, got none")
}
if !strings.HasPrefix(err.Error(), "intercepted in bar") {
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
}
}
func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
app := &App{ app := &App{
Commands: []*Command{ Commands: []*Command{
@ -150,3 +174,64 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
t.Errorf("Expect an intercepted error, but got \"%v\"", err) t.Errorf("Expect an intercepted error, but got \"%v\"", err)
} }
} }
func TestCommand_OnUsageError_WithSubcommand(t *testing.T) {
app := NewApp()
app.Commands = []Command{
{
Name: "bar",
Subcommands: []Command{
{
Name: "baz",
},
},
Flags: []Flag{
IntFlag{Name: "flag"},
},
OnUsageError: func(c *Context, err error, _ bool) error {
if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
t.Errorf("Expect an invalid value error, but got \"%v\"", err)
}
return errors.New("intercepted: " + err.Error())
},
},
}
err := app.Run([]string{"foo", "bar", "--flag=wrong"})
if err == nil {
t.Fatalf("expected to receive error from Run, got none")
}
if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
}
}
func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
app := NewApp()
app.ErrWriter = ioutil.Discard
app.Commands = []Command{
{
Name: "bar",
Usage: "this is for testing",
Subcommands: []Command{
{
Name: "baz",
Usage: "this is for testing",
Action: func(c *Context) error {
if c.App.ErrWriter != ioutil.Discard {
return fmt.Errorf("ErrWriter not passed")
}
return nil
},
},
},
},
}
err := app.Run([]string{"foo", "bar", "baz"})
if err != nil {
t.Fatal(err)
}
}

View File

@ -13,8 +13,9 @@ import (
// can be used to retrieve context-specific args and // can be used to retrieve context-specific args and
// parsed command-line options. // parsed command-line options.
type Context struct { type Context struct {
App *App App *App
Command *Command Command *Command
shellComplete bool
flagSet *flag.FlagSet flagSet *flag.FlagSet
parentContext *Context parentContext *Context
@ -22,7 +23,13 @@ type Context struct {
// NewContext creates a new context. For use in when invoking an App or Command action. // NewContext creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
return &Context{App: app, flagSet: set, parentContext: parentCtx} c := &Context{App: app, flagSet: set, parentContext: parentCtx}
if parentCtx != nil {
c.shellComplete = parentCtx.shellComplete
}
return c
} }
// NumFlags returns the number of flags set // NumFlags returns the number of flags set

View File

@ -166,9 +166,10 @@ func TestContext_IsSet_fromEnv(t *testing.T) {
globalTimeoutIsSet, TIsSet, globalNoEnvVarIsSet, NIsSet bool globalTimeoutIsSet, TIsSet, globalNoEnvVarIsSet, NIsSet bool
) )
os.Clearenv() clearenv()
os.Setenv("GLOBAL_APP_TIMEOUT_SECONDS", "15.5") os.Setenv("GLOBAL_APP_TIMEOUT_SECONDS", "15.5")
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
os.Setenv("APP_PASSWORD", "")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
&Float64Flag{ &Float64Flag{
@ -216,8 +217,17 @@ func TestContext_IsSet_fromEnv(t *testing.T) {
expect(t, NIsSet, false) expect(t, NIsSet, false)
expect(t, timeoutIsSet, true) expect(t, timeoutIsSet, true)
expect(t, tIsSet, true) expect(t, tIsSet, true)
expect(t, passwordIsSet, true)
expect(t, pIsSet, true)
expect(t, noEnvVarIsSet, false) expect(t, noEnvVarIsSet, false)
expect(t, nIsSet, false) expect(t, nIsSet, false)
os.Setenv("APP_UNPARSABLE", "foobar")
if err := a.Run([]string{"run"}); err != nil {
t.Logf("error running Run(): %+v", err)
}
expect(t, unparsableIsSet, false)
expect(t, uIsSet, false)
} }
func TestContext_NumFlags(t *testing.T) { func TestContext_NumFlags(t *testing.T) {
@ -238,8 +248,10 @@ func TestContext_Set(t *testing.T) {
set.Int("int", 5, "an int") set.Int("int", 5, "an int")
c := NewContext(nil, set, nil) c := NewContext(nil, set, nil)
expect(t, c.IsSet("int"), false)
c.Set("int", "1") c.Set("int", "1")
expect(t, c.Int("int"), 1) expect(t, c.Int("int"), 1)
expect(t, c.IsSet("int"), true)
} }
func TestContext_LocalFlagNames(t *testing.T) { func TestContext_LocalFlagNames(t *testing.T) {

View File

@ -48,6 +48,10 @@ func (m *multiError) Errors() []error {
return errs return errs
} }
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
// ExitCoder is the interface checked by `App` and `Command` for a custom exit // ExitCoder is the interface checked by `App` and `Command` for a custom exit
// code // code
type ExitCoder interface { type ExitCoder interface {
@ -57,7 +61,7 @@ type ExitCoder interface {
type exitError struct { type exitError struct {
exitCode int exitCode int
message string message interface{}
} }
// Exit wraps a message and exit code into an ExitCoder suitable for handling by // Exit wraps a message and exit code into an ExitCoder suitable for handling by
@ -80,7 +84,7 @@ func (ee *exitError) ExitCode() int {
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the // so prints the error to stderr (if it is non-empty) and calls OsExiter with the
// given exit code. If the given error is a MultiError, then this func is // given exit code. If the given error is a MultiError, then this func is
// called on all members of the Errors slice. // called on all members of the Errors slice and calls OsExiter with the last exit code.
func HandleExitCoder(err error) { func HandleExitCoder(err error) {
if err == nil { if err == nil {
return return
@ -88,7 +92,11 @@ func HandleExitCoder(err error) {
if exitErr, ok := err.(ExitCoder); ok { if exitErr, ok := err.(ExitCoder); ok {
if err.Error() != "" { if err.Error() != "" {
fmt.Fprintln(ErrWriter, err) if _, ok := exitErr.(ErrorFormatter); ok {
fmt.Fprintf(ErrWriter, "%+v\n", err)
} else {
fmt.Fprintln(ErrWriter, err)
}
} }
OsExiter(exitErr.ExitCode()) OsExiter(exitErr.ExitCode())
return return
@ -100,9 +108,19 @@ func HandleExitCoder(err error) {
} }
return return
} }
}
if err.Error() != "" {
fmt.Fprintln(ErrWriter, err) func handleMultiError(multiErr MultiError) int {
} code := 1
OsExiter(1) for _, merr := range multiErr.Errors {
if multiErr2, ok := merr.(MultiError); ok {
code = handleMultiError(multiErr2)
} else {
fmt.Fprintln(ErrWriter, merr)
if exitErr, ok := merr.(ExitCoder); ok {
code = exitErr.ExitCode()
}
}
}
return code
} }

View File

@ -3,6 +3,7 @@ package cli
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"testing" "testing"
) )
@ -11,8 +12,10 @@ func TestHandleExitCoder_nil(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
exitCode = rc if !called {
called = true exitCode = rc
called = true
}
} }
defer func() { OsExiter = fakeOsExiter }() defer func() { OsExiter = fakeOsExiter }()
@ -28,8 +31,10 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
exitCode = rc if !called {
called = true exitCode = rc
called = true
}
} }
defer func() { OsExiter = fakeOsExiter }() defer func() { OsExiter = fakeOsExiter }()
@ -45,27 +50,43 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
exitCode = rc if !called {
called = true exitCode = rc
called = true
}
} }
defer func() { OsExiter = fakeOsExiter }() defer func() { OsExiter = fakeOsExiter }()
exitErr := Exit("galactic perimeter breach", 9) exitErr := Exit("galactic perimeter breach", 9)
err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) exitErr2 := Exit("last ExitCoder", 11)
err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2)
HandleExitCoder(err) HandleExitCoder(err)
expect(t, exitCode, 9) expect(t, exitCode, 11)
expect(t, called, true) expect(t, called, true)
} }
func TestHandleExitCoder_ErrorWithMessage(t *testing.T) { // make a stub to not import pkg/errors
exitCode := 0 type ErrorWithFormat struct {
error
}
func NewErrorWithFormat(m string) *ErrorWithFormat {
return &ErrorWithFormat{error: errors.New(m)}
}
func (f *ErrorWithFormat) Format(s fmt.State, verb rune) {
fmt.Fprintf(s, "This the format: %v", f.error)
}
func TestHandleExitCoder_ErrorWithFormat(t *testing.T) {
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
exitCode = rc if !called {
called = true called = true
}
} }
ErrWriter = &bytes.Buffer{} ErrWriter = &bytes.Buffer{}
@ -74,33 +95,28 @@ func TestHandleExitCoder_ErrorWithMessage(t *testing.T) {
ErrWriter = fakeErrWriter ErrWriter = fakeErrWriter
}() }()
err := errors.New("gourd havens") err := NewExitError(NewErrorWithFormat("I am formatted"), 1)
HandleExitCoder(err) HandleExitCoder(err)
expect(t, exitCode, 1)
expect(t, called, true) expect(t, called, true)
expect(t, ErrWriter.(*bytes.Buffer).String(), "gourd havens\n") expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: I am formatted\n")
} }
func TestHandleExitCoder_ErrorWithoutMessage(t *testing.T) { func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) {
exitCode := 0
called := false called := false
OsExiter = func(rc int) { OsExiter = func(rc int) {
exitCode = rc if !called {
called = true called = true
}
} }
ErrWriter = &bytes.Buffer{} ErrWriter = &bytes.Buffer{}
defer func() { defer func() { OsExiter = fakeOsExiter }()
OsExiter = fakeOsExiter
ErrWriter = fakeErrWriter
}()
err := errors.New("") err := NewMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2"))
HandleExitCoder(err) HandleExitCoder(err)
expect(t, exitCode, 1)
expect(t, called, true) expect(t, called, true)
expect(t, ErrWriter.(*bytes.Buffer).String(), "") expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: err1\nThis the format: err2\n")
} }

130
flag.go
View File

@ -4,12 +4,12 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"os"
"reflect" "reflect"
"regexp" "regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
) )
@ -88,13 +88,29 @@ type Flag interface {
Names() []string Names() []string
} }
func flagSet(name string, flags []Flag) *flag.FlagSet { // errorableFlag is an interface that allows us to return errors during apply
// it allows flags defined in this library to return errors in a fashion backwards compatible
// TODO remove in v2 and modify the existing Flag interface to return errors
type errorableFlag interface {
Flag
ApplyWithError(*flag.FlagSet) error
}
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError) set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags { for _, f := range flags {
f.Apply(set) //TODO remove in v2 when errorableFlag is removed
if ef, ok := f.(errorableFlag); ok {
if err := ef.ApplyWithError(set); err != nil {
return nil, err
}
} else {
f.Apply(set)
}
} }
return set return set, nil
} }
// Generic is a generic parseable type identified by a specific flag // Generic is a generic parseable type identified by a specific flag
@ -109,7 +125,7 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) {
val := f.Value val := f.Value
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
val.Set(envVal) val.Set(envVal)
break break
} }
@ -166,15 +182,22 @@ func (f *StringSlice) Value() []string {
return f.slice return f.slice
} }
// Get returns the slice of strings set by this flag
func (f *StringSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *StringSliceFlag) Apply(set *flag.FlagSet) { func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
newVal := NewStringSlice() newVal := NewStringSlice()
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
newVal.Set(s) if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
}
} }
f.Value = newVal f.Value = newVal
break break
@ -256,17 +279,21 @@ func (i *IntSlice) Value() []int {
return i.slice return i.slice
} }
// Get returns the slice of ints set by this flag
func (f *IntSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *IntSliceFlag) Apply(set *flag.FlagSet) { func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
newVal := NewIntSlice() newVal := NewIntSlice()
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
err := newVal.Set(s) if err := newVal.Set(s); err != nil {
if err != nil { return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
fmt.Fprintf(ErrWriter, err.Error())
} }
} }
f.Value = newVal f.Value = newVal
@ -329,17 +356,21 @@ func (f *Int64Slice) Value() []int64 {
return f.slice return f.slice
} }
// Get returns the slice of ints set by this flag
func (f *Int64Slice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
newVal := NewInt64Slice() newVal := NewInt64Slice()
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
err := newVal.Set(s) if err := newVal.Set(s); err != nil {
if err != nil { return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
fmt.Fprintf(ErrWriter, err.Error())
} }
} }
f.Value = newVal f.Value = newVal
@ -361,11 +392,13 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
func (f *BoolFlag) Apply(set *flag.FlagSet) { func (f *BoolFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValBool, err := strconv.ParseBool(envVal) envValBool, err := strconv.ParseBool(envVal)
if err == nil { if err == nil {
f.Value = envValBool f.Value = envValBool
} }
val = envValBool
break break
} }
} }
@ -384,7 +417,7 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) {
func (f *StringFlag) Apply(set *flag.FlagSet) { func (f *StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
f.Value = envVal f.Value = envVal
break break
} }
@ -404,12 +437,13 @@ func (f *StringFlag) Apply(set *flag.FlagSet) {
func (f *IntFlag) Apply(set *flag.FlagSet) { func (f *IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64) envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil { if err != nil {
f.Value = int(envValInt) return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
break
} }
f.Value = int(envValInt)
break
} }
} }
} }
@ -427,12 +461,14 @@ func (f *IntFlag) Apply(set *flag.FlagSet) {
func (f *Int64Flag) Apply(set *flag.FlagSet) { func (f *Int64Flag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64) envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil { if err != nil {
f.Value = envValInt return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
break
} }
f.Value = envValInt
break
} }
} }
} }
@ -450,12 +486,14 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) {
func (f *UintFlag) Apply(set *flag.FlagSet) { func (f *UintFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64) envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err == nil { if err != nil {
f.Value = uint(envValInt) return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
break
} }
f.Value = uint(envValInt)
break
} }
} }
} }
@ -473,12 +511,14 @@ func (f *UintFlag) Apply(set *flag.FlagSet) {
func (f *Uint64Flag) Apply(set *flag.FlagSet) { func (f *Uint64Flag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64) envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err == nil { if err != nil {
f.Value = uint64(envValInt) return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
break
} }
f.Value = uint64(envValInt)
break
} }
} }
} }
@ -496,12 +536,14 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) {
func (f *DurationFlag) Apply(set *flag.FlagSet) { func (f *DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValDuration, err := time.ParseDuration(envVal) envValDuration, err := time.ParseDuration(envVal)
if err == nil { if err != nil {
f.Value = envValDuration return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
break
} }
f.Value = envValDuration
break
} }
} }
} }
@ -519,11 +561,14 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) {
func (f *Float64Flag) Apply(set *flag.FlagSet) { func (f *Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
envValFloat, err := strconv.ParseFloat(envVal, 10) envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil { if err != nil {
f.Value = float64(envValFloat) return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
} }
f.Value = float64(envValFloat)
break
} }
} }
} }
@ -591,7 +636,7 @@ func (f *Float64Slice) Value() []float64 {
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) { func (f *Float64SliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil { if f.EnvVars != nil {
for _, envVar := range f.EnvVars { for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" { if envVal, ok := syscall.Getenv(envVar); ok {
newVal := NewFloat64Slice() newVal := NewFloat64Slice()
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
@ -618,7 +663,8 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) {
func visibleFlags(fl []Flag) []Flag { func visibleFlags(fl []Flag) []Flag {
visible := []Flag{} visible := []Flag{}
for _, flag := range fl { for _, flag := range fl {
if !flagValue(flag).FieldByName("Hidden").Bool() { field := flagValue(flag).FieldByName("Hidden")
if !field.IsValid() || !field.Bool() {
visible = append(visible, flag) visible = append(visible, flag)
} }
} }

View File

@ -17,6 +17,7 @@ type BoolFlag struct {
Hidden bool Hidden bool
Value bool Value bool
DefaultText string DefaultText string
Destination *bool Destination *bool
} }
@ -61,6 +62,7 @@ type DurationFlag struct {
Hidden bool Hidden bool
Value time.Duration Value time.Duration
DefaultText string DefaultText string
Destination *time.Duration Destination *time.Duration
} }
@ -105,6 +107,7 @@ type Float64Flag struct {
Hidden bool Hidden bool
Value float64 Value float64
DefaultText string DefaultText string
Destination *float64 Destination *float64
} }
@ -192,6 +195,7 @@ type Int64Flag struct {
Hidden bool Hidden bool
Value int64 Value int64
DefaultText string DefaultText string
Destination *int64 Destination *int64
} }
@ -236,6 +240,7 @@ type IntFlag struct {
Hidden bool Hidden bool
Value int Value int
DefaultText string DefaultText string
Destination *int Destination *int
} }
@ -409,6 +414,7 @@ type StringFlag struct {
Hidden bool Hidden bool
Value string Value string
DefaultText string DefaultText string
Destination *string Destination *string
} }
@ -496,6 +502,7 @@ type Uint64Flag struct {
Hidden bool Hidden bool
Value uint64 Value uint64
DefaultText string DefaultText string
Destination *uint64 Destination *uint64
} }
@ -540,6 +547,7 @@ type UintFlag struct {
Hidden bool Hidden bool
Value uint Value uint
DefaultText string DefaultText string
Destination *uint Destination *uint
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"reflect" "reflect"
"regexp"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@ -41,6 +42,87 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) {
expect(t, v, true) expect(t, v, true)
} }
func TestFlagsFromEnv(t *testing.T) {
var flagTests = []struct {
input string
output interface{}
flag Flag
errRegexp string
}{
{"", false, &BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"1", true, &BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"false", false, &BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
{"foobar", true, &BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)},
{"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVar: "TIME"}, ""},
{"foobar", false, &DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)},
{"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"1", 1.0, &Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"foobar", 0, &Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)},
{"1", int64(1), &Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"1.2", 0, &Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)},
{"foobar", 0, &Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)},
{"1", 1, &IntFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"1.2", 0, &IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)},
{"foobar", 0, &IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)},
{"1,2", NewIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"1.2,2", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)},
{"foobar", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)},
{"1,2", NewInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"1.2,2", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)},
{"foobar", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)},
{"foo", "foo", &StringFlag{Name: "name", EnvVar: "NAME"}, ""},
{"foo,bar", NewStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVar: "NAMES"}, ""},
{"1", uint(1), &UintFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"1.2", 0, &UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)},
{"foobar", 0, &UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)},
{"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
{"1.2", 0, &Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)},
{"foobar", 0, &Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)},
{"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, ""},
}
for _, test := range flagTests {
clearenv()
os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input)
a := App{
Flags: []Flag{test.flag},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.value(test.flag.GetName()), test.output) {
t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.value(test.flag.GetName()))
}
return nil
},
}
err := a.Run([]string{"run"})
if test.errRegexp != "" {
if err == nil {
t.Errorf("expected error to match %s, got none", test.errRegexp)
} else {
if matched, _ := regexp.MatchString(test.errRegexp, err.Error()); !matched {
t.Errorf("expected error to match %s, got error %s", test.errRegexp, err)
}
}
} else {
if err != nil && test.errRegexp == "" {
t.Errorf("expected no error got %s", err)
}
}
}
}
var stringFlagTests = []struct { var stringFlagTests = []struct {
name string name string
aliases []string aliases []string
@ -78,7 +160,7 @@ func TestStringFlagDefaultText(t *testing.T) {
} }
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_FOO", "derp") os.Setenv("APP_FOO", "derp")
for _, test := range stringFlagTests { for _, test := range stringFlagTests {
flag := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} flag := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}}
@ -130,7 +212,7 @@ func TestStringSliceFlagHelpOutput(t *testing.T) {
} }
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_QWWX", "11,4") os.Setenv("APP_QWWX", "11,4")
for _, test := range stringSliceFlagTests { for _, test := range stringSliceFlagTests {
flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}}
@ -175,7 +257,7 @@ func TestIntFlagHelpOutput(t *testing.T) {
} }
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_BAR", "2") os.Setenv("APP_BAR", "2")
for _, test := range intFlagTests { for _, test := range intFlagTests {
flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
@ -222,7 +304,7 @@ func TestInt64FlagHelpOutput(t *testing.T) {
} }
func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_BAR", "2") os.Setenv("APP_BAR", "2")
for _, test := range int64FlagTests { for _, test := range int64FlagTests {
flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
@ -258,7 +340,7 @@ func TestUintFlagHelpOutput(t *testing.T) {
} }
func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { func TestUintFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_BAR", "2") os.Setenv("APP_BAR", "2")
for _, test := range uintFlagTests { for _, test := range uintFlagTests {
flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
@ -294,7 +376,7 @@ func TestUint64FlagHelpOutput(t *testing.T) {
} }
func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_BAR", "2") os.Setenv("APP_BAR", "2")
for _, test := range uint64FlagTests { for _, test := range uint64FlagTests {
flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
@ -330,7 +412,7 @@ func TestDurationFlagHelpOutput(t *testing.T) {
} }
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_BAR", "2h3m6s") os.Setenv("APP_BAR", "2h3m6s")
for _, test := range durationFlagTests { for _, test := range durationFlagTests {
flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
@ -380,7 +462,7 @@ func TestIntSliceFlagHelpOutput(t *testing.T) {
} }
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_SMURF", "42,3") os.Setenv("APP_SMURF", "42,3")
for _, test := range intSliceFlagTests { for _, test := range intSliceFlagTests {
flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}}
@ -429,7 +511,7 @@ func TestInt64SliceFlagHelpOutput(t *testing.T) {
} }
func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_SMURF", "42,17179869184") os.Setenv("APP_SMURF", "42,17179869184")
for _, test := range int64SliceFlagTests { for _, test := range int64SliceFlagTests {
flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}}
@ -465,7 +547,7 @@ func TestFloat64FlagHelpOutput(t *testing.T) {
} }
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_BAZ", "99.4") os.Setenv("APP_BAZ", "99.4")
for _, test := range float64FlagTests { for _, test := range float64FlagTests {
flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}}
@ -516,7 +598,7 @@ func TestFloat64SliceFlagHelpOutput(t *testing.T) {
} }
func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_SMURF", "0.1234,-10.5") os.Setenv("APP_SMURF", "0.1234,-10.5")
for _, test := range float64SliceFlagTests { for _, test := range float64SliceFlagTests {
flag := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} flag := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}}
@ -553,7 +635,7 @@ func TestGenericFlagHelpOutput(t *testing.T) {
} }
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_ZAP", "3") os.Setenv("APP_ZAP", "3")
for _, test := range genericFlagTests { for _, test := range genericFlagTests {
flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}}
@ -615,7 +697,7 @@ func TestParseDestinationString(t *testing.T) {
} }
func TestParseMultiStringFromEnv(t *testing.T) { func TestParseMultiStringFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_COUNT", "20") os.Setenv("APP_COUNT", "20")
(&App{ (&App{
Flags: []Flag{ Flags: []Flag{
@ -634,7 +716,7 @@ func TestParseMultiStringFromEnv(t *testing.T) {
} }
func TestParseMultiStringFromEnvCascade(t *testing.T) { func TestParseMultiStringFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_COUNT", "20") os.Setenv("APP_COUNT", "20")
(&App{ (&App{
Flags: []Flag{ Flags: []Flag{
@ -706,7 +788,7 @@ func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) {
} }
func TestParseMultiStringSliceFromEnv(t *testing.T) { func TestParseMultiStringSliceFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&App{ (&App{
@ -726,7 +808,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) {
} }
func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&App{ (&App{
@ -746,7 +828,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) {
} }
func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&App{ (&App{
@ -766,7 +848,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
} }
func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&App{ (&App{
@ -823,7 +905,7 @@ func TestParseDestinationInt(t *testing.T) {
} }
func TestParseMultiIntFromEnv(t *testing.T) { func TestParseMultiIntFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "10") os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -843,7 +925,7 @@ func TestParseMultiIntFromEnv(t *testing.T) {
} }
func TestParseMultiIntFromEnvCascade(t *testing.T) { func TestParseMultiIntFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "10") os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -914,7 +996,7 @@ func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) {
} }
func TestParseMultiIntSliceFromEnv(t *testing.T) { func TestParseMultiIntSliceFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&App{ (&App{
@ -934,7 +1016,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) {
} }
func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&App{ (&App{
@ -954,7 +1036,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) {
} }
func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&App{ (&App{
@ -991,7 +1073,7 @@ func TestParseMultiInt64Slice(t *testing.T) {
} }
func TestParseMultiInt64SliceFromEnv(t *testing.T) { func TestParseMultiInt64SliceFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,17179869184") os.Setenv("APP_INTERVALS", "20,30,17179869184")
(&App{ (&App{
@ -1011,7 +1093,7 @@ func TestParseMultiInt64SliceFromEnv(t *testing.T) {
} }
func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "20,30,17179869184") os.Setenv("APP_INTERVALS", "20,30,17179869184")
(&App{ (&App{
@ -1068,7 +1150,7 @@ func TestParseDestinationFloat64(t *testing.T) {
} }
func TestParseMultiFloat64FromEnv(t *testing.T) { func TestParseMultiFloat64FromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -1088,7 +1170,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) {
} }
func TestParseMultiFloat64FromEnvCascade(t *testing.T) { func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -1108,7 +1190,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
} }
func TestParseMultiFloat64SliceFromEnv(t *testing.T) { func TestParseMultiFloat64SliceFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "0.1,-10.5") os.Setenv("APP_INTERVALS", "0.1,-10.5")
(&App{ (&App{
@ -1128,7 +1210,7 @@ func TestParseMultiFloat64SliceFromEnv(t *testing.T) {
} }
func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_INTERVALS", "0.1234,-10.5") os.Setenv("APP_INTERVALS", "0.1234,-10.5")
(&App{ (&App{
@ -1185,7 +1267,7 @@ func TestParseDestinationBool(t *testing.T) {
} }
func TestParseMultiBoolFromEnv(t *testing.T) { func TestParseMultiBoolFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_DEBUG", "1") os.Setenv("APP_DEBUG", "1")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -1205,7 +1287,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) {
} }
func TestParseMultiBoolFromEnvCascade(t *testing.T) { func TestParseMultiBoolFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_DEBUG", "1") os.Setenv("APP_DEBUG", "1")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -1264,7 +1346,7 @@ func TestParseDestinationBoolTrue(t *testing.T) {
} }
func TestParseMultiBoolTrueFromEnv(t *testing.T) { func TestParseMultiBoolTrueFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_DEBUG", "0") os.Setenv("APP_DEBUG", "0")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -1289,7 +1371,7 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) {
} }
func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_DEBUG", "0") os.Setenv("APP_DEBUG", "0")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -1331,6 +1413,10 @@ func (p *Parser) String() string {
return fmt.Sprintf("%s,%s", p[0], p[1]) return fmt.Sprintf("%s,%s", p[0], p[1])
} }
func (p *Parser) Get() interface{} {
return p
}
func TestParseGeneric(t *testing.T) { func TestParseGeneric(t *testing.T) {
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -1350,7 +1436,7 @@ func TestParseGeneric(t *testing.T) {
} }
func TestParseGenericFromEnv(t *testing.T) { func TestParseGenericFromEnv(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_SERVE", "20,30") os.Setenv("APP_SERVE", "20,30")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
@ -1375,7 +1461,7 @@ func TestParseGenericFromEnv(t *testing.T) {
} }
func TestParseGenericFromEnvCascade(t *testing.T) { func TestParseGenericFromEnvCascade(t *testing.T) {
os.Clearenv() clearenv()
os.Setenv("APP_FOO", "99,2000") os.Setenv("APP_FOO", "99,2000")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{

View File

@ -104,7 +104,9 @@ def main(sysargs=sys.argv[:]):
def _generate_flag_types(writefunc, output_go, input_json): def _generate_flag_types(writefunc, output_go, input_json):
types = json.load(input_json) types = json.load(input_json)
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False) tmp = tempfile.NamedTemporaryFile(
suffix='.go', mode='w', delete=False, encoding='utf-8'
)
writefunc(tmp, types) writefunc(tmp, types)
tmp.close() tmp.close()
@ -222,11 +224,18 @@ def _write_altsrc_flag_types(outfile, types):
f.set = set f.set = set
f.{name}Flag.Apply(set) f.{name}Flag.Apply(set)
}} }}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.ApplyWithError
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
f.set = set
return f.{name}Flag.ApplyWithError(set)
}}
""".format(**typedef)) """.format(**typedef))
def _fwrite(outfile, text): def _fwrite(outfile, text):
print(textwrap.dedent(text), end='', file=outfile) print(textwrap.dedent(text), end=None, file=outfile)
_WRITEFUNCS = { _WRITEFUNCS = {

102
help.go
View File

@ -13,7 +13,7 @@ import (
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
var AppHelpTemplate = `NAME: var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE: USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
@ -47,7 +47,7 @@ var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{.Usage}}
USAGE: USAGE:
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY: CATEGORY:
{{.Category}}{{end}}{{if .Description}} {{.Category}}{{end}}{{if .Description}}
@ -64,10 +64,10 @@ OPTIONS:
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME: var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
USAGE: USAGE:
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}} COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}} {{.Name}}:{{end}}{{range .VisibleCommands}}
@ -112,17 +112,43 @@ var helpSubcommand = &Command{
// Prints help for the App or Command // Prints help for the App or Command
type helpPrinter func(w io.Writer, templ string, data interface{}) type helpPrinter func(w io.Writer, templ string, data interface{})
// Prints help for the App or Command with custom template function.
type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
// HelpPrinter is a function that writes the help output. If not set a default // HelpPrinter is a function that writes the help output. If not set a default
// is used. The function signature is: // is used. The function signature is:
// func(w io.Writer, templ string, data interface{}) // func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp var HelpPrinter helpPrinter = printHelp
// HelpPrinterCustom is same as HelpPrinter but
// takes a custom function for template function map.
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
// VersionPrinter prints the version for the App // VersionPrinter prints the version for the App
var VersionPrinter = printVersion var VersionPrinter = printVersion
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
func ShowAppHelpAndExit(c *Context, exitCode int) {
ShowAppHelp(c)
os.Exit(exitCode)
}
// ShowAppHelp is an action that displays the help. // ShowAppHelp is an action that displays the help.
func ShowAppHelp(c *Context) { func ShowAppHelp(c *Context) (err error) {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) if c.App.CustomAppHelpTemplate == "" {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
return
}
customAppData := func() map[string]interface{} {
if c.App.ExtraInfo == nil {
return nil
}
return map[string]interface{}{
"ExtraInfo": c.App.ExtraInfo,
}
}
HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData())
return nil
} }
// DefaultAppComplete prints the list of subcommands as the default app completion method // DefaultAppComplete prints the list of subcommands as the default app completion method
@ -137,6 +163,12 @@ func DefaultAppComplete(c *Context) {
} }
} }
// ShowCommandHelpAndExit - exits with code after showing help
func ShowCommandHelpAndExit(c *Context, command string, code int) {
ShowCommandHelp(c, command)
os.Exit(code)
}
// ShowCommandHelp prints help for the given command // ShowCommandHelp prints help for the given command
func ShowCommandHelp(ctx *Context, command string) error { func ShowCommandHelp(ctx *Context, command string) error {
// show the subcommand help for a command with subcommands // show the subcommand help for a command with subcommands
@ -147,7 +179,11 @@ func ShowCommandHelp(ctx *Context, command string) error {
for _, c := range ctx.App.Commands { for _, c := range ctx.App.Commands {
if c.HasName(command) { if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) if c.CustomHelpTemplate != "" {
HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil)
} else {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
}
return nil return nil
} }
} }
@ -198,10 +234,15 @@ func ShowCommandCompletions(ctx *Context, command string) {
} }
} }
func printHelp(out io.Writer, templ string, data interface{}) { func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
funcMap := template.FuncMap{ funcMap := template.FuncMap{
"join": strings.Join, "join": strings.Join,
} }
if customFunc != nil {
for key, value := range customFunc {
funcMap[key] = value
}
}
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
@ -219,6 +260,10 @@ func printHelp(out io.Writer, templ string, data interface{}) {
w.Flush() w.Flush()
} }
func printHelp(out io.Writer, templ string, data interface{}) {
printHelpCustom(out, templ, data, nil)
}
func checkVersion(c *Context) bool { func checkVersion(c *Context) bool {
found := false found := false
if VersionFlag.Name != "" { if VersionFlag.Name != "" {
@ -261,22 +306,45 @@ func checkSubcommandHelp(c *Context) bool {
return false return false
} }
func checkCompletions(c *Context) bool { func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
if c.Bool(GenerateCompletionFlag.Name) && c.App.EnableShellCompletion { if !a.EnableBashCompletion {
ShowCompletions(c) return false, arguments
return true
} }
return false pos := len(arguments) - 1
lastArg := arguments[pos]
if lastArg != "--"+BashCompletionFlag.GetName() {
return false, arguments
}
return true, arguments[:pos]
}
func checkCompletions(c *Context) bool {
if !c.Bool(GenerateCompletionFlag.Name) && !c.App.EnableShellCompletion {
return false
}
if args := c.Args(); args.Present() {
name := args.First()
if cmd := c.App.Command(name); cmd != nil {
// let the command handle the completion
return false
}
}
ShowCompletions(c)
return true
} }
func checkCommandCompletions(c *Context, name string) bool { func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(GenerateCompletionFlag.Name) && c.App.EnableShellCompletion { if !c.Bool(GenerateCompletionFlag.Name) && !c.App.EnableShellCompletion {
ShowCommandCompletions(c, name) return false
return true
} }
return false ShowCommandCompletions(c, name)
return true
} }
func checkInitCompletion(c *Context) (bool, error) { func checkInitCompletion(c *Context) (bool, error) {

View File

@ -3,6 +3,8 @@ package cli
import ( import (
"bytes" "bytes"
"flag" "flag"
"fmt"
"runtime"
"strings" "strings"
"testing" "testing"
) )
@ -255,6 +257,92 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
} }
} }
func TestShowCommandHelp_Customtemplate(t *testing.T) {
app := &App{
Commands: []Command{
{
Name: "frobbly",
Action: func(ctx *Context) error {
return nil
},
HelpName: "foo frobbly",
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} [FLAGS] TARGET [TARGET ...]
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}
EXAMPLES:
1. Frobbly runs with this param locally.
$ {{.HelpName}} wobbly
`,
},
},
}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "help", "frobbly"})
if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") {
t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String())
}
if !strings.Contains(output.String(), "1. Frobbly runs with this param locally.") {
t.Errorf("expected output to include \"1. Frobbly runs with this param locally.\"; got: %q", output.String())
}
if !strings.Contains(output.String(), "$ foo frobbly wobbly") {
t.Errorf("expected output to include \"$ foo frobbly wobbly\"; got: %q", output.String())
}
}
func TestShowSubcommandHelp_CommandUsageText(t *testing.T) {
app := &App{
Commands: []Command{
{
Name: "frobbly",
UsageText: "this is usage text",
},
},
}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "frobbly", "--help"})
if !strings.Contains(output.String(), "this is usage text") {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
app := &App{
Commands: []Command{
{
Name: "frobbly",
Subcommands: []Command{
{
Name: "bobbly",
UsageText: "this is usage text",
},
},
},
},
}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
if !strings.Contains(output.String(), "this is usage text") {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowAppHelp_HiddenCommand(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) {
app := &App{ app := &App{
Commands: []*Command{ Commands: []*Command{
@ -286,3 +374,78 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) {
t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
} }
} }
func TestShowAppHelp_CustomAppTemplate(t *testing.T) {
app := &App{
Commands: []Command{
{
Name: "frobbly",
Action: func(ctx *Context) error {
return nil
},
},
{
Name: "secretfrob",
Hidden: true,
Action: func(ctx *Context) error {
return nil
},
},
},
ExtraInfo: func() map[string]string {
platform := fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH)
goruntime := fmt.Sprintf("Version: %s | CPUs: %d", runtime.Version(), runtime.NumCPU())
return map[string]string{
"PLATFORM": platform,
"RUNTIME": goruntime,
}
},
CustomAppHelpTemplate: `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...]
COMMANDS:
{{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .VisibleFlags}}
GLOBAL FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
VERSION:
2.0.0
{{"\n"}}{{range $key, $value := ExtraInfo}}
{{$key}}:
{{$value}}
{{end}}`,
}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"app", "--help"})
if strings.Contains(output.String(), "secretfrob") {
t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String())
}
if !strings.Contains(output.String(), "frobbly") {
t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
}
if !strings.Contains(output.String(), "PLATFORM:") ||
!strings.Contains(output.String(), "OS:") ||
!strings.Contains(output.String(), "Arch:") {
t.Errorf("expected output to include \"PLATFORM:, OS: and Arch:\"; got: %q", output.String())
}
if !strings.Contains(output.String(), "RUNTIME:") ||
!strings.Contains(output.String(), "Version:") ||
!strings.Contains(output.String(), "CPUs:") {
t.Errorf("expected output to include \"RUNTIME:, Version: and CPUs:\"; got: %q", output.String())
}
if !strings.Contains(output.String(), "VERSION:") ||
!strings.Contains(output.String(), "2.0.0") {
t.Errorf("expected output to include \"VERSION:, 2.0.0\"; got: %q", output.String())
}
}

9
helpers_unix_test.go Normal file
View File

@ -0,0 +1,9 @@
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package cli
import "os"
func clearenv() {
os.Clearenv()
}

20
helpers_windows_test.go Normal file
View File

@ -0,0 +1,20 @@
package cli
import (
"os"
"syscall"
)
// os.Clearenv() doesn't actually unset variables on Windows
// See: https://github.com/golang/go/issues/17902
func clearenv() {
for _, s := range os.Environ() {
for j := 1; j < len(s); j++ {
if s[j] == '=' {
keyp, _ := syscall.UTF16PtrFromString(s[0:j])
syscall.SetEnvironmentVariable(keyp, nil)
break
}
}
}
}