Merge remote-tracking branch 'origin/v1' into merging-from-v1
This commit is contained in:
commit
a61867e5e6
36
.travis.yml
36
.travis.yml
@ -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
|
||||||
|
65
CHANGELOG.md
65
CHANGELOG.md
@ -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
|
||||||
|
33
README.md
33
README.md
@ -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": ["--help"],
|
"args": ["--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 }}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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",
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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
36
app.go
@ -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)
|
||||||
|
293
app_test.go
293
app_test.go
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
10
appveyor.yml
10
appveyor.yml
@ -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
20
autocomplete/bash_autocomplete
Normal file → Executable 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
1
cli.go
@ -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
|
||||||
// },
|
// },
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
65
command.go
65
command.go
@ -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}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
13
context.go
13
context.go
@ -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
|
||||||
|
@ -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) {
|
||||||
|
34
errors.go
34
errors.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
130
flag.go
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
156
flag_test.go
156
flag_test.go
@ -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{
|
||||||
|
@ -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
102
help.go
@ -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) {
|
||||||
|
163
help_test.go
163
help_test.go
@ -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
9
helpers_unix_test.go
Normal 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
20
helpers_windows_test.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user