Merge branch 'master' into merging-jereksel-zsh

main
Dan Buch 7 years ago committed by GitHub
commit 7250c97913

@ -12,6 +12,7 @@ go:
- 1.4.2
- 1.5.x
- 1.6.x
- 1.7.x
- master
matrix:
@ -20,9 +21,11 @@ matrix:
include:
- go: 1.6.x
os: osx
- go: 1.7.x
os: osx
before_script:
- go get github.com/urfave/gfmrun/...
- go get github.com/urfave/gfmrun/... || true
- go get golang.org/x/tools/... || true
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
npm install markdown-toc ;

@ -3,13 +3,62 @@
**ATTN**: This project uses [semantic versioning](http://semver.org/).
## [Unreleased]
## [1.19.1] - 2016-11-21
### Fixed
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
the `Action` for a command would cause it to error rather than calling the
function. Should not have a affected declarative cases using `func(c
*cli.Context) err)`.
- Shell completion now handles the case where the user specifies
`--generate-bash-completion` immediately after a flag that takes an argument.
Previously it call the application with `--generate-bash-completion` as the
flag value.
## [1.19.0] - 2016-11-19
### Added
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
- A `Description` field was added to `App` for a more detailed description of
the application (similar to the existing `Description` field on `Command`)
- Flag type code generation via `go generate`
- Write to stderr and exit 1 if action returns non-nil error
- Added support for TOML to the `altsrc` loader
- `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
- 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
### Added
- `./runtests` test runner with coverage tracking by default
@ -28,6 +77,10 @@
- No longer swallows `panic`s that occur within the `Action`s themselves when
detecting the signature of the `Action` field
## [1.17.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
## [1.17.0] - 2016-05-09
### Added
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
@ -49,6 +102,10 @@
- cleanups based on [Go Report Card
feedback](https://goreportcard.com/report/github.com/urfave/cli)
## [1.16.1] - 2016-08-28
### Fixed
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
## [1.16.0] - 2016-05-02
### Added
- `Hidden` field on all flag struct types to omit from generated help text

@ -23,15 +23,16 @@ applications in an expressive way.
- [Installation](#installation)
* [Supported platforms](#supported-platforms)
* [Using the `v2` branch](#using-the-v2-branch)
* [Pinning to the `v1` branch](#pinning-to-the-v1-branch)
* [Pinning to the `v1` releases](#pinning-to-the-v1-releases)
- [Getting Started](#getting-started)
- [Examples](#examples)
* [Arguments](#arguments)
* [Flags](#flags)
+ [Placeholder Values](#placeholder-values)
+ [Alternate Names](#alternate-names)
+ [Ordering](#ordering)
+ [Values from the Environment](#values-from-the-environment)
+ [Values from alternate input sources (YAML and others)](#values-from-alternate-input-sources-yaml-and-others)
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
* [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code)
@ -104,11 +105,11 @@ import (
...
```
### Pinning to the `v1` branch
### Pinning to the `v1` releases
Similarly to the section above describing use of the `v2` branch, if one wants
to avoid any unexpected compatibility pains once `v2` becomes `master`, then
pinning to the `v1` branch is an acceptable option, e.g.:
pinning to `v1` is an acceptable option, e.g.:
```
$ go get gopkg.in/urfave/cli.v1
@ -122,6 +123,8 @@ import (
...
```
This will pull the latest tagged `v1` release (e.g. `v1.18.1` at the time of writing).
## Getting Started
One of the philosophies behind cli is that an API should be playful and full of
@ -448,6 +451,56 @@ That flag can then be set with `--lang spanish` or `-l spanish`. Note that
giving two different forms of the same flag in the same command invocation is an
error.
#### Ordering
Flags for the application and commands are shown in the order they are defined.
However, it's possible to sort them from outside this library by using `FlagsByName`
with `sort`.
For example this:
<!-- {
"args": ["&#45;&#45;help"],
"output": "Load configuration from FILE\n.*Language for the greeting.*"
} -->
``` go
package main
import (
"os"
"sort"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "Language for the greeting",
},
cli.StringFlag{
Name: "config, c",
Usage: "Load configuration from `FILE`",
},
}
sort.Sort(cli.FlagsByName(app.Flags))
app.Run(os.Args)
}
```
Will result in help output like:
```
--config FILE, -c FILE Load configuration from FILE
--lang value, -l value Language for the greeting (default: "english")
```
#### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g.
@ -513,10 +566,14 @@ func main() {
}
```
#### Values from alternate input sources (YAML and others)
#### Values from alternate input sources (YAML, TOML, and others)
There is a separate package altsrc that adds support for getting flag values
from other input sources like YAML.
from other file input sources.
Currently supported input source formats:
* YAML
* TOML
In order to get values for a flag from an alternate input source the following
code would be added to wrap an existing cli.Flag like below:
@ -538,9 +595,9 @@ the yaml input source for any flags that are defined on that command. As a note
the "load" flag used would also have to be defined on the command flags in order
for this code snipped to work.
Currently only YAML files are supported but developers can add support for other
input sources by implementing the altsrc.InputSourceContext for their given
sources.
Currently only the aboved specified formats are supported but developers can
add support for other input sources by implementing the
altsrc.InputSourceContext for their given sources.
Here is a more complete sample of a command using YAML support:
@ -843,7 +900,7 @@ func main() {
### Generated Help Text
The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked
The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked
by the cli internals in order to print generated help text for the app, command,
or subcommand, and break execution.
@ -948,12 +1005,12 @@ is checked by the cli internals in order to print the `App.Version` via
#### Customization
The default flag may be cusomized to something other than `-v/--version` by
The default flag may be customized to something other than `-v/--version` by
setting `cli.VersionFlag`, e.g.:
<!-- {
"args": ["&#45;&#45print-version"],
"output": "partay version v19\\.99\\.0"
"output": "partay version 19\\.99\\.0"
} -->
``` go
package main
@ -972,7 +1029,7 @@ func main() {
app := cli.NewApp()
app.Name = "partay"
app.Version = "v19.99.0"
app.Version = "19.99.0"
app.Run(os.Args)
}
```
@ -981,7 +1038,7 @@ Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.
<!-- {
"args": ["&#45;&#45version"],
"output": "version=v19\\.99\\.0 revision=fafafaf"
"output": "version=19\\.99\\.0 revision=fafafaf"
} -->
``` go
package main
@ -1004,7 +1061,7 @@ func main() {
app := cli.NewApp()
app.Name = "partay"
app.Version = "v19.99.0"
app.Version = "19.99.0"
app.Run(os.Args)
}
```
@ -1083,7 +1140,7 @@ func (g *genericType) String() string {
func main() {
app := cli.NewApp()
app.Name = "kənˈtrīv"
app.Version = "v19.99.0"
app.Version = "19.99.0"
app.Compiled = time.Now()
app.Authors = []cli.Author{
cli.Author{

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

@ -3,7 +3,7 @@ package altsrc
import (
"flag"
"github.com/urfave/cli"
"gopkg.in/urfave/cli.v1"
)
// WARNING: This file is generated!
@ -27,6 +27,13 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) {
f.BoolFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped BoolFlag.ApplyWithError
func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.BoolFlag.ApplyWithError(set)
}
// BoolTFlag is the flag type that wraps cli.BoolTFlag to allow
// for other values to be specified
type BoolTFlag struct {
@ -46,6 +53,13 @@ func (f *BoolTFlag) Apply(set *flag.FlagSet) {
f.BoolTFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped BoolTFlag.ApplyWithError
func (f *BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.BoolTFlag.ApplyWithError(set)
}
// DurationFlag is the flag type that wraps cli.DurationFlag to allow
// for other values to be specified
type DurationFlag struct {
@ -65,6 +79,13 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) {
f.DurationFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped DurationFlag.ApplyWithError
func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.DurationFlag.ApplyWithError(set)
}
// Float64Flag is the flag type that wraps cli.Float64Flag to allow
// for other values to be specified
type Float64Flag struct {
@ -84,6 +105,13 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) {
f.Float64Flag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Float64Flag.ApplyWithError
func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Float64Flag.ApplyWithError(set)
}
// GenericFlag is the flag type that wraps cli.GenericFlag to allow
// for other values to be specified
type GenericFlag struct {
@ -103,6 +131,13 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) {
f.GenericFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped GenericFlag.ApplyWithError
func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.GenericFlag.ApplyWithError(set)
}
// Int64Flag is the flag type that wraps cli.Int64Flag to allow
// for other values to be specified
type Int64Flag struct {
@ -122,6 +157,13 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) {
f.Int64Flag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Int64Flag.ApplyWithError
func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Int64Flag.ApplyWithError(set)
}
// IntFlag is the flag type that wraps cli.IntFlag to allow
// for other values to be specified
type IntFlag struct {
@ -141,6 +183,13 @@ func (f *IntFlag) Apply(set *flag.FlagSet) {
f.IntFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped IntFlag.ApplyWithError
func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.IntFlag.ApplyWithError(set)
}
// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow
// for other values to be specified
type IntSliceFlag struct {
@ -160,6 +209,13 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
f.IntSliceFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped IntSliceFlag.ApplyWithError
func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.IntSliceFlag.ApplyWithError(set)
}
// Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow
// for other values to be specified
type Int64SliceFlag struct {
@ -179,6 +235,13 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
f.Int64SliceFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Int64SliceFlag.ApplyWithError
func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Int64SliceFlag.ApplyWithError(set)
}
// StringFlag is the flag type that wraps cli.StringFlag to allow
// for other values to be specified
type StringFlag struct {
@ -198,6 +261,13 @@ func (f *StringFlag) Apply(set *flag.FlagSet) {
f.StringFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped StringFlag.ApplyWithError
func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.StringFlag.ApplyWithError(set)
}
// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow
// for other values to be specified
type StringSliceFlag struct {
@ -217,6 +287,13 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
f.StringSliceFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped StringSliceFlag.ApplyWithError
func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.StringSliceFlag.ApplyWithError(set)
}
// Uint64Flag is the flag type that wraps cli.Uint64Flag to allow
// for other values to be specified
type Uint64Flag struct {
@ -236,6 +313,13 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) {
f.Uint64Flag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Uint64Flag.ApplyWithError
func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Uint64Flag.ApplyWithError(set)
}
// UintFlag is the flag type that wraps cli.UintFlag to allow
// for other values to be specified
type UintFlag struct {
@ -254,3 +338,10 @@ func (f *UintFlag) Apply(set *flag.FlagSet) {
f.set = set
f.UintFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped UintFlag.ApplyWithError
func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.UintFlag.ApplyWithError(set)
}

@ -8,7 +8,7 @@ import (
"testing"
"time"
"github.com/urfave/cli"
"gopkg.in/urfave/cli.v1"
)
type testApplyInputSource struct {

@ -3,7 +3,7 @@ package altsrc
import (
"time"
"github.com/urfave/cli"
"gopkg.in/urfave/cli.v1"
)
// InputSourceContext is an interface used to allow

@ -6,7 +6,7 @@ import (
"strings"
"time"
"github.com/urfave/cli"
"gopkg.in/urfave/cli.v1"
)
// MapInputSource implements InputSourceContext to return

@ -0,0 +1,310 @@
// Disabling building of toml support in cases where golang is 1.0 or 1.1
// as the encoding library is not implemented or supported.
// +build go1.2
package altsrc
import (
"flag"
"io/ioutil"
"os"
"testing"
"gopkg.in/urfave/cli.v1"
)
func TestCommandTomFileTest(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 15)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}
func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
os.Setenv("THE_TEST", "10")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 10)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}
func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
defer os.Remove("current.toml")
os.Setenv("THE_TEST", "10")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("top.test")
expect(t, val, 10)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}
func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml", "--test", "7"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 7)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}
func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte(`[top]
test = 15`), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml", "--top.test", "7"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("top.test")
expect(t, val, 7)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "top.test"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}
func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 15)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}
func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("top.test")
expect(t, val, 15)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}
func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
os.Setenv("THE_TEST", "11")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 11)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}
func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
defer os.Remove("current.toml")
os.Setenv("THE_TEST", "11")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
c := cli.NewContext(app, set, nil)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
val := c.Int("top.test")
expect(t, val, 11)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}

@ -0,0 +1,113 @@
// Disabling building of toml support in cases where golang is 1.0 or 1.1
// as the encoding library is not implemented or supported.
// +build go1.2
package altsrc
import (
"fmt"
"reflect"
"github.com/BurntSushi/toml"
"gopkg.in/urfave/cli.v1"
)
type tomlMap struct {
Map map[interface{}]interface{}
}
func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
ret = make(map[interface{}]interface{})
m := i.(map[string]interface{})
for key, val := range m {
v := reflect.ValueOf(val)
switch v.Kind() {
case reflect.Bool:
ret[key] = val.(bool)
case reflect.String:
ret[key] = val.(string)
case reflect.Int:
ret[key] = int(val.(int))
case reflect.Int8:
ret[key] = int(val.(int8))
case reflect.Int16:
ret[key] = int(val.(int16))
case reflect.Int32:
ret[key] = int(val.(int32))
case reflect.Int64:
ret[key] = int(val.(int64))
case reflect.Uint:
ret[key] = int(val.(uint))
case reflect.Uint8:
ret[key] = int(val.(uint8))
case reflect.Uint16:
ret[key] = int(val.(uint16))
case reflect.Uint32:
ret[key] = int(val.(uint32))
case reflect.Uint64:
ret[key] = int(val.(uint64))
case reflect.Float32:
ret[key] = float64(val.(float32))
case reflect.Float64:
ret[key] = float64(val.(float64))
case reflect.Map:
if tmp, err := unmarshalMap(val); err == nil {
ret[key] = tmp
} else {
return nil, err
}
case reflect.Array:
fallthrough // [todo] - Support array type
default:
return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind())
}
}
return ret, nil
}
func (self *tomlMap) UnmarshalTOML(i interface{}) error {
if tmp, err := unmarshalMap(i); err == nil {
self.Map = tmp
} else {
return err
}
return nil
}
type tomlSourceContext struct {
FilePath string
}
// NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath.
func NewTomlSourceFromFile(file string) (InputSourceContext, error) {
tsc := &tomlSourceContext{FilePath: file}
var results tomlMap = tomlMap{}
if err := readCommandToml(tsc.FilePath, &results); err != nil {
return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error())
}
return &MapInputSource{valueMap: results.Map}, nil
}
// NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context.
func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
return func(context *cli.Context) (InputSourceContext, error) {
filePath := context.String(flagFileName)
return NewTomlSourceFromFile(filePath)
}
}
func readCommandToml(filePath string, container interface{}) (err error) {
b, err := loadDataFrom(filePath)
if err != nil {
return err
}
err = toml.Unmarshal(b, container)
if err != nil {
return err
}
err = nil
return
}

@ -11,7 +11,7 @@ import (
"os"
"testing"
"github.com/urfave/cli"
"gopkg.in/urfave/cli.v1"
)
func TestCommandYamlFileTest(t *testing.T) {

@ -12,7 +12,7 @@ import (
"net/url"
"os"
"github.com/urfave/cli"
"gopkg.in/urfave/cli.v1"
"gopkg.in/yaml.v2"
)

117
app.go

@ -6,9 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"time"
)
@ -19,11 +17,8 @@ var (
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errNonFuncAction = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+
fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
)
@ -42,6 +37,8 @@ type App struct {
ArgsUsage string
// Version of the program
Version string
// Description of the program
Description string
// List of commands to execute
Commands []Command
// List of flags to parse
@ -62,10 +59,11 @@ type App struct {
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc
// The action to execute when no subcommands are specified
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
// *Note*: support for the deprecated `Action` signature will be removed in a future version
Action interface{}
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
// of deprecation period has passed, maybe?
// Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc
@ -147,10 +145,6 @@ func (a *App) Setup() {
}
}
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
if !a.HideVersion {
a.appendFlag(VersionFlag)
}
@ -164,6 +158,10 @@ func (a *App) Setup() {
if a.Metadata == nil {
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
@ -171,8 +169,20 @@ func (a *App) Setup() {
func (a *App) Run(arguments []string) (err error) {
a.Setup()
// handle the completion flag separately from the flagset since
// completion could be attempted after a flag, but before its value was put
// on the command line. this causes the flagset to interpret the completion
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
// parse flags
set := flagSet(a.Name, a.Flags)
set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set)
@ -182,6 +192,7 @@ func (a *App) Run(arguments []string) (err error) {
ShowAppHelp(context)
return nerr
}
context.shellComplete = shellComplete
if checkCompletions(context) {
return nil
@ -193,7 +204,7 @@ func (a *App) Run(arguments []string) (err error) {
HandleExitCoder(err)
return err
}
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowAppHelp(context)
return err
}
@ -240,6 +251,10 @@ func (a *App) Run(arguments []string) (err error) {
}
}
if a.Action == nil {
a.Action = helpCommand.Action
}
// Run default Action
err = HandleAction(a.Action, context)
@ -247,11 +262,12 @@ func (a *App) Run(arguments []string) (err error) {
return err
}
// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
//
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
// to cli.App.Run. This will cause the application to exit with the given eror
// code in the cli.ExitCoder
func (a *App) RunAndExitOnError() {
fmt.Fprintf(a.errWriter(),
"DEPRECATED cli.App.RunAndExitOnError. %s See %s\n",
contactSysadmin, runAndExitOnErrorDeprecationURL)
if err := a.Run(os.Args); err != nil {
fmt.Fprintln(a.errWriter(), err)
OsExiter(1)
@ -280,13 +296,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}
a.Commands = newCmds
// append flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
// parse flags
set, err := flagSet(a.Name, a.Flags)
if err != nil {
return err
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
@ -313,7 +328,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
HandleExitCoder(err)
return err
}
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowSubcommandHelp(context)
return err
}
@ -454,50 +469,24 @@ type Author struct {
func (a Author) String() string {
e := ""
if a.Email != "" {
e = "<" + a.Email + "> "
e = " <" + a.Email + ">"
}
return fmt.Sprintf("%v %v", a.Name, e)
return fmt.Sprintf("%v%v", a.Name, e)
}
// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an
// ActionFunc, a func with the legacy signature for Action, or some other
// invalid thing. If it's an ActionFunc or a func with the legacy signature for
// Action, the func is run!
// HandleAction attempts to figure out which Action signature was used. If
// it's an ActionFunc or a func with the legacy signature for Action, the func
// is run!
func HandleAction(action interface{}, context *Context) (err error) {
defer func() {
if r := recover(); r != nil {
// Try to detect a known reflection error from *this scope*, rather than
// swallowing all panics that may happen when calling an Action func.
s := fmt.Sprintf("%v", r)
if strings.HasPrefix(s, "reflect: ") && strings.Contains(s, "too many input arguments") {
err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2)
} else {
panic(r)
}
}
}()
if reflect.TypeOf(action).Kind() != reflect.Func {
return errNonFuncAction
}
vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)})
if len(vals) == 0 {
fmt.Fprintf(ErrWriter,
"DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n",
contactSysadmin, appActionDeprecationURL)
if a, ok := action.(ActionFunc); ok {
return a(context)
} else if a, ok := action.(func(*Context) error); ok {
return a(context)
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
a(context)
return nil
} else {
return errInvalidActionType
}
if len(vals) > 1 {
return errInvalidActionSignature
}
if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok {
return retErr
}
return err
}

@ -90,7 +90,62 @@ func ExampleApp_Run_subcommand() {
// Hello, Jeremy
}
func ExampleApp_Run_help() {
func ExampleApp_Run_appHelp() {
// set args for examples sake
os.Args = []string{"greet", "help"}
app := NewApp()
app.Name = "greet"
app.Version = "0.1.0"
app.Description = "This is how we describe greet the app"
app.Authors = []Author{
{Name: "Harrison", Email: "harrison@lolwut.com"},
{Name: "Oliver Allen", Email: "oliver@toyshop.com"},
}
app.Flags = []Flag{
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Commands = []Command{
{
Name: "describeit",
Aliases: []string{"d"},
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *Context) error {
fmt.Printf("i like to describe things")
return nil
},
},
}
app.Run(os.Args)
// Output:
// NAME:
// greet - A new cli application
//
// USAGE:
// greet [global options] command [command options] [arguments...]
//
// VERSION:
// 0.1.0
//
// DESCRIPTION:
// This is how we describe greet the app
//
// AUTHORS:
// Harrison <harrison@lolwut.com>
// Oliver Allen <oliver@toyshop.com>
//
// COMMANDS:
// describeit, d use it to see a description
// help, h Shows a list of commands or help for one command
//
// GLOBAL OPTIONS:
// --name value a name to say (default: "bob")
// --help, -h show help
// --version, -v print the version
}
func ExampleApp_Run_commandHelp() {
// set args for examples sake
os.Args = []string{"greet", "h", "describeit"}
@ -123,6 +178,49 @@ func ExampleApp_Run_help() {
// This is how we describe describeit the function
}
func ExampleApp_Run_noAction() {
app := App{}
app.Name = "greet"
app.Run([]string{"greet"})
// Output:
// NAME:
// greet
//
// USAGE:
// [global options] command [command options] [arguments...]
//
// COMMANDS:
// help, h Shows a list of commands or help for one command
//
// GLOBAL OPTIONS:
// --help, -h show help
// --version, -v print the version
}
func ExampleApp_Run_subcommandNoAction() {
app := App{}
app.Name = "greet"
app.Commands = []Command{
{
Name: "describeit",
Aliases: []string{"d"},
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
},
}
app.Run([]string{"greet", "describeit"})
// Output:
// NAME:
// describeit - use it to see a description
//
// USAGE:
// describeit [arguments...]
//
// DESCRIPTION:
// This is how we describe describeit the function
}
func ExampleApp_Run_bashComplete() {
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
@ -240,6 +338,12 @@ 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
@ -290,6 +394,23 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
expect(t, context.String("lang"), "spanish")
}
func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
a := App{
Flags: []Flag{
StringFlag{Name: "--foo"},
},
Writer: bytes.NewBufferString(""),
}
set := flag.NewFlagSet("", flag.ContinueOnError)
set.Parse([]string{"", "---foo"})
c := &Context{flagSet: set}
err := a.RunAsSubcommand(c)
expect(t, err, errors.New("bad flag syntax: ---foo"))
}
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
var parsedOption string
var args []string
@ -1440,7 +1561,11 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
func TestHandleAction_WithNonFuncAction(t *testing.T) {
app := NewApp()
app.Action = 42
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))
if err == nil {
t.Fatalf("expected to receive error from Run, got none")
@ -1452,7 +1577,7 @@ func TestHandleAction_WithNonFuncAction(t *testing.T) {
t.Fatalf("expected to receive a *ExitError")
}
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") {
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
}
@ -1464,7 +1589,11 @@ func TestHandleAction_WithNonFuncAction(t *testing.T) {
func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
app := NewApp()
app.Action = func() string { return "" }
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))
if err == nil {
t.Fatalf("expected to receive error from Run, got none")
@ -1476,7 +1605,7 @@ func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
t.Fatalf("expected to receive a *ExitError")
}
if !strings.HasPrefix(exitErr.Error(), "ERROR unknown Action error") {
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
}
@ -1488,7 +1617,11 @@ func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
app := NewApp()
app.Action = func(_ *Context) (int, error) { return 0, nil }
err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))
if err == nil {
t.Fatalf("expected to receive error from Run, got none")
@ -1500,7 +1633,7 @@ func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
t.Fatalf("expected to receive a *ExitError")
}
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action signature") {
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error())
}
@ -1519,5 +1652,72 @@ func TestHandleAction_WithUnknownPanic(t *testing.T) {
fn(ctx)
return nil
}
HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil))
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
HandleAction(app.Action, NewContext(app, fs, nil))
}
func TestShellCompletionForIncompleteFlags(t *testing.T) {
app := NewApp()
app.Flags = []Flag{
IntFlag{
Name: "test-completion",
},
}
app.EnableBashCompletion = true
app.BashComplete = func(ctx *Context) {
for _, command := range ctx.App.Commands {
if command.Hidden {
continue
}
for _, name := range command.Names() {
fmt.Fprintln(ctx.App.Writer, name)
}
}
for _, flag := range ctx.App.Flags {
for _, name := range strings.Split(flag.GetName(), ",") {
if name == BashCompletionFlag.Name {
continue
}
switch name = strings.TrimSpace(name); len(name) {
case 0:
case 1:
fmt.Fprintln(ctx.App.Writer, "-"+name)
default:
fmt.Fprintln(ctx.App.Writer, "--"+name)
}
}
}
}
app.Action = func(ctx *Context) error {
return fmt.Errorf("should not get here")
}
err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.Name})
if err != nil {
t.Errorf("app should not return an error: %s", err)
}
}
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")
}
}

@ -46,6 +46,11 @@ type Command struct {
Flags []Flag
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Skip argument reordering which attempts to move flags before arguments,
// but only works if all flags appear after all arguments. This behavior was
// removed n version 2 since it only works under specific conditions so we
// backport here by exposing it as an option for compatibility.
SkipArgReorder bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide this command from help or completion
@ -82,14 +87,15 @@ func (c Command) Run(ctx *Context) (err error) {
)
}
if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag)
set, err := flagSet(c.Name, c.Flags)
if err != nil {
return err
}
set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard)
if !c.SkipFlagParsing {
if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
} else if !c.SkipArgReorder {
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
@ -122,21 +128,7 @@ func (c Command) Run(ctx *Context) (err error) {
err = set.Parse(ctx.Args().Tail())
}
} else {
if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, 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.")
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
err = set.Parse(ctx.Args().Tail())
}
nerr := normalizeFlags(c.Flags, set)
@ -148,11 +140,22 @@ func (c Command) Run(ctx *Context) (err error) {
}
context := NewContext(ctx.App, set, ctx)
if checkCommandCompletions(context, c.Name) {
return nil
}
if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(ctx, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
}
if checkCommandHelp(context, c.Name) {
return nil
}
@ -182,6 +185,10 @@ func (c Command) Run(ctx *Context) (err error) {
}
}
if c.Action == nil {
c.Action = helpSubcommand.Action
}
context.Command = c
err = HandleAction(c.Action, context)

@ -13,12 +13,18 @@ func TestCommandFlagParsing(t *testing.T) {
cases := []struct {
testArgs []string
skipFlagParsing bool
skipArgReorder bool
expectedErr error
}{
{[]string{"blah", "blah", "-break"}, false, errors.New("flag provided but not defined: -break")}, // Test normal "not ignoring flags" flow
{[]string{"blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags
{[]string{"blah", "-break"}, true, nil}, // Test SkipFlagParsing with random flag arg
{[]string{"blah", "-help"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg
// Test normal "not ignoring flags" flow
{[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break")},
// Test no arg reorder
{[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil},
{[]string{"test-cmd", "blah", "blah"}, true, false, nil}, // Test SkipFlagParsing without any args that look like flags
{[]string{"test-cmd", "blah", "-break"}, true, false, nil}, // Test SkipFlagParsing with random flag arg
{[]string{"test-cmd", "blah", "-help"}, true, false, nil}, // Test SkipFlagParsing with "special" help flag arg
}
for _, c := range cases {
@ -30,15 +36,15 @@ func TestCommandFlagParsing(t *testing.T) {
context := NewContext(app, set, nil)
command := Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(_ *Context) error { return nil },
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(_ *Context) error { return nil },
SkipFlagParsing: c.skipFlagParsing,
SkipArgReorder: c.skipArgReorder,
}
command.SkipFlagParsing = c.skipFlagParsing
err := command.Run(context)
expect(t, err, c.expectedErr)

@ -3,7 +3,9 @@ package cli
import (
"errors"
"flag"
"reflect"
"strings"
"syscall"
)
// Context is a type that is passed through to
@ -11,17 +13,23 @@ import (
// can be used to retrieve context-specific Args and
// parsed command-line options.
type Context struct {
App *App
Command Command
flagSet *flag.FlagSet
setFlags map[string]bool
globalSetFlags map[string]bool
parentContext *Context
App *App
Command Command
shellComplete bool
flagSet *flag.FlagSet
setFlags map[string]bool
parentContext *Context
}
// NewContext creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
return &Context{App: app, flagSet: set, parentContext: parentCtx}
c := &Context{App: app, flagSet: set, parentContext: parentCtx}
if parentCtx != nil {
c.shellComplete = parentCtx.shellComplete
}
return c
}
// NumFlags returns the number of flags set
@ -43,28 +51,78 @@ func (c *Context) GlobalSet(name, value string) error {
func (c *Context) IsSet(name string) bool {
if c.setFlags == nil {
c.setFlags = make(map[string]bool)
c.flagSet.Visit(func(f *flag.Flag) {
c.setFlags[f.Name] = true
})
c.flagSet.VisitAll(func(f *flag.Flag) {
if _, ok := c.setFlags[f.Name]; ok {
return
}
c.setFlags[f.Name] = false
})
// XXX hack to support IsSet for flags with EnvVar
//
// There isn't an easy way to do this with the current implementation since
// whether a flag was set via an environment variable is very difficult to
// determine here. Instead, we intend to introduce a backwards incompatible
// change in version 2 to add `IsSet` to the Flag interface to push the
// responsibility closer to where the information required to determine
// whether a flag is set by non-standard means such as environment
// variables is avaliable.
//
// See https://github.com/urfave/cli/issues/294 for additional discussion
flags := c.Command.Flags
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
if c.App != nil {
flags = c.App.Flags
}
}
for _, f := range flags {
eachName(f.GetName(), func(name string) {
if isSet, ok := c.setFlags[name]; isSet || !ok {
return
}
val := reflect.ValueOf(f)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
envVarValue := val.FieldByName("EnvVar")
if !envVarValue.IsValid() {
return
}
eachName(envVarValue.String(), func(envVar string) {
envVar = strings.TrimSpace(envVar)
if _, ok := syscall.Getenv(envVar); ok {
c.setFlags[name] = true
return
}
})
})
}
}
return c.setFlags[name] == true
return c.setFlags[name]
}
// GlobalIsSet determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool {
if c.globalSetFlags == nil {
c.globalSetFlags = make(map[string]bool)
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext {
ctx.flagSet.Visit(func(f *flag.Flag) {
c.globalSetFlags[f.Name] = true
})
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if ctx.IsSet(name) {
return true
}
}
return c.globalSetFlags[name]
return false
}
// FlagNames returns a slice of flag names used in this context.
@ -96,6 +154,11 @@ func (c *Context) Parent() *Context {
return c.parentContext
}
// value returns the value of the flag coressponding to `name`
func (c *Context) value(name string) interface{} {
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
}
// Args contains apps console arguments
type Args []string

@ -2,6 +2,7 @@ package cli
import (
"flag"
"os"
"testing"
"time"
)
@ -180,6 +181,52 @@ func TestContext_IsSet(t *testing.T) {
expect(t, c.IsSet("myflagGlobal"), false)
}
// XXX Corresponds to hack in context.IsSet for flags with EnvVar field
// Should be moved to `flag_test` in v2
func TestContext_IsSet_fromEnv(t *testing.T) {
var (
timeoutIsSet, tIsSet bool
noEnvVarIsSet, nIsSet bool
passwordIsSet, pIsSet bool
unparsableIsSet, uIsSet bool
)
clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
os.Setenv("APP_PASSWORD", "")
a := App{
Flags: []Flag{
Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"},
Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"},
Float64Flag{Name: "no-env-var, n"},
},
Action: func(ctx *Context) error {
timeoutIsSet = ctx.IsSet("timeout")
tIsSet = ctx.IsSet("t")
passwordIsSet = ctx.IsSet("password")
pIsSet = ctx.IsSet("p")
unparsableIsSet = ctx.IsSet("unparsable")
uIsSet = ctx.IsSet("u")
noEnvVarIsSet = ctx.IsSet("no-env-var")
nIsSet = ctx.IsSet("n")
return nil
},
}
a.Run([]string{"run"})
expect(t, timeoutIsSet, true)
expect(t, tIsSet, true)
expect(t, passwordIsSet, true)
expect(t, pIsSet, true)
expect(t, noEnvVarIsSet, false)
expect(t, nIsSet, false)
os.Setenv("APP_UNPARSABLE", "foobar")
a.Run([]string{"run"})
expect(t, unparsableIsSet, false)
expect(t, uIsSet, false)
}
func TestContext_GlobalIsSet(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
@ -199,6 +246,61 @@ func TestContext_GlobalIsSet(t *testing.T) {
expect(t, c.GlobalIsSet("bogusGlobal"), false)
}
// XXX Corresponds to hack in context.IsSet for flags with EnvVar field
// Should be moved to `flag_test` in v2
func TestContext_GlobalIsSet_fromEnv(t *testing.T) {
var (
timeoutIsSet, tIsSet bool
noEnvVarIsSet, nIsSet bool
passwordIsSet, pIsSet bool
unparsableIsSet, uIsSet bool
)
clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
os.Setenv("APP_PASSWORD", "")
a := App{
Flags: []Flag{
Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"},
Float64Flag{Name: "no-env-var, n"},
Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"},
},
Commands: []Command{
{
Name: "hello",
Action: func(ctx *Context) error {
timeoutIsSet = ctx.GlobalIsSet("timeout")
tIsSet = ctx.GlobalIsSet("t")
passwordIsSet = ctx.GlobalIsSet("password")
pIsSet = ctx.GlobalIsSet("p")
unparsableIsSet = ctx.GlobalIsSet("unparsable")
uIsSet = ctx.GlobalIsSet("u")
noEnvVarIsSet = ctx.GlobalIsSet("no-env-var")
nIsSet = ctx.GlobalIsSet("n")
return nil
},
},
},
}
if err := a.Run([]string{"run", "hello"}); err != nil {
t.Logf("error running Run(): %+v", err)
}
expect(t, timeoutIsSet, true)
expect(t, tIsSet, true)
expect(t, passwordIsSet, true)
expect(t, pIsSet, true)
expect(t, noEnvVarIsSet, false)
expect(t, nIsSet, false)
os.Setenv("APP_UNPARSABLE", "foobar")
if err := a.Run([]string{"run"}); err != nil {
t.Logf("error running Run(): %+v", err)
}
expect(t, unparsableIsSet, false)
expect(t, uIsSet, false)
}
func TestContext_NumFlags(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")

@ -24,7 +24,7 @@ func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err}
}
// Error implents the error interface.
// Error implements the error interface.
func (m MultiError) Error() string {
errs := make([]string, len(m.Errors))
for i, err := range m.Errors {
@ -34,6 +34,10 @@ func (m MultiError) Error() string {
return strings.Join(errs, "\n")
}
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
// code
type ExitCoder interface {
@ -44,11 +48,11 @@ type ExitCoder interface {
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
type ExitError struct {
exitCode int
message string
message interface{}
}
// NewExitError makes a new *ExitError
func NewExitError(message string, exitCode int) *ExitError {
func NewExitError(message interface{}, exitCode int) *ExitError {
return &ExitError{
exitCode: exitCode,
message: message,
@ -58,7 +62,7 @@ func NewExitError(message string, exitCode int) *ExitError {
// Error returns the string message, fulfilling the interface required by
// `error`
func (ee *ExitError) Error() string {
return ee.message
return fmt.Sprintf("%v", ee.message)
}
// ExitCode returns the exit code, fulfilling the interface required by
@ -78,7 +82,11 @@ func HandleExitCoder(err error) {
if exitErr, ok := err.(ExitCoder); ok {
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())
return
@ -92,7 +100,11 @@ func HandleExitCoder(err error) {
}
if err.Error() != "" {
fmt.Fprintln(ErrWriter, err)
if _, ok := err.(ErrorFormatter); ok {
fmt.Fprintf(ErrWriter, "%+v\n", err)
} else {
fmt.Fprintln(ErrWriter, err)
}
}
OsExiter(1)
}

@ -3,6 +3,7 @@ package cli
import (
"bytes"
"errors"
"fmt"
"testing"
)
@ -104,3 +105,53 @@ func TestHandleExitCoder_ErrorWithoutMessage(t *testing.T) {
expect(t, called, true)
expect(t, ErrWriter.(*bytes.Buffer).String(), "")
}
// make a stub to not import pkg/errors
type ErrorWithFormat struct {
error
}
func 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
OsExiter = func(rc int) {
called = true
}
ErrWriter = &bytes.Buffer{}
defer func() {
OsExiter = fakeOsExiter
ErrWriter = fakeErrWriter
}()
err := NewErrorWithFormat("I am formatted")
HandleExitCoder(err)
expect(t, called, true)
expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: I am formatted\n")
}
func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) {
called := false
OsExiter = func(rc int) {
called = true
}
ErrWriter = &bytes.Buffer{}
defer func() { OsExiter = fakeOsExiter }()
err := NewMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2"))
HandleExitCoder(err)
expect(t, called, true)
expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: err1\nThis the format: err2\n")
}

@ -3,11 +3,11 @@ package cli
import (
"flag"
"fmt"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"syscall"
"time"
)
@ -37,6 +37,21 @@ var HelpFlag = BoolFlag{
// to display a flag.
var FlagStringer FlagStringFunc = stringifyFlag
// FlagsByName is a slice of Flag.
type FlagsByName []Flag
func (f FlagsByName) Len() int {
return len(f)
}
func (f FlagsByName) Less(i, j int) bool {
return f[i].GetName() < f[j].GetName()
}
func (f FlagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
// Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recommended that
// this interface be implemented.
@ -47,13 +62,29 @@ type Flag interface {
GetName() string
}
func flagSet(name string, flags []Flag) *flag.FlagSet {
// errorableFlag is an interface that allows us to return errors during apply
// it allows flags defined in this library to return errors in a fashion backwards compatible
// TODO remove in v2 and modify the existing Flag interface to return errors
type errorableFlag interface {
Flag
ApplyWithError(*flag.FlagSet) error
}
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags {
f.Apply(set)
//TODO remove in v2 when errorableFlag is removed
if ef, ok := f.(errorableFlag); ok {
if err := ef.ApplyWithError(set); err != nil {
return nil, err
}
} else {
f.Apply(set)
}
}
return set
return set, nil
}
func eachName(longName string, fn func(string)) {
@ -72,13 +103,22 @@ type Generic interface {
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
// Ignores parsing errors
func (f GenericFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
val := f.Value
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
val.Set(envVal)
if envVal, ok := syscall.Getenv(envVar); ok {
if err := val.Set(envVal); err != nil {
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
}
break
}
}
@ -87,9 +127,11 @@ func (f GenericFlag) Apply(set *flag.FlagSet) {
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
return nil
}
// StringSlice is an opaque type for []string to satisfy flag.Value
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
type StringSlice []string
// Set appends the string value to the list of values
@ -108,16 +150,29 @@ func (f *StringSlice) Value() []string {
return *f
}
// Get returns the slice of strings set by this flag
func (f *StringSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
newVal.Set(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
@ -131,9 +186,11 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// IntSlice is an opaque type for []int to satisfy flag.Value
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
@ -156,18 +213,28 @@ func (f *IntSlice) Value() []int {
return *f
}
// Get returns the slice of ints set by this flag
func (f *IntSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
err := newVal.Set(s)
if err != nil {
fmt.Fprintf(ErrWriter, err.Error())
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
@ -182,9 +249,11 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Int64Slice is an opaque type for []int to satisfy flag.Value
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
type Int64Slice []int64
// Set parses the value into an integer and appends it to the list of values
@ -207,18 +276,28 @@ func (f *Int64Slice) Value() []int64 {
return *f
}
// Get returns the slice of ints set by this flag
func (f *Int64Slice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := &Int64Slice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
err := newVal.Set(s)
if err != nil {
fmt.Fprintf(ErrWriter, err.Error())
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
@ -233,19 +312,33 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
}
set.Var(f.Value, name, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
val := false
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
val = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
}
}
@ -258,20 +351,35 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
}
set.Bool(name, val, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f BoolTFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
val := true
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
val = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
}
val = envValBool
break
}
}
}
@ -283,14 +391,22 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) {
}
set.Bool(name, val, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f StringFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
f.Value = envVal
break
}
@ -304,19 +420,28 @@ func (f StringFlag) Apply(set *flag.FlagSet) {
}
set.String(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f IntFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil {
f.Value = int(envValInt)
break
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = int(envValInt)
break
}
}
}
@ -328,19 +453,29 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
}
set.Int(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Int64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil {
f.Value = envValInt
break
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValInt
break
}
}
}
@ -352,19 +487,29 @@ func (f Int64Flag) Apply(set *flag.FlagSet) {
}
set.Int64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f UintFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err == nil {
f.Value = uint(envValInt)
break
if err != nil {
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint(envValInt)
break
}
}
}
@ -376,19 +521,29 @@ func (f UintFlag) Apply(set *flag.FlagSet) {
}
set.Uint(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Uint64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err == nil {
f.Value = uint64(envValInt)
break
if err != nil {
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint64(envValInt)
break
}
}
}
@ -400,19 +555,29 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) {
}
set.Uint64(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f DurationFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
envValDuration, err := time.ParseDuration(envVal)
if err == nil {
f.Value = envValDuration
break
if err != nil {
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValDuration
break
}
}
}
@ -424,18 +589,29 @@ func (f DurationFlag) Apply(set *flag.FlagSet) {
}
set.Duration(name, f.Value, f.Usage)
})
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f Float64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
if envVal, ok := syscall.Getenv(envVar); ok {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil {
f.Value = float64(envValFloat)
if err != nil {
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = float64(envValFloat)
break
}
}
}
@ -447,6 +623,8 @@ func (f Float64Flag) Apply(set *flag.FlagSet) {
}
set.Float64(name, f.Value, f.Usage)
})
return nil
}
func visibleFlags(fl []Flag) []Flag {

@ -29,6 +29,81 @@ func TestBoolFlagHelpOutput(t *testing.T) {
}
}
func TestFlagsFromEnv(t *testing.T) {
var flagTests = []struct {
input string
output interface{}
flag Flag
err error
}{
{"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
{"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
{"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
{"foobar", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Errorf(`could not parse foobar as bool value for flag debug: strconv.ParseBool: parsing "foobar": invalid syntax`)},
{"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
{"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
{"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
{"foobar", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Errorf(`could not parse foobar as bool value for flag debug: strconv.ParseBool: parsing "foobar": invalid syntax`)},
{"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, nil},
{"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Errorf(`could not parse foobar as duration for flag time: time: invalid duration foobar`)},
{"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil},
{"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil},
{"foobar", 0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as float64 value for flag seconds: strconv.ParseFloat: parsing "foobar": invalid syntax`)},
{"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil},
{"1.2", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as int value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)},
{"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)},
{"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, nil},
{"1.2", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as int value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)},
{"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)},
{"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil},
{"1.2,2", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2,2 as int slice value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)},
{"foobar", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)},
{"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil},
{"1.2,2", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2,2 as int64 slice value for flag seconds: strconv.ParseInt: parsing "1.2": invalid syntax`)},
{"foobar", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int64 slice value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)},
{"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, nil},
{"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, nil},
{"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, nil},
{"1.2", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as uint value for flag seconds: strconv.ParseUint: parsing "1.2": invalid syntax`)},
{"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)},
{"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil},
{"1.2", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse 1.2 as uint64 value for flag seconds: strconv.ParseUint: parsing "1.2": invalid syntax`)},
{"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint64 value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)},
{"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, nil},
}
for _, test := range flagTests {
os.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 !reflect.DeepEqual(test.err, err) {
t.Errorf("expected error %s, got error %s", test.err, err)
}
}
}
var stringFlagTests = []struct {
name string
usage string
@ -941,6 +1016,38 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) {
a.Run([]string{"run"})
}
func TestParseBoolTFromEnv(t *testing.T) {
var boolTFlagTests = []struct {
input string
output bool
}{
{"", false},
{"1", true},
{"false", false},
{"true", true},
}
for _, test := range boolTFlagTests {
os.Clearenv()
os.Setenv("DEBUG", test.input)
a := App{
Flags: []Flag{
BoolTFlag{Name: "debug, d", EnvVar: "DEBUG"},
},
Action: func(ctx *Context) error {
if ctx.Bool("debug") != test.output {
t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("debug"))
}
if ctx.Bool("d") != test.output {
t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("d"))
}
return nil
},
}
a.Run([]string{"run"})
}
}
func TestParseMultiBoolT(t *testing.T) {
a := App{
Flags: []Flag{
@ -1036,6 +1143,10 @@ func (p *Parser) String() string {
return fmt.Sprintf("%s,%s", p[0], p[1])
}
func (p *Parser) Get() interface{} {
return p
}
func TestParseGeneric(t *testing.T) {
a := App{
Flags: []Flag{

@ -202,6 +202,10 @@ def _write_altsrc_flag_types(outfile, types):
_fwrite(outfile, """\
package altsrc
import (
"gopkg.in/urfave/cli.v1"
)
// WARNING: This file is generated!
""")
@ -228,6 +232,13 @@ def _write_altsrc_flag_types(outfile, types):
f.set = set
f.{name}Flag.Apply(set)
}}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.ApplyWithError
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
f.set = set
return f.{name}Flag.ApplyWithError(set)
}}
""".format(**typedef))

@ -13,27 +13,31 @@ import (
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
{{if .Version}}{{if not .HideVersion}}
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
VERSION:
{{.Version}}
{{end}}{{end}}{{if len .Authors}}
AUTHOR(S):
{{range .Authors}}{{.}}{{end}}
{{end}}{{if .VisibleCommands}}
{{.Version}}{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if len .Authors}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
{{end}}{{end}}{{if .VisibleFlags}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{if .Copyright}}
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT:
{{.Copyright}}
{{end}}
{{.Copyright}}{{end}}
`
// CommandHelpTemplate is the text template for the command help topic.
@ -254,20 +258,43 @@ func checkSubcommandHelp(c *Context) bool {
return false
}
func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
if !a.EnableBashCompletion {
return false, arguments
}
pos := len(arguments) - 1
lastArg := arguments[pos]
if lastArg != "--"+BashCompletionFlag.Name {
return false, arguments
}
return true, arguments[:pos]
}
func checkCompletions(c *Context) bool {
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
ShowCompletions(c)
return true
if !c.shellComplete {
return false
}
return false
if args := c.Args(); args.Present() {
name := args.First()
if cmd := c.App.Command(name); cmd != nil {
// let the command handle the completion
return false
}
}
ShowCompletions(c)
return true
}
func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
ShowCommandCompletions(c, name)
return true
if !c.shellComplete {
return false
}
return false
ShowCommandCompletions(c, name)
return true
}

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

@ -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
}
}
}
}

@ -57,6 +57,10 @@ def _test():
def _gfmrun():
go_version = check_output('go version'.split()).split()[2]
if go_version < 'go1.3':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])

Loading…
Cancel
Save