Merge branch 'master' into fix/handle-multierror

This commit is contained in:
Jesse Szwedko 2016-11-12 12:46:57 -08:00 committed by GitHub
commit 36053a9dfd
8 changed files with 191 additions and 64 deletions

View File

@ -12,6 +12,7 @@ go:
- 1.4.2 - 1.4.2
- 1.5.x - 1.5.x
- 1.6.x - 1.6.x
- 1.7.x
- master - master
matrix: matrix:
@ -20,6 +21,8 @@ matrix:
include: include:
- go: 1.6.x - go: 1.6.x
os: osx os: osx
- go: 1.7.x
os: osx
before_script: before_script:
- go get github.com/urfave/gfmrun/... || true - go get github.com/urfave/gfmrun/... || true

View File

@ -30,6 +30,7 @@ applications in an expressive way.
* [Flags](#flags) * [Flags](#flags)
+ [Placeholder Values](#placeholder-values) + [Placeholder Values](#placeholder-values)
+ [Alternate Names](#alternate-names) + [Alternate Names](#alternate-names)
+ [Ordering](#ordering)
+ [Values from the Environment](#values-from-the-environment) + [Values from the Environment](#values-from-the-environment)
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
* [Subcommands](#subcommands) * [Subcommands](#subcommands)
@ -450,6 +451,56 @@ That flag can then be set with `--lang spanish` or `-l spanish`. Note that
giving two different forms of the same flag in the same command invocation is an giving two different forms of the same flag in the same command invocation is an
error. error.
#### Ordering
Flags for the application and commands are shown in the order they are defined.
However, it's possible to sort them from outside this library by using `FlagsByName`
with `sort`.
For example this:
<!-- {
"args": ["&#45;&#45;help"],
"output": "Load configuration from FILE\n.*Language for the greeting.*"
} -->
``` go
package main
import (
"os"
"sort"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "Language for the greeting",
},
cli.StringFlag{
Name: "config, c",
Usage: "Load configuration from `FILE`",
},
}
sort.Sort(cli.FlagsByName(app.Flags))
app.Run(os.Args)
}
```
Will result in help output like:
```
--config FILE, -c FILE Load configuration from FILE
--lang value, -l value Language for the greeting (default: "english")
```
#### Values from the Environment #### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g. You can also have the default value set from the environment via `EnvVar`. e.g.

66
app.go
View File

@ -6,9 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"sort" "sort"
"strings"
"time" "time"
) )
@ -19,11 +17,8 @@ var (
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errNonFuncAction = NewExitError("ERROR invalid Action type. "+ errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+ fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+
fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2) fmt.Sprintf("See %s", appActionDeprecationURL), 2)
) )
@ -42,6 +37,8 @@ type App struct {
ArgsUsage string ArgsUsage string
// Version of the program // Version of the program
Version string Version string
// Description of the program
Description string
// List of commands to execute // List of commands to execute
Commands []Command Commands []Command
// List of flags to parse // List of flags to parse
@ -165,6 +162,10 @@ func (a *App) Setup() {
if a.Metadata == nil { if a.Metadata == nil {
a.Metadata = make(map[string]interface{}) a.Metadata = make(map[string]interface{})
} }
if a.Writer == nil {
a.Writer = os.Stdout
}
} }
// Run is the entry point to the cli app. Parses the arguments slice and routes // Run is the entry point to the cli app. Parses the arguments slice and routes
@ -194,7 +195,7 @@ func (a *App) Run(arguments []string) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowAppHelp(context) ShowAppHelp(context)
return err return err
} }
@ -315,7 +316,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowSubcommandHelp(context) ShowSubcommandHelp(context)
return err return err
} }
@ -456,47 +457,22 @@ type Author struct {
func (a Author) String() string { func (a Author) String() string {
e := "" e := ""
if a.Email != "" { if a.Email != "" {
e = "<" + a.Email + "> " e = " <" + a.Email + ">"
} }
return fmt.Sprintf("%v %v", a.Name, e) return fmt.Sprintf("%v%v", a.Name, e)
} }
// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an // HandleAction attempts to figure out which Action signature was used. If
// ActionFunc, a func with the legacy signature for Action, or some other // it's an ActionFunc or a func with the legacy signature for Action, the func
// invalid thing. If it's an ActionFunc or a func with the legacy signature for // is run!
// Action, the func is run!
func HandleAction(action interface{}, context *Context) (err error) { func HandleAction(action interface{}, context *Context) (err error) {
defer func() { if a, ok := action.(func(*Context) error); ok {
if r := recover(); r != nil { return a(context)
// Try to detect a known reflection error from *this scope*, rather than } else if a, ok := action.(func(*Context)); ok { // deprecated function signature
// swallowing all panics that may happen when calling an Action func. a(context)
s := fmt.Sprintf("%v", r)
if strings.HasPrefix(s, "reflect: ") && strings.Contains(s, "too many input arguments") {
err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v.", r), 2)
} else {
panic(r)
}
}
}()
if reflect.TypeOf(action).Kind() != reflect.Func {
return errNonFuncAction
}
vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)})
if len(vals) == 0 {
return nil return nil
} else {
return errInvalidActionType
} }
if len(vals) > 1 {
return errInvalidActionSignature
}
if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok {
return retErr
}
return err
} }

View File

