Merge branch 'v2' into v2-completion

This commit is contained in:
lewo 2016-09-01 09:35:02 +02:00 committed by GitHub
commit 6aa0ab6903
23 changed files with 671 additions and 73 deletions

View File

@ -7,11 +7,11 @@ cache:
- node_modules - node_modules
go: go:
- 1.2.2 - 1.2.x
- 1.3.3 - 1.3.x
- 1.4 - 1.4.2
- 1.5.4 - 1.5.x
- 1.6.2 - 1.6.x
- master - master
env: pip_install="pip install --user" env: pip_install="pip install --user"
@ -20,17 +20,12 @@ matrix:
allow_failures: allow_failures:
- go: master - go: master
include: include:
- go: 1.6.2 - go: 1.6.x
os: osx os: osx
env: pip_install="sudo pip install" env: pip_install="sudo pip install"
before_script: before_script:
- $pip_install flake8 - $pip_install flake8
- go get github.com/urfave/gfmrun/...
- go get golang.org/x/tools/cmd/goimports || true
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
npm install markdown-toc ;
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
@ -38,10 +33,4 @@ before_script:
script: script:
- flake8 runtests cli-v1-to-v2 generate-flag-types - flake8 runtests cli-v1-to-v2 generate-flag-types
- ./runtests gen - make all
- ./runtests vet
- ./runtests test
- ./runtests gfmrun
- ./cli-v1-to-v2 --selftest
- ./runtests migrations
- ./runtests toc

View File

@ -35,6 +35,8 @@
## [Unreleased] - (1.x series) ## [Unreleased] - (1.x series)
### Added ### Added
- 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
- Added support for TOML to the `altsrc` loader
### Changed ### Changed
- Raise minimum tested/supported Go version to 1.2+ - Raise minimum tested/supported Go version to 1.2+

37
GNUmakefile Normal file
View File

@ -0,0 +1,37 @@
default: test
deps:
go get golang.org/x/tools/cmd/goimports || true
go get github.com/urfave/gfmrun/... || true
go list ./... \
| xargs go list -f '{{ join .Deps "\n" }}{{ printf "\n" }}{{ join .TestImports "\n" }}' \
| grep -v github.com/urfave/cli \
| xargs go get
@if [ ! -f node_modules/.bin/markdown-toc ]; then \
npm install markdown-toc ; \
fi
gen: deps
./runtests gen
vet:
./runtests vet
gfmrun:
./runtests gfmrun
v1-to-v2:
./cli-v1-to-v2 --selftest
migrations:
./runtests migrations
toc:
./runtests toc
test: deps
./runtests test
all: gen vet test gfmrun v1-to-v2 migrations toc
.PHONY: default gen vet test gfmrun migrations toc v1-to-v2 deps all

View File

@ -31,7 +31,8 @@ applications in an expressive way.
+ [Placeholder Values](#placeholder-values) + [Placeholder Values](#placeholder-values)
+ [Alternate Names](#alternate-names) + [Alternate Names](#alternate-names)
+ [Values from the Environment](#values-from-the-environment) + [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)
+ [Default Values for help output](#default-values-for-help-output)
* [Subcommands](#subcommands) * [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories) * [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code) * [Exit code](#exit-code)
@ -520,10 +521,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 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 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: code would be added to wrap an existing cli.Flag like below:
@ -545,9 +550,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 the "load" flag used would also have to be defined on the command flags in order
for this code snipped to work. for this code snipped to work.
Currently only YAML and JSON files are supported but developers can add support Currently only the aboved specified formats are supported but developers can
for other input sources by implementing the altsrc.InputSourceContext for their add support for other input sources by implementing the
given sources. altsrc.InputSourceContext for their given sources.
Here is a more complete sample of a command using YAML support: Here is a more complete sample of a command using YAML support:
@ -585,6 +590,48 @@ func main() {
} }
``` ```
#### Default Values for help output
Sometimes it's useful to specify a flag's default help-text value within the flag declaration. This can be useful if the default value for a flag is a computed value. The default value can be set via the `DefaultText` struct field.
For example this:
<!-- {
"args": ["&#45;&#45;help"],
"output": "&#45;&#45;port value"
} -->
```go
package main
import (
"os"
"gopkg.in/urfave/cli.v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.IntFlag{
Name: "port",
Usage: "Use a randomized port",
Value: 0,
DefaultText: "random",
},
},
}
app.Run(os.Args)
}
```
Will result in help output like:
```
--port value Use a randomized port (default: random)
```
### Subcommands ### Subcommands
Subcommands can be defined for a more git-like command line app. Subcommands can be defined for a more git-like command line app.

View File

@ -5,7 +5,7 @@ import (
"os" "os"
"strconv" "strconv"
"github.com/urfave/cli" "gopkg.in/urfave/cli.v2"
) )
// FlagInputSourceExtension is an extension interface of cli.Flag that // FlagInputSourceExtension is an extension interface of cli.Flag that

View File

@ -3,7 +3,7 @@ package altsrc
import ( import (
"flag" "flag"
"github.com/urfave/cli" "gopkg.in/urfave/cli.v2"
) )
// WARNING: This file is generated! // WARNING: This file is generated!

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/urfave/cli" "gopkg.in/urfave/cli.v2"
) )
const ( const (

View File

@ -8,7 +8,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/urfave/cli" "gopkg.in/urfave/cli.v2"
) )
// NewJSONSourceFromFlagFunc returns a func that takes a cli.Context // NewJSONSourceFromFlagFunc returns a func that takes a cli.Context

View File

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

310
altsrc/toml_command_test.go Normal file
View File

@ -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.v2"
)
func TestCommandTomFileTest(t *testing.T) {
app := (&cli.App{})
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.App{})
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", EnvVars: []string{"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.App{})
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", EnvVars: []string{"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.App{})
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.App{})
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.App{})
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.App{})
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.App{})
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, EnvVars: []string{"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.App{})
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, EnvVars: []string{"THE_TEST"}}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
err := command.Run(c)
expect(t, err, nil)
}

