Merge branch 'master' into invalidate-isset-cache
This commit is contained in:
commit
4fb301aa38
@ -7,12 +7,9 @@ cache:
|
|||||||
- node_modules
|
- node_modules
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.2.x
|
|
||||||
- 1.3.x
|
|
||||||
- 1.4.2
|
|
||||||
- 1.5.x
|
|
||||||
- 1.6.x
|
- 1.6.x
|
||||||
- 1.7.x
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
- master
|
- master
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
@ -23,10 +20,12 @@ matrix:
|
|||||||
os: osx
|
os: osx
|
||||||
- go: 1.7.x
|
- go: 1.7.x
|
||||||
os: osx
|
os: osx
|
||||||
|
- go: 1.8.x
|
||||||
|
os: osx
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- go get github.com/urfave/gfmrun/... || true
|
- go get github.com/urfave/gfmrun/... || true
|
||||||
- go get golang.org/x/tools/... || true
|
- go get golang.org/x/tools/cmd/goimports
|
||||||
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
|
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
|
||||||
npm install markdown-toc ;
|
npm install markdown-toc ;
|
||||||
fi
|
fi
|
||||||
|
@ -59,7 +59,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) {
|
|||||||
c := runTest(t, testApplyInputSource{
|
c := runTest(t, testApplyInputSource{
|
||||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []string{"hello", "world"},
|
MapValue: []interface{}{"hello", "world"},
|
||||||
})
|
})
|
||||||
expect(t, c.StringSlice("test"), []string{"hello", "world"})
|
expect(t, c.StringSlice("test"), []string{"hello", "world"})
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
|||||||
c := runTest(t, testApplyInputSource{
|
c := runTest(t, testApplyInputSource{
|
||||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []string{"hello", "world"},
|
MapValue: []interface{}{"hello", "world"},
|
||||||
ContextValueString: "ohno",
|
ContextValueString: "ohno",
|
||||||
})
|
})
|
||||||
expect(t, c.StringSlice("test"), []string{"ohno"})
|
expect(t, c.StringSlice("test"), []string{"ohno"})
|
||||||
@ -78,7 +78,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|||||||
c := runTest(t, testApplyInputSource{
|
c := runTest(t, testApplyInputSource{
|
||||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}),
|
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []string{"hello", "world"},
|
MapValue: []interface{}{"hello", "world"},
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "oh,no",
|
EnvVarValue: "oh,no",
|
||||||
})
|
})
|
||||||
@ -89,7 +89,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) {
|
|||||||
c := runTest(t, testApplyInputSource{
|
c := runTest(t, testApplyInputSource{
|
||||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []int{1, 2},
|
MapValue: []interface{}{1, 2},
|
||||||
})
|
})
|
||||||
expect(t, c.IntSlice("test"), []int{1, 2})
|
expect(t, c.IntSlice("test"), []int{1, 2})
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
|||||||
c := runTest(t, testApplyInputSource{
|
c := runTest(t, testApplyInputSource{
|
||||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []int{1, 2},
|
MapValue: []interface{}{1, 2},
|
||||||
ContextValueString: "3",
|
ContextValueString: "3",
|
||||||
})
|
})
|
||||||
expect(t, c.IntSlice("test"), []int{3})
|
expect(t, c.IntSlice("test"), []int{3})
|
||||||
@ -108,7 +108,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|||||||
c := runTest(t, testApplyInputSource{
|
c := runTest(t, testApplyInputSource{
|
||||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}),
|
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []int{1, 2},
|
MapValue: []interface{}{1, 2},
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "3,4",
|
EnvVarValue: "3,4",
|
||||||
})
|
})
|
||||||
|
@ -130,45 +130,59 @@ func (fsm *MapInputSource) String(name string) (string, error) {
|
|||||||
// StringSlice returns an []string from the map if it exists otherwise returns nil
|
// StringSlice returns an []string from the map if it exists otherwise returns nil
|
||||||
func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
|
func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
if exists {
|
if !exists {
|
||||||
otherValue, isType := otherGenericValue.([]string)
|
otherGenericValue, exists = nestedVal(name, fsm.valueMap)
|
||||||
if !isType {
|
if !exists {
|
||||||
return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue)
|
return nil, nil
|
||||||
}
|
}
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.([]string)
|
|
||||||
if !isType {
|
|
||||||
return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
otherValue, isType := otherGenericValue.([]interface{})
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringSlice = make([]string, 0, len(otherValue))
|
||||||
|
for i, v := range otherValue {
|
||||||
|
stringValue, isType := v.(string)
|
||||||
|
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "string", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
stringSlice = append(stringSlice, stringValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringSlice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntSlice returns an []int from the map if it exists otherwise returns nil
|
// IntSlice returns an []int from the map if it exists otherwise returns nil
|
||||||
func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
|
func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
if exists {
|
if !exists {
|
||||||
otherValue, isType := otherGenericValue.([]int)
|
otherGenericValue, exists = nestedVal(name, fsm.valueMap)
|
||||||
if !isType {
|
if !exists {
|
||||||
return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue)
|
return nil, nil
|
||||||
}
|
}
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.([]int)
|
|
||||||
if !isType {
|
|
||||||
return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
otherValue, isType := otherGenericValue.([]interface{})
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
var intSlice = make([]int, 0, len(otherValue))
|
||||||
|
for i, v := range otherValue {
|
||||||
|
intValue, isType := v.(int)
|
||||||
|
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "int", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
intSlice = append(intSlice, intValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return intSlice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic returns an cli.Generic from the map if it exists otherwise returns nil
|
// Generic returns an cli.Generic from the map if it exists otherwise returns nil
|
||||||
|
@ -57,8 +57,8 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
|
|||||||
} else {
|
} else {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case reflect.Array:
|
case reflect.Array, reflect.Slice:
|
||||||
fallthrough // [todo] - Support array type
|
ret[key] = val.([]interface{})
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind())
|
return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind())
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
|
||||||
@ -78,6 +80,12 @@ func loadDataFrom(filePath string) ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
|
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
|
||||||
}
|
}
|
||||||
return ioutil.ReadFile(filePath)
|
return ioutil.ReadFile(filePath)
|
||||||
|
} else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") {
|
||||||
|
// on Windows systems u.Path is always empty, so we need to check the string directly.
|
||||||
|
if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
|
||||||
|
}
|
||||||
|
return ioutil.ReadFile(filePath)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
|
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
|
||||||
}
|
}
|
||||||
|
6
app.go
6
app.go
@ -85,6 +85,12 @@ type App struct {
|
|||||||
ErrWriter io.Writer
|
ErrWriter io.Writer
|
||||||
// Other custom info
|
// Other custom info
|
||||||
Metadata map[string]interface{}
|
Metadata map[string]interface{}
|
||||||
|
// Carries a function which returns app specific info.
|
||||||
|
ExtraInfo func() map[string]string
|
||||||
|
// CustomAppHelpTemplate the text template for app help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
CustomAppHelpTemplate string
|
||||||
|
|
||||||
didSetup bool
|
didSetup bool
|
||||||
}
|
}
|
||||||
|
61
app_test.go
61
app_test.go
@ -1520,6 +1520,63 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A custom flag that conforms to the relevant interfaces, but has none of the
|
||||||
|
// fields that the other flag types do.
|
||||||
|
type customBoolFlag struct {
|
||||||
|
Nombre string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use the normal FlagStringer
|
||||||
|
func (c *customBoolFlag) String() string {
|
||||||
|
return "***" + c.Nombre + "***"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *customBoolFlag) GetName() string {
|
||||||
|
return c.Nombre
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *customBoolFlag) Apply(set *flag.FlagSet) {
|
||||||
|
set.String(c.Nombre, c.Nombre, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomFlagsUnused(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
app.Flags = []Flag{&customBoolFlag{"custom"}}
|
||||||
|
|
||||||
|
err := app.Run([]string{"foo"})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Run returned unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomFlagsUsed(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
app.Flags = []Flag{&customBoolFlag{"custom"}}
|
||||||
|
|
||||||
|
err := app.Run([]string{"foo", "--custom=bar"})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Run returned unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomHelpVersionFlags(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
// Be sure to reset the global flags
|
||||||
|
defer func(helpFlag Flag, versionFlag Flag) {
|
||||||
|
HelpFlag = helpFlag
|
||||||
|
VersionFlag = versionFlag
|
||||||
|
}(HelpFlag, VersionFlag)
|
||||||
|
|
||||||
|
HelpFlag = &customBoolFlag{"help-custom"}
|
||||||
|
VersionFlag = &customBoolFlag{"version-custom"}
|
||||||
|
|
||||||
|
err := app.Run([]string{"foo", "--help-custom=bar"})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Run returned unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandleAction_WithNonFuncAction(t *testing.T) {
|
func TestHandleAction_WithNonFuncAction(t *testing.T) {
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
app.Action = 42
|
app.Action = 42
|
||||||
@ -1642,7 +1699,7 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) {
|
|||||||
|
|
||||||
for _, flag := range ctx.App.Flags {
|
for _, flag := range ctx.App.Flags {
|
||||||
for _, name := range strings.Split(flag.GetName(), ",") {
|
for _, name := range strings.Split(flag.GetName(), ",") {
|
||||||
if name == BashCompletionFlag.Name {
|
if name == BashCompletionFlag.GetName() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1659,7 +1716,7 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) {
|
|||||||
app.Action = func(ctx *Context) error {
|
app.Action = func(ctx *Context) error {
|
||||||
return fmt.Errorf("should not get here")
|
return fmt.Errorf("should not get here")
|
||||||
}
|
}
|
||||||
err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.Name})
|
err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.GetName()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("app should not return an error: %s", err)
|
t.Errorf("app should not return an error: %s", err)
|
||||||
}
|
}
|
||||||
|
20
autocomplete/bash_autocomplete
Normal file → Executable file
20
autocomplete/bash_autocomplete
Normal file → Executable file
@ -3,12 +3,14 @@
|
|||||||
: ${PROG:=$(basename ${BASH_SOURCE})}
|
: ${PROG:=$(basename ${BASH_SOURCE})}
|
||||||
|
|
||||||
_cli_bash_autocomplete() {
|
_cli_bash_autocomplete() {
|
||||||
local cur opts base
|
local cur opts base
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
complete -F _cli_bash_autocomplete $PROG
|
complete -F _cli_bash_autocomplete $PROG
|
||||||
|
|
||||||
|
unset PROG
|
||||||
|
32
command.go
32
command.go
@ -59,6 +59,11 @@ type Command struct {
|
|||||||
// Full name of command for help, defaults to full command name, including parent commands.
|
// Full name of command for help, defaults to full command name, including parent commands.
|
||||||
HelpName string
|
HelpName string
|
||||||
commandNamePath []string
|
commandNamePath []string
|
||||||
|
|
||||||
|
// CustomHelpTemplate the text template for the command help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
CustomHelpTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandsByName []Command
|
type CommandsByName []Command
|
||||||
@ -154,19 +159,20 @@ func (c Command) Run(ctx *Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context := NewContext(ctx.App, set, ctx)
|
context := NewContext(ctx.App, set, ctx)
|
||||||
|
context.Command = c
|
||||||
if checkCommandCompletions(context, c.Name) {
|
if checkCommandCompletions(context, c.Name) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.OnUsageError != nil {
|
if c.OnUsageError != nil {
|
||||||
err := c.OnUsageError(ctx, err, false)
|
err := c.OnUsageError(context, err, false)
|
||||||
HandleExitCoder(err)
|
HandleExitCoder(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
|
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
fmt.Fprintln(context.App.Writer)
|
||||||
ShowCommandHelp(ctx, c.Name)
|
ShowCommandHelp(context, c.Name)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,9 +197,9 @@ func (c Command) Run(ctx *Context) (err error) {
|
|||||||
if c.Before != nil {
|
if c.Before != nil {
|
||||||
err = c.Before(context)
|
err = c.Before(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(ctx.App.Writer, err)
|
fmt.Fprintln(context.App.Writer, err)
|
||||||
fmt.Fprintln(ctx.App.Writer)
|
fmt.Fprintln(context.App.Writer)
|
||||||
ShowCommandHelp(ctx, c.Name)
|
ShowCommandHelp(context, c.Name)
|
||||||
HandleExitCoder(err)
|
HandleExitCoder(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -203,7 +209,6 @@ func (c Command) Run(ctx *Context) (err error) {
|
|||||||
c.Action = helpSubcommand.Action
|
c.Action = helpSubcommand.Action
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Command = c
|
|
||||||
err = HandleAction(c.Action, context)
|
err = HandleAction(c.Action, context)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -244,14 +249,13 @@ func (c Command) startApp(ctx *Context) error {
|
|||||||
app.HelpName = app.Name
|
app.HelpName = app.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Description != "" {
|
app.Usage = c.Usage
|
||||||
app.Usage = c.Description
|
app.Description = c.Description
|
||||||
} else {
|
app.ArgsUsage = c.ArgsUsage
|
||||||
app.Usage = c.Usage
|
|
||||||
}
|
|
||||||
|
|
||||||
// set CommandNotFound
|
// set CommandNotFound
|
||||||
app.CommandNotFound = ctx.App.CommandNotFound
|
app.CommandNotFound = ctx.App.CommandNotFound
|
||||||
|
app.CustomAppHelpTemplate = c.CustomHelpTemplate
|
||||||
|
|
||||||
// set the flags and commands
|
// set the flags and commands
|
||||||
app.Commands = c.Subcommands
|
app.Commands = c.Subcommands
|
||||||
@ -264,6 +268,7 @@ func (c Command) startApp(ctx *Context) error {
|
|||||||
app.Author = ctx.App.Author
|
app.Author = ctx.App.Author
|
||||||
app.Email = ctx.App.Email
|
app.Email = ctx.App.Email
|
||||||
app.Writer = ctx.App.Writer
|
app.Writer = ctx.App.Writer
|
||||||
|
app.ErrWriter = ctx.App.ErrWriter
|
||||||
|
|
||||||
app.categories = CommandCategories{}
|
app.categories = CommandCategories{}
|
||||||
for _, command := range c.Subcommands {
|
for _, command := range c.Subcommands {
|
||||||
@ -286,6 +291,7 @@ func (c Command) startApp(ctx *Context) error {
|
|||||||
} else {
|
} else {
|
||||||
app.Action = helpSubcommand.Action
|
app.Action = helpSubcommand.Action
|
||||||
}
|
}
|
||||||
|
app.OnUsageError = c.OnUsageError
|
||||||
|
|
||||||
for index, cc := range app.Commands {
|
for index, cc := range app.Commands {
|
||||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
||||||
|
@ -127,6 +127,30 @@ func TestCommand_Run_BeforeSavesMetadata(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommand_OnUsageError_hasCommandContext(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
app.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Flags: []Flag{
|
||||||
|
IntFlag{Name: "flag"},
|
||||||
|
},
|
||||||
|
OnUsageError: func(c *Context, err error, _ bool) error {
|
||||||
|
return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run([]string{"foo", "bar", "--flag=wrong"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected to receive error from Run, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(err.Error(), "intercepted in bar") {
|
||||||
|
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
|
func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
app.Commands = []Command{
|
app.Commands = []Command{
|
||||||
@ -153,3 +177,64 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
|
|||||||
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
|
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommand_OnUsageError_WithSubcommand(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
app.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Subcommands: []Command{
|
||||||
|
{
|
||||||
|
Name: "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Flags: []Flag{
|
||||||
|
IntFlag{Name: "flag"},
|
||||||
|
},
|
||||||
|
OnUsageError: func(c *Context, err error, _ bool) error {
|
||||||
|
if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
|
||||||
|
t.Errorf("Expect an invalid value error, but got \"%v\"", err)
|
||||||
|
}
|
||||||
|
return errors.New("intercepted: " + err.Error())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run([]string{"foo", "bar", "--flag=wrong"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected to receive error from Run, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
|
||||||
|
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
app.ErrWriter = ioutil.Discard
|
||||||
|
app.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Subcommands: []Command{
|
||||||
|
{
|
||||||
|
Name: "baz",
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Action: func(c *Context) error {
|
||||||
|
if c.App.ErrWriter != ioutil.Discard {
|
||||||
|
return fmt.Errorf("ErrWriter not passed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run([]string{"foo", "bar", "baz"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
23
errors.go
23
errors.go
@ -74,7 +74,7 @@ func (ee *ExitError) ExitCode() int {
|
|||||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
||||||
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
||||||
// given exit code. If the given error is a MultiError, then this func is
|
// given exit code. If the given error is a MultiError, then this func is
|
||||||
// called on all members of the Errors slice.
|
// called on all members of the Errors slice and calls OsExiter with the last exit code.
|
||||||
func HandleExitCoder(err error) {
|
func HandleExitCoder(err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
@ -93,18 +93,23 @@ func HandleExitCoder(err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if multiErr, ok := err.(MultiError); ok {
|
if multiErr, ok := err.(MultiError); ok {
|
||||||
for _, merr := range multiErr.Errors {
|
code := handleMultiError(multiErr)
|
||||||
HandleExitCoder(merr)
|
OsExiter(code)
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err.Error() != "" {
|
func handleMultiError(multiErr MultiError) int {
|
||||||
if _, ok := err.(ErrorFormatter); ok {
|
code := 1
|
||||||
fmt.Fprintf(ErrWriter, "%+v\n", err)
|
for _, merr := range multiErr.Errors {
|
||||||
|
if multiErr2, ok := merr.(MultiError); ok {
|
||||||
|
code = handleMultiError(multiErr2)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(ErrWriter, err)
|
fmt.Fprintln(ErrWriter, merr)
|
||||||
|
if exitErr, ok := merr.(ExitCoder); ok {
|
||||||
|
code = exitErr.ExitCode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OsExiter(1)
|
return code
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,10 @@ func TestHandleExitCoder_nil(t *testing.T) {
|
|||||||
called := false
|
called := false
|
||||||
|
|
||||||
OsExiter = func(rc int) {
|
OsExiter = func(rc int) {
|
||||||
exitCode = rc
|
if !called {
|
||||||
called = true
|
exitCode = rc
|
||||||
|
called = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { OsExiter = fakeOsExiter }()
|
defer func() { OsExiter = fakeOsExiter }()
|
||||||
@ -29,8 +31,10 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) {
|
|||||||
called := false
|
called := false
|
||||||
|
|
||||||
OsExiter = func(rc int) {
|
OsExiter = func(rc int) {
|
||||||
exitCode = rc
|
if !called {
|
||||||
called = true
|
exitCode = rc
|
||||||
|
called = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { OsExiter = fakeOsExiter }()
|
defer func() { OsExiter = fakeOsExiter }()
|
||||||
@ -46,66 +50,23 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
|
|||||||
called := false
|
called := false
|
||||||
|
|
||||||
OsExiter = func(rc int) {
|
OsExiter = func(rc int) {
|
||||||
exitCode = rc
|
if !called {
|
||||||
called = true
|
exitCode = rc
|
||||||
|
called = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { OsExiter = fakeOsExiter }()
|
defer func() { OsExiter = fakeOsExiter }()
|
||||||
|
|
||||||
exitErr := NewExitError("galactic perimeter breach", 9)
|
exitErr := NewExitError("galactic perimeter breach", 9)
|
||||||
err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr)
|
exitErr2 := NewExitError("last ExitCoder", 11)
|
||||||
|
err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2)
|
||||||
HandleExitCoder(err)
|
HandleExitCoder(err)
|
||||||
|
|
||||||
expect(t, exitCode, 9)
|
expect(t, exitCode, 11)
|
||||||
expect(t, called, true)
|
expect(t, called, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleExitCoder_ErrorWithMessage(t *testing.T) {
|
|
||||||
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(), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a stub to not import pkg/errors
|
// make a stub to not import pkg/errors
|
||||||
type ErrorWithFormat struct {
|
type ErrorWithFormat struct {
|
||||||
error
|
error
|
||||||
@ -123,7 +84,9 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) {
|
|||||||
called := false
|
called := false
|
||||||
|
|
||||||
OsExiter = func(rc int) {
|
OsExiter = func(rc int) {
|
||||||
called = true
|
if !called {
|
||||||
|
called = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ErrWriter = &bytes.Buffer{}
|
ErrWriter = &bytes.Buffer{}
|
||||||
|
|
||||||
@ -132,7 +95,7 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) {
|
|||||||
ErrWriter = fakeErrWriter
|
ErrWriter = fakeErrWriter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := NewErrorWithFormat("I am formatted")
|
err := NewExitError(NewErrorWithFormat("I am formatted"), 1)
|
||||||
HandleExitCoder(err)
|
HandleExitCoder(err)
|
||||||
|
|
||||||
expect(t, called, true)
|
expect(t, called, true)
|
||||||
@ -143,7 +106,9 @@ func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) {
|
|||||||
called := false
|
called := false
|
||||||
|
|
||||||
OsExiter = func(rc int) {
|
OsExiter = func(rc int) {
|
||||||
called = true
|
if !called {
|
||||||
|
called = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ErrWriter = &bytes.Buffer{}
|
ErrWriter = &bytes.Buffer{}
|
||||||
|
|
||||||
|
12
flag.go
12
flag.go
@ -14,13 +14,13 @@ import (
|
|||||||
const defaultPlaceholder = "value"
|
const defaultPlaceholder = "value"
|
||||||
|
|
||||||
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
||||||
var BashCompletionFlag = BoolFlag{
|
var BashCompletionFlag Flag = BoolFlag{
|
||||||
Name: "generate-bash-completion",
|
Name: "generate-bash-completion",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersionFlag prints the version for the application
|
// VersionFlag prints the version for the application
|
||||||
var VersionFlag = BoolFlag{
|
var VersionFlag Flag = BoolFlag{
|
||||||
Name: "version, v",
|
Name: "version, v",
|
||||||
Usage: "print the version",
|
Usage: "print the version",
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ var VersionFlag = BoolFlag{
|
|||||||
// HelpFlag prints the help for all commands and subcommands
|
// HelpFlag prints the help for all commands and subcommands
|
||||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
||||||
// unless HideHelp is set to true)
|
// unless HideHelp is set to true)
|
||||||
var HelpFlag = BoolFlag{
|
var HelpFlag Flag = BoolFlag{
|
||||||
Name: "help, h",
|
Name: "help, h",
|
||||||
Usage: "show help",
|
Usage: "show help",
|
||||||
}
|
}
|
||||||
@ -630,7 +630,8 @@ func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|||||||
func visibleFlags(fl []Flag) []Flag {
|
func visibleFlags(fl []Flag) []Flag {
|
||||||
visible := []Flag{}
|
visible := []Flag{}
|
||||||
for _, flag := range fl {
|
for _, flag := range fl {
|
||||||
if !flagValue(flag).FieldByName("Hidden").Bool() {
|
field := flagValue(flag).FieldByName("Hidden")
|
||||||
|
if !field.IsValid() || !field.Bool() {
|
||||||
visible = append(visible, flag)
|
visible = append(visible, flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -723,9 +724,8 @@ func stringifyFlag(f Flag) string {
|
|||||||
|
|
||||||
needsPlaceholder := false
|
needsPlaceholder := false
|
||||||
defaultValueString := ""
|
defaultValueString := ""
|
||||||
val := fv.FieldByName("Value")
|
|
||||||
|
|
||||||
if val.IsValid() {
|
if val := fv.FieldByName("Value"); val.IsValid() {
|
||||||
needsPlaceholder = true
|
needsPlaceholder = true
|
||||||
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
||||||
|
|
||||||
|
92
flag_test.go
92
flag_test.go
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -31,57 +32,57 @@ func TestBoolFlagHelpOutput(t *testing.T) {
|
|||||||
|
|
||||||
func TestFlagsFromEnv(t *testing.T) {
|
func TestFlagsFromEnv(t *testing.T) {
|
||||||
var flagTests = []struct {
|
var flagTests = []struct {
|
||||||
input string
|
input string
|
||||||
output interface{}
|
output interface{}
|
||||||
flag Flag
|
flag Flag
|
||||||
err error
|
errRegexp string
|
||||||
}{
|
}{
|
||||||
{"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
|
{"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
|
||||||
{"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
|
{"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
|
||||||
{"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
|
{"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
|
||||||
{"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`)},
|
{"foobar", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)},
|
||||||
|
|
||||||
{"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
|
{"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
|
||||||
{"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
|
{"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
|
||||||
{"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, nil},
|
{"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""},
|
||||||
{"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`)},
|
{"foobar", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)},
|
||||||
|
|
||||||
{"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, nil},
|
{"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, ""},
|
||||||
{"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Errorf(`could not parse foobar as duration for flag time: time: invalid duration foobar`)},
|
{"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)},
|
||||||
|
|
||||||
{"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil},
|
{"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
|
||||||
{"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil},
|
{"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
|
||||||
{"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`)},
|
{"foobar", 0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)},
|
||||||
|
|
||||||
{"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil},
|
{"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
|
||||||
{"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`)},
|
{"1.2", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)},
|
||||||
{"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)},
|
{"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)},
|
||||||
|
|
||||||
{"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, nil},
|
{"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
|
||||||
{"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`)},
|
{"1.2", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)},
|
||||||
{"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as int value for flag seconds: strconv.ParseInt: parsing "foobar": invalid syntax`)},
|
{"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)},
|
||||||
|
|
||||||
{"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil},
|
{"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
|
||||||
{"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`)},
|
{"1.2,2", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)},
|
||||||
{"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`)},
|
{"foobar", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)},
|
||||||
|
|
||||||
{"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, nil},
|
{"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
|
||||||
{"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`)},
|
{"1.2,2", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)},
|
||||||
{"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`)},
|
{"foobar", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)},
|
||||||
|
|
||||||
{"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, nil},
|
{"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, ""},
|
||||||
|
|
||||||
{"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, nil},
|
{"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, ""},
|
||||||
|
|
||||||
{"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, nil},
|
{"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, ""},
|
||||||
{"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`)},
|
{"1.2", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)},
|
||||||
{"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)},
|
{"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)},
|
||||||
|
|
||||||
{"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, nil},
|
{"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""},
|
||||||
{"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`)},
|
{"1.2", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)},
|
||||||
{"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Errorf(`could not parse foobar as uint64 value for flag seconds: strconv.ParseUint: parsing "foobar": invalid syntax`)},
|
{"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)},
|
||||||
|
|
||||||
{"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, nil},
|
{"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range flagTests {
|
for _, test := range flagTests {
|
||||||
@ -98,8 +99,19 @@ func TestFlagsFromEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := a.Run([]string{"run"})
|
err := a.Run([]string{"run"})
|
||||||
if !reflect.DeepEqual(test.err, err) {
|
|
||||||
t.Errorf("expected error %s, got error %s", test.err, err)
|
if test.errRegexp != "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error to match %s, got none", test.errRegexp)
|
||||||
|
} else {
|
||||||
|
if matched, _ := regexp.MatchString(test.errRegexp, err.Error()); !matched {
|
||||||
|
t.Errorf("expected error to match %s, got error %s", test.errRegexp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil && test.errRegexp == "" {
|
||||||
|
t.Errorf("expected no error got %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
64
help.go
64
help.go
@ -64,7 +64,7 @@ OPTIONS:
|
|||||||
// cli.go uses text/template to render templates. You can
|
// cli.go uses text/template to render templates. You can
|
||||||
// render custom help text by setting this variable.
|
// render custom help text by setting this variable.
|
||||||
var SubcommandHelpTemplate = `NAME:
|
var SubcommandHelpTemplate = `NAME:
|
||||||
{{.HelpName}} - {{.Usage}}
|
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||||
@ -112,17 +112,42 @@ var helpSubcommand = Command{
|
|||||||
// Prints help for the App or Command
|
// Prints help for the App or Command
|
||||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
type helpPrinter func(w io.Writer, templ string, data interface{})
|
||||||
|
|
||||||
|
// Prints help for the App or Command with custom template function.
|
||||||
|
type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
|
||||||
|
|
||||||
// HelpPrinter is a function that writes the help output. If not set a default
|
// HelpPrinter is a function that writes the help output. If not set a default
|
||||||
// is used. The function signature is:
|
// is used. The function signature is:
|
||||||
// func(w io.Writer, templ string, data interface{})
|
// func(w io.Writer, templ string, data interface{})
|
||||||
var HelpPrinter helpPrinter = printHelp
|
var HelpPrinter helpPrinter = printHelp
|
||||||
|
|
||||||
|
// HelpPrinterCustom is same as HelpPrinter but
|
||||||
|
// takes a custom function for template function map.
|
||||||
|
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
|
||||||
|
|
||||||
// VersionPrinter prints the version for the App
|
// VersionPrinter prints the version for the App
|
||||||
var VersionPrinter = printVersion
|
var VersionPrinter = printVersion
|
||||||
|
|
||||||
|
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
|
||||||
|
func ShowAppHelpAndExit(c *Context, exitCode int) {
|
||||||
|
ShowAppHelp(c)
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
// ShowAppHelp is an action that displays the help.
|
// ShowAppHelp is an action that displays the help.
|
||||||
func ShowAppHelp(c *Context) error {
|
func ShowAppHelp(c *Context) (err error) {
|
||||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
|
if c.App.CustomAppHelpTemplate == "" {
|
||||||
|
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
customAppData := func() map[string]interface{} {
|
||||||
|
if c.App.ExtraInfo == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"ExtraInfo": c.App.ExtraInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +163,12 @@ func DefaultAppComplete(c *Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShowCommandHelpAndExit - exits with code after showing help
|
||||||
|
func ShowCommandHelpAndExit(c *Context, command string, code int) {
|
||||||
|
ShowCommandHelp(c, command)
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
// ShowCommandHelp prints help for the given command
|
// ShowCommandHelp prints help for the given command
|
||||||
func ShowCommandHelp(ctx *Context, command string) error {
|
func ShowCommandHelp(ctx *Context, command string) error {
|
||||||
// show the subcommand help for a command with subcommands
|
// show the subcommand help for a command with subcommands
|
||||||
@ -148,7 +179,11 @@ func ShowCommandHelp(ctx *Context, command string) error {
|
|||||||
|
|
||||||
for _, c := range ctx.App.Commands {
|
for _, c := range ctx.App.Commands {
|
||||||
if c.HasName(command) {
|
if c.HasName(command) {
|
||||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
|
if c.CustomHelpTemplate != "" {
|
||||||
|
HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil)
|
||||||
|
} else {
|
||||||
|
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,10 +226,15 @@ func ShowCommandCompletions(ctx *Context, command string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
|
||||||
funcMap := template.FuncMap{
|
funcMap := template.FuncMap{
|
||||||
"join": strings.Join,
|
"join": strings.Join,
|
||||||
}
|
}
|
||||||
|
if customFunc != nil {
|
||||||
|
for key, value := range customFunc {
|
||||||
|
funcMap[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
||||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||||
@ -210,10 +250,14 @@ func printHelp(out io.Writer, templ string, data interface{}) {
|
|||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printHelp(out io.Writer, templ string, data interface{}) {
|
||||||
|
printHelpCustom(out, templ, data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func checkVersion(c *Context) bool {
|
func checkVersion(c *Context) bool {
|
||||||
found := false
|
found := false
|
||||||
if VersionFlag.Name != "" {
|
if VersionFlag.GetName() != "" {
|
||||||
eachName(VersionFlag.Name, func(name string) {
|
eachName(VersionFlag.GetName(), func(name string) {
|
||||||
if c.GlobalBool(name) || c.Bool(name) {
|
if c.GlobalBool(name) || c.Bool(name) {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
@ -224,8 +268,8 @@ func checkVersion(c *Context) bool {
|
|||||||
|
|
||||||
func checkHelp(c *Context) bool {
|
func checkHelp(c *Context) bool {
|
||||||
found := false
|
found := false
|
||||||
if HelpFlag.Name != "" {
|
if HelpFlag.GetName() != "" {
|
||||||
eachName(HelpFlag.Name, func(name string) {
|
eachName(HelpFlag.GetName(), func(name string) {
|
||||||
if c.GlobalBool(name) || c.Bool(name) {
|
if c.GlobalBool(name) || c.Bool(name) {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
@ -260,7 +304,7 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
|
|||||||
pos := len(arguments) - 1
|
pos := len(arguments) - 1
|
||||||
lastArg := arguments[pos]
|
lastArg := arguments[pos]
|
||||||
|
|
||||||
if lastArg != "--"+BashCompletionFlag.Name {
|
if lastArg != "--"+BashCompletionFlag.GetName() {
|
||||||
return false, arguments
|
return false, arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
120
help_test.go
120
help_test.go
@ -3,6 +3,8 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -256,6 +258,49 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShowCommandHelp_Customtemplate(t *testing.T) {
|
||||||
|
app := &App{
|
||||||
|
Commands: []Command{
|
||||||
|
{
|
||||||
|
Name: "frobbly",
|
||||||
|
Action: func(ctx *Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
HelpName: "foo frobbly",
|
||||||
|
CustomHelpTemplate: `NAME:
|
||||||
|
{{.HelpName}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
{{.HelpName}} [FLAGS] TARGET [TARGET ...]
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
{{range .VisibleFlags}}{{.}}
|
||||||
|
{{end}}
|
||||||
|
EXAMPLES:
|
||||||
|
1. Frobbly runs with this param locally.
|
||||||
|
$ {{.HelpName}} wobbly
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
output := &bytes.Buffer{}
|
||||||
|
app.Writer = output
|
||||||
|
app.Run([]string{"foo", "help", "frobbly"})
|
||||||
|
|
||||||
|
if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") {
|
||||||
|
t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(output.String(), "1. Frobbly runs with this param locally.") {
|
||||||
|
t.Errorf("expected output to include \"1. Frobbly runs with this param locally.\"; got: %q", output.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(output.String(), "$ foo frobbly wobbly") {
|
||||||
|
t.Errorf("expected output to include \"$ foo frobbly wobbly\"; got: %q", output.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestShowAppHelp_HiddenCommand(t *testing.T) {
|
func TestShowAppHelp_HiddenCommand(t *testing.T) {
|
||||||
app := &App{
|
app := &App{
|
||||||
Commands: []Command{
|
Commands: []Command{
|
||||||
@ -287,3 +332,78 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) {
|
|||||||
t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
|
t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShowAppHelp_CustomAppTemplate(t *testing.T) {
|
||||||
|
app := &App{
|
||||||
|
Commands: []Command{
|
||||||
|
{
|
||||||
|
Name: "frobbly",
|
||||||
|
Action: func(ctx *Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "secretfrob",
|
||||||
|
Hidden: true,
|
||||||
|
Action: func(ctx *Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExtraInfo: func() map[string]string {
|
||||||
|
platform := fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
goruntime := fmt.Sprintf("Version: %s | CPUs: %d", runtime.Version(), runtime.NumCPU())
|
||||||
|
return map[string]string{
|
||||||
|
"PLATFORM": platform,
|
||||||
|
"RUNTIME": goruntime,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CustomAppHelpTemplate: `NAME:
|
||||||
|
{{.Name}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
{{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...]
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
{{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||||
|
{{end}}{{if .VisibleFlags}}
|
||||||
|
GLOBAL FLAGS:
|
||||||
|
{{range .VisibleFlags}}{{.}}
|
||||||
|
{{end}}{{end}}
|
||||||
|
VERSION:
|
||||||
|
2.0.0
|
||||||
|
{{"\n"}}{{range $key, $value := ExtraInfo}}
|
||||||
|
{{$key}}:
|
||||||
|
{{$value}}
|
||||||
|
{{end}}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
output := &bytes.Buffer{}
|
||||||
|
app.Writer = output
|
||||||
|
app.Run([]string{"app", "--help"})
|
||||||
|
|
||||||
|
if strings.Contains(output.String(), "secretfrob") {
|
||||||
|
t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(output.String(), "frobbly") {
|
||||||
|
t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(output.String(), "PLATFORM:") ||
|
||||||
|
!strings.Contains(output.String(), "OS:") ||
|
||||||
|
!strings.Contains(output.String(), "Arch:") {
|
||||||
|
t.Errorf("expected output to include \"PLATFORM:, OS: and Arch:\"; got: %q", output.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(output.String(), "RUNTIME:") ||
|
||||||
|
!strings.Contains(output.String(), "Version:") ||
|
||||||
|
!strings.Contains(output.String(), "CPUs:") {
|
||||||
|
t.Errorf("expected output to include \"RUNTIME:, Version: and CPUs:\"; got: %q", output.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(output.String(), "VERSION:") ||
|
||||||
|
!strings.Contains(output.String(), "2.0.0") {
|
||||||
|
t.Errorf("expected output to include \"VERSION:, 2.0.0\"; got: %q", output.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user