@ -90,7 +90,62 @@ func ExampleApp_Run_subcommand() {
// Hello, Jeremy // Hello, Jeremy
} }
func ExampleApp_Run_help() { func ExampleApp_Run_appHelp() {
// set args for examples sake
os.Args = []string{"greet", "help"}
app := NewApp()
app.Name = "greet"
app.Version = "0.1.0"
app.Description = "This is how we describe greet the app"
app.Authors = []Author{
{Name: "Harrison", Email: "harrison@lolwut.com"},
{Name: "Oliver Allen", Email: "oliver@toyshop.com"},
}
app.Flags = []Flag{
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Commands = []Command{
{
Name: "describeit",
Aliases: []string{"d"},
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *Context) error {
fmt.Printf("i like to describe things")
return nil
},
},
}
app.Run(os.Args)
// Output:
// NAME:
// greet - A new cli application
//
// USAGE:
// greet [global options] command [command options] [arguments...]
//
// VERSION:
// 0.1.0
//
// DESCRIPTION:
// This is how we describe greet the app
//
// AUTHORS:
// Harrison <harrison@lolwut.com>
// Oliver Allen <oliver@toyshop.com>
//
// COMMANDS:
// describeit, d use it to see a description
// help, h Shows a list of commands or help for one command
//
// GLOBAL OPTIONS:
// --name value a name to say (default: "bob")
// --help, -h show help
// --version, -v print the version
}
func ExampleApp_Run_commandHelp() {
// set args for examples sake // set args for examples sake
os.Args = []string{"greet", "h", "describeit"} os.Args = []string{"greet", "h", "describeit"}
@ -202,6 +257,12 @@ func TestApp_Command(t *testing.T) {
} }
} }
func TestApp_Setup_defaultsWriter(t *testing.T) {
app := &App{}
app.Setup()
expect(t, app.Writer, os.Stdout)
}
func TestApp_CommandWithArgBeforeFlags(t *testing.T) { func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
var parsedOption, firstArg string var parsedOption, firstArg string
@ -252,6 +313,23 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
expect(t, context.String("lang"), "spanish") expect(t, context.String("lang"), "spanish")
} }
func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
a := App{
Flags: []Flag{
StringFlag{Name: "--foo"},
},
Writer: bytes.NewBufferString(""),
}
set := flag.NewFlagSet("", flag.ContinueOnError)
set.Parse([]string{"", "---foo"})
c := &Context{flagSet: set}
err := a.RunAsSubcommand(c)
expect(t, err, errors.New("bad flag syntax: ---foo"))
}
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
var parsedOption string var parsedOption string
var args []string var args []string
@ -1414,7 +1492,7 @@ func TestHandleAction_WithNonFuncAction(t *testing.T) {
t.Fatalf("expected to receive a *ExitError") t.Fatalf("expected to receive a *ExitError")
} }
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") {
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
} }
@ -1438,7 +1516,7 @@ func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
t.Fatalf("expected to receive a *ExitError") t.Fatalf("expected to receive a *ExitError")
} }
if !strings.HasPrefix(exitErr.Error(), "ERROR unknown Action error") { if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
} }
@ -1462,7 +1540,7 @@ func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
t.Fatalf("expected to receive a *ExitError") t.Fatalf("expected to receive a *ExitError")
} }
if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action signature") { if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error())
} }

View File

@ -138,7 +138,7 @@ func (c Command) Run(ctx *Context) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(ctx.App.Writer) fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name) ShowCommandHelp(ctx, c.Name)
return err return err

View File

@ -24,7 +24,7 @@ func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err} return MultiError{Errors: err}
} }
// Error implents the error interface. // Error implements the error interface.
func (m MultiError) Error() string { func (m MultiError) Error() string {
errs := make([]string, len(m.Errors)) errs := make([]string, len(m.Errors))
for i, err := range m.Errors { for i, err := range m.Errors {

15
flag.go
View File

@ -37,6 +37,21 @@ var HelpFlag = BoolFlag{
// to display a flag. // to display a flag.
var FlagStringer FlagStringFunc = stringifyFlag var FlagStringer FlagStringFunc = stringifyFlag
// FlagsByName is a slice of Flag.
type FlagsByName []Flag
func (f FlagsByName) Len() int {
return len(f)
}
func (f FlagsByName) Less(i, j int) bool {
return f[i].GetName() < f[j].GetName()
}
func (f FlagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
// Flag is a common interface related to parsing flags in cli. // Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recommended that // For more advanced flag parsing techniques, it is recommended that
// this interface be implemented. // this interface be implemented.

30
help.go
View File

@ -16,24 +16,28 @@ var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
{{if .Version}}{{if not .HideVersion}}
VERSION: VERSION:
{{.Version}} {{.Version}}{{end}}{{end}}{{if .Description}}
{{end}}{{end}}{{if len .Authors}}
AUTHOR(S): DESCRIPTION:
{{range .Authors}}{{.}}{{end}} {{.Description}}{{end}}{{if len .Authors}}
{{end}}{{if .VisibleCommands}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}} COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{end}}{{range .VisibleCommands}} {{.Name}}:{{end}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS: GLOBAL OPTIONS:
{{range .VisibleFlags}}{{.}} {{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{end}}{{if .Copyright}} {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT: COPYRIGHT:
{{.Copyright}} {{.Copyright}}{{end}}
{{end}}
` `
// CommandHelpTemplate is the text template for the command help topic. // CommandHelpTemplate is the text template for the command help topic.