Merge branch 'master' into merging-jereksel-zsh

main
Dan Buch 7 years ago committed by GitHub
commit 688c5a9d4f

@ -0,0 +1,2 @@
[flake8]
max-line-length = 120

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

@ -455,13 +455,13 @@ error.
Flags for the application and commands are shown in the order they are defined. Flags for the application and commands are shown in the order they are defined.
However, it's possible to sort them from outside this library by using `FlagsByName` However, it's possible to sort them from outside this library by using `FlagsByName`
with `sort`. or `CommandsByName` with `sort`.
For example this: For example this:
<!-- { <!-- {
"args": ["&#45;&#45;help"], "args": ["&#45;&#45;help"],
"output": "Load configuration from FILE\n.*Language for the greeting.*" "output": "add a task to the list\n.*complete a task on the list\n.*\n\n.*\n.*Load configuration from FILE\n.*Language for the greeting.*"
} --> } -->
``` go ``` go
package main package main
@ -488,7 +488,27 @@ func main() {
}, },
} }
app.Commands = []cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) error {
return nil
},
},
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) error {
return nil
},
},
}
sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
app.Run(os.Args) app.Run(os.Args)
} }
@ -940,16 +960,13 @@ SUPPORT: support@awesometown.example.com
cli.AppHelpTemplate = `NAME: cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
[command options]{{end}} {{if
.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if len .Authors}} {{if len .Authors}}
AUTHOR(S): AUTHOR:
{{range .Authors}}{{ . }}{{end}} {{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}} {{end}}{{if .Commands}}
COMMANDS: COMMANDS:
{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t" {{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS: GLOBAL OPTIONS:
{{range .VisibleFlags}}{{.}} {{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{if .Copyright }} {{end}}{{end}}{{if .Copyright }}

@ -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 := otherGenericValue.([]interface{})
otherValue, isType := nestedGenericValue.([]string) 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 { if !isType {
return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue) return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "string", v)
} }
return otherValue, nil
stringSlice = append(stringSlice, stringValue)
} }
return nil, nil 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 := otherGenericValue.([]interface{})
otherValue, isType := nestedGenericValue.([]int) 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 { if !isType {
return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue) return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "int", v)
} }
return otherValue, nil
intSlice = append(intSlice, intValue)
} }
return nil, nil 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)
} }

@ -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
} }
@ -234,7 +240,6 @@ func (a *App) Run(arguments []string) (err error) {
if a.Before != nil { if a.Before != nil {
beforeErr := a.Before(context) beforeErr := a.Before(context)
if beforeErr != nil { if beforeErr != nil {
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
ShowAppHelp(context) ShowAppHelp(context)
HandleExitCoder(beforeErr) HandleExitCoder(beforeErr)
err = beforeErr err = beforeErr

@ -1558,6 +1558,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
@ -1680,7 +1737,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
} }
@ -1697,7 +1754,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)
} }

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

@ -12,6 +12,7 @@
// app.Usage = "say a greeting" // app.Usage = "say a greeting"
// app.Action = func(c *cli.Context) error { // app.Action = func(c *cli.Context) error {
// println("Greetings") // println("Greetings")
// return nil
// } // }
// //
// app.Run(os.Args) // app.Run(os.Args)

@ -59,6 +59,25 @@ type Command struct {
// Full name of command for help, defaults to full command name, including parent commands. // Full name of command for help, defaults to full command name, including parent commands.
HelpName string HelpName string
commandNamePath []string commandNamePath []string
// CustomHelpTemplate the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomHelpTemplate string
}
type CommandsByName []Command
func (c CommandsByName) Len() int {
return len(c)
}
func (c CommandsByName) Less(i, j int) bool {
return c[i].Name < c[j].Name
}
func (c CommandsByName) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
} }
// FullName returns the full name of the command. // FullName returns the full name of the command.
@ -140,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
} }
@ -177,9 +197,7 @@ func (c Command) Run(ctx *Context) (err error) {
if c.Before != nil { if c.Before != nil {
err = c.Before(context) err = c.Before(context)
if err != nil { if err != nil {
fmt.Fprintln(ctx.App.Writer, err) ShowCommandHelp(context, c.Name)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
@ -189,7 +207,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 {
@ -230,14 +247,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
@ -250,6 +266,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 {
@ -272,6 +289,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)
}
}

@ -39,11 +39,13 @@ func (c *Context) NumFlags() int {
// Set sets a context flag to a value. // Set sets a context flag to a value.
func (c *Context) Set(name, value string) error { func (c *Context) Set(name, value string) error {
c.setFlags = nil
return c.flagSet.Set(name, value) return c.flagSet.Set(name, value)
} }
// GlobalSet sets a context flag to a value on the global flagset // GlobalSet sets a context flag to a value on the global flagset
func (c *Context) GlobalSet(name, value string) error { func (c *Context) GlobalSet(name, value string) error {
globalContext(c).setFlags = nil
return globalContext(c).flagSet.Set(name, value) return globalContext(c).flagSet.Set(name, value)
} }

@ -375,8 +375,10 @@ func TestContext_Set(t *testing.T) {
set.Int("int", 5, "an int") set.Int("int", 5, "an int")
c := NewContext(nil, set, nil) c := NewContext(nil, set, nil)
expect(t, c.IsSet("int"), false)
c.Set("int", "1") c.Set("int", "1")
expect(t, c.Int("int"), 1) expect(t, c.Int("int"), 1)
expect(t, c.IsSet("int"), true)
} }
func TestContext_GlobalSet(t *testing.T) { func TestContext_GlobalSet(t *testing.T) {
@ -393,7 +395,9 @@ func TestContext_GlobalSet(t *testing.T) {
expect(t, c.Int("int"), 1) expect(t, c.Int("int"), 1)
expect(t, c.GlobalInt("int"), 5) expect(t, c.GlobalInt("int"), 5)
expect(t, c.GlobalIsSet("int"), false)
c.GlobalSet("int", "1") c.GlobalSet("int", "1")
expect(t, c.Int("int"), 1) expect(t, c.Int("int"), 1)
expect(t, c.GlobalInt("int"), 1) expect(t, c.GlobalInt("int"), 1)
expect(t, c.GlobalIsSet("int"), true)
} }

@ -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,64 +50,21 @@ 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)
}
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, called, true)
expect(t, ErrWriter.(*bytes.Buffer).String(), "")
} }
// make a stub to not import pkg/errors // make a stub to not import pkg/errors
@ -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{}

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

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

@ -47,7 +47,7 @@ var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{.Usage}}
USAGE: USAGE:
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY: CATEGORY:
{{.Category}}{{end}}{{if .Description}} {{.Category}}{{end}}{{if .Description}}
@ -64,10 +64,10 @@ OPTIONS:
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME: var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
USAGE: USAGE:
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}} COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}} {{.Name}}:{{end}}{{range .VisibleCommands}}
@ -112,17 +112,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
} }
@ -144,6 +169,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
@ -154,7 +185,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
} }
} }
@ -197,10 +232,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))
@ -216,10 +256,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
} }
@ -230,8 +274,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
} }
@ -266,7 +310,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
} }

@ -3,6 +3,8 @@ package cli
import ( import (
"bytes" "bytes"
"flag" "flag"
"fmt"
"runtime"
"strings" "strings"
"testing" "testing"
) )
@ -256,6 +258,92 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
} }
} }
func TestShowCommandHelp_Customtemplate(t *testing.T) {
app := &App{
Commands: []Command{
{
Name: "frobbly",
Action: func(ctx *Context) error {
return nil
},
HelpName: "foo frobbly",
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} [FLAGS] TARGET [TARGET ...]
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}
EXAMPLES:
1. Frobbly runs with this param locally.
$ {{.HelpName}} wobbly
`,
},
},
}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "help", "frobbly"})
if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") {
t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String())
}
if !strings.Contains(output.String(), "1. Frobbly runs with this param locally.") {
t.Errorf("expected output to include \"1. Frobbly runs with this param locally.\"; got: %q", output.String())
}
if !strings.Contains(output.String(), "$ foo frobbly wobbly") {
t.Errorf("expected output to include \"$ foo frobbly wobbly\"; got: %q", output.String())
}
}
func TestShowSubcommandHelp_CommandUsageText(t *testing.T) {
app := &App{
Commands: []Command{
{
Name: "frobbly",
UsageText: "this is usage text",
},
},
}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "frobbly", "--help"})
if !strings.Contains(output.String(), "this is usage text") {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
app := &App{
Commands: []Command{
{
Name: "frobbly",
Subcommands: []Command{
{
Name: "bobbly",
UsageText: "this is usage text",
},
},
},
},
}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
if !strings.Contains(output.String(), "this is usage text") {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowAppHelp_HiddenCommand(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) {
app := &App{ app := &App{
Commands: []Command{ Commands: []Command{
@ -287,3 +375,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…
Cancel
Save