113
altsrc/toml_file_loader.go Normal file
View File

@ -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.v2"
)
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
}

View File

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

View File

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

View File

@ -13,6 +13,19 @@ import (
"testing" "testing"
) )
var (
lastExitCode = 0
fakeOsExiter = func(rc int) {
lastExitCode = rc
}
fakeErrWriter = &bytes.Buffer{}
)
func init() {
OsExiter = fakeOsExiter
ErrWriter = fakeErrWriter
}
type opCounts struct { type opCounts struct {
Total, ShellComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int Total, ShellComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int
} }

View File

@ -98,5 +98,11 @@ func HandleExitCoder(err error) {
for _, merr := range multiErr.Errors() { for _, merr := range multiErr.Errors() {
HandleExitCoder(merr) HandleExitCoder(merr)
} }
return
} }
if err.Error() != "" {
fmt.Fprintln(ErrWriter, err)
}
OsExiter(1)
} }

View File

@ -1,8 +1,8 @@
package cli package cli
import ( import (
"bytes"
"errors" "errors"
"os"
"testing" "testing"
) )
@ -15,7 +15,7 @@ func TestHandleExitCoder_nil(t *testing.T) {
called = true called = true
} }
defer func() { OsExiter = os.Exit }() defer func() { OsExiter = fakeOsExiter }()
HandleExitCoder(nil) HandleExitCoder(nil)
@ -32,7 +32,7 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) {
called = true called = true
} }
defer func() { OsExiter = os.Exit }() defer func() { OsExiter = fakeOsExiter }()
HandleExitCoder(Exit("galactic perimeter breach", 9)) HandleExitCoder(Exit("galactic perimeter breach", 9))
@ -49,7 +49,7 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
called = true called = true
} }
defer func() { OsExiter = os.Exit }() 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) err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr)
@ -58,3 +58,49 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
expect(t, exitCode, 9) expect(t, exitCode, 9)
expect(t, called, true) expect(t, called, true)
} }
func TestHandleExitCoder_ErrorWithMessage(t *testing.T) {
exitCode := 0
called := false
OsExiter = func(rc int) {
exitCode = rc
called = true
}
ErrWriter = &bytes.Buffer{}
defer func() {
OsExiter = fakeOsExiter
ErrWriter = fakeErrWriter
}()
err := errors.New("gourd havens")
HandleExitCoder(err)
expect(t, exitCode, 1)
expect(t, called, true)
expect(t, ErrWriter.(*bytes.Buffer).String(), "gourd havens\n")
}
func TestHandleExitCoder_ErrorWithoutMessage(t *testing.T) {
exitCode := 0
called := false
OsExiter = func(rc int) {
exitCode = rc
called = true
}
ErrWriter = &bytes.Buffer{}
defer func() {
OsExiter = fakeOsExiter
ErrWriter = fakeErrWriter
}()
err := errors.New("")
HandleExitCoder(err)
expect(t, exitCode, 1)
expect(t, called, true)
expect(t, ErrWriter.(*bytes.Buffer).String(), "")
}

View File

@ -736,7 +736,6 @@ func stringifyFlag(f Flag) string {
needsPlaceholder := false needsPlaceholder := false
defaultValueString := "" defaultValueString := ""
val := fv.FieldByName("Value") val := fv.FieldByName("Value")
if val.IsValid() { if val.IsValid() {
needsPlaceholder = val.Kind() != reflect.Bool needsPlaceholder = val.Kind() != reflect.Bool
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
@ -746,6 +745,12 @@ func stringifyFlag(f Flag) string {
} }
} }
helpText := fv.FieldByName("DefaultText")
if helpText.IsValid() && helpText.String() != "" {
needsPlaceholder = val.Kind() != reflect.Bool
defaultValueString = fmt.Sprintf(" (default: %s)", helpText.String())
}
if defaultValueString == " (default: )" { if defaultValueString == " (default: )" {
defaultValueString = "" defaultValueString = ""
} }

