Merge branch 'v2' into v2-completion

main
lewo 8 years ago committed by GitHub
commit 6aa0ab6903

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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) {

@ -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"
) )

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

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

@ -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(), "")
}

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

@ -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
} }
@ -139,12 +142,13 @@ func lookupFloat64(name string, set *flag.FlagSet) float64 {
// GenericFlag is a flag with type Generic // GenericFlag is a flag with type Generic
type GenericFlag struct { type GenericFlag struct {
Name string Name string
Aliases []string Aliases []string
Usage string Usage string
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
} }
@ -267,12 +273,13 @@ func lookupInt(name string, set *flag.FlagSet) int {
// IntSliceFlag is a flag with type *IntSlice // IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct { type IntSliceFlag struct {
Name string Name string
Aliases []string Aliases []string
Usage string Usage string
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
@ -309,12 +316,13 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int {
// Int64SliceFlag is a flag with type *Int64Slice // Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct { type Int64SliceFlag struct {
Name string Name string
Aliases []string Aliases []string
Usage string Usage string
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
@ -351,12 +359,13 @@ func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
// Float64SliceFlag is a flag with type *Float64Slice // Float64SliceFlag is a flag with type *Float64Slice
type Float64SliceFlag struct { type Float64SliceFlag struct {
Name string Name string
Aliases []string Aliases []string
Usage string Usage string
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
} }
@ -436,12 +446,13 @@ func lookupString(name string, set *flag.FlagSet) string {
// StringSliceFlag is a flag with type *StringSlice // StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct { type StringSliceFlag struct {
Name string Name string
Aliases []string Aliases []string
Usage string Usage string
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
} }

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

@ -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!
""") """)

@ -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')
) )

Loading…
Cancel
Save