View File

@ -16,6 +16,7 @@ type BoolFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value bool Value bool
DefaultText string
Destination *bool Destination *bool
} }
@ -59,6 +60,7 @@ type DurationFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value time.Duration Value time.Duration
DefaultText string
Destination *time.Duration Destination *time.Duration
} }
@ -102,6 +104,7 @@ type Float64Flag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value float64 Value float64
DefaultText string
Destination *float64 Destination *float64
} }
@ -145,6 +148,7 @@ type GenericFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value Generic Value Generic
DefaultText string
} }
// String returns a readable representation of this value // String returns a readable representation of this value
@ -187,6 +191,7 @@ type Int64Flag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value int64 Value int64
DefaultText string
Destination *int64 Destination *int64
} }
@ -230,6 +235,7 @@ type IntFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value int Value int
DefaultText string
Destination *int Destination *int
} }
@ -273,6 +279,7 @@ type IntSliceFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value *IntSlice Value *IntSlice
DefaultText string
} }
// String returns a readable representation of this value // String returns a readable representation of this value
@ -315,6 +322,7 @@ type Int64SliceFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value *Int64Slice Value *Int64Slice
DefaultText string
} }
// String returns a readable representation of this value // String returns a readable representation of this value
@ -357,6 +365,7 @@ type Float64SliceFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value *Float64Slice Value *Float64Slice
DefaultText string
} }
// String returns a readable representation of this value // String returns a readable representation of this value
@ -399,6 +408,7 @@ type StringFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value string Value string
DefaultText string
Destination *string Destination *string
} }
@ -442,6 +452,7 @@ type StringSliceFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value *StringSlice Value *StringSlice
DefaultText string
} }
// String returns a readable representation of this value // String returns a readable representation of this value
@ -484,6 +495,7 @@ type Uint64Flag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value uint64 Value uint64
DefaultText string
Destination *uint64 Destination *uint64
} }
@ -527,6 +539,7 @@ type UintFlag struct {
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value uint Value uint
DefaultText string
Destination *uint Destination *uint
} }

View File

@ -67,6 +67,16 @@ func TestStringFlagHelpOutput(t *testing.T) {
} }
} }
func TestStringFlagDefaultText(t *testing.T) {
flag := &StringFlag{Name: "foo", Aliases: nil, Usage: "amount of `foo` requested", Value: "none", DefaultText: "all of it"}
expected := "--foo foo\tamount of foo requested (default: all of it)"
output := flag.String()
if output != expected {
t.Errorf("%q does not match %q", output, expected)
}
}
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_FOO", "derp") os.Setenv("APP_FOO", "derp")
@ -482,7 +492,6 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) {
expect(t, v, float64(43.33333)) expect(t, v, float64(43.33333))
} }
var float64SliceFlagTests = []struct { var float64SliceFlagTests = []struct {
name string name string
aliases []string aliases []string
@ -523,8 +532,6 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
} }
} }
var genericFlagTests = []struct { var genericFlagTests = []struct {
name string name string
value Generic value Generic
@ -1100,7 +1107,6 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
a.Run([]string{"run"}) a.Run([]string{"run"})
} }
func TestParseMultiFloat64SliceFromEnv(t *testing.T) { func TestParseMultiFloat64SliceFromEnv(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_INTERVALS", "0.1,-10.5") os.Setenv("APP_INTERVALS", "0.1,-10.5")
@ -1141,7 +1147,6 @@ func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) {
}).Run([]string{"run"}) }).Run([]string{"run"})
} }
func TestParseMultiBool(t *testing.T) { func TestParseMultiBool(t *testing.T) {
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{

View File

@ -145,6 +145,7 @@ def _write_cli_flag_types(outfile, types):
EnvVars []string EnvVars []string
Hidden bool Hidden bool
Value {type} Value {type}
DefaultText string
""".format(**typedef)) """.format(**typedef))
if typedef['dest']: if typedef['dest']:
@ -193,6 +194,8 @@ def _write_altsrc_flag_types(outfile, types):
_fwrite(outfile, """\ _fwrite(outfile, """\
package altsrc package altsrc
import "gopkg.in/urfave/cli.v2"
// WARNING: This file is generated! // WARNING: This file is generated!
""") """)

View File

@ -61,6 +61,10 @@ def _test():
@_target @_target
def _gfmrun(): 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']) _run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
@ -71,6 +75,11 @@ def _vet():
@_target @_target
def _migrations(): def _migrations():
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
migration_script = os.path.abspath( migration_script = os.path.abspath(
os.environ.get('V1TOV2', './cli-v1-to-v2') os.environ.get('V1TOV2', './cli-v1-to-v2')
) )