Merge branch 'master' into fix/handle-multierror
This commit is contained in:
commit
36053a9dfd
@ -12,6 +12,7 @@ go:
|
||||
- 1.4.2
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- master
|
||||
|
||||
matrix:
|
||||
@ -20,6 +21,8 @@ matrix:
|
||||
include:
|
||||
- go: 1.6.x
|
||||
os: osx
|
||||
- go: 1.7.x
|
||||
os: osx
|
||||
|
||||
before_script:
|
||||
- go get github.com/urfave/gfmrun/... || true
|
||||
|
51
README.md
51
README.md
@ -30,6 +30,7 @@ applications in an expressive way.
|
||||
* [Flags](#flags)
|
||||
+ [Placeholder Values](#placeholder-values)
|
||||
+ [Alternate Names](#alternate-names)
|
||||
+ [Ordering](#ordering)
|
||||
+ [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)
|
||||
* [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
|
||||
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": ["--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
|
||||
|
||||
You can also have the default value set from the environment via `EnvVar`. e.g.
|
||||
|
66
app.go
66
app.go
@ -6,9 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"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."
|
||||
|
||||
errNonFuncAction = NewExitError("ERROR invalid Action type. "+
|
||||
fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||
errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+
|
||||
fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+
|
||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||
)
|
||||
|
||||
@ -42,6 +37,8 @@ type App struct {
|
||||
ArgsUsage string
|
||||
// Version of the program
|
||||
Version string
|
||||
// Description of the program
|
||||
Description string
|
||||
// List of commands to execute
|
||||
Commands []Command
|
||||
// List of flags to parse
|
||||
@ -165,6 +162,10 @@ func (a *App) Setup() {
|
||||
if a.Metadata == nil {
|
||||
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
|
||||
@ -194,7 +195,7 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
HandleExitCoder(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)
|
||||
return err
|
||||
}
|
||||
@ -315,7 +316,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
HandleExitCoder(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)
|
||||
return err
|
||||
}
|
||||
@ -456,47 +457,22 @@ type Author struct {
|
||||
func (a Author) String() string {
|
||||
e := ""
|
||||
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
|
||||
// ActionFunc, a func with the legacy signature for Action, or some other
|
||||
// invalid thing. If it's an ActionFunc or a func with the legacy signature for
|
||||
// Action, the func is run!
|
||||
// HandleAction attempts to figure out which Action signature was used. If
|
||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
||||
// is run!
|
||||
func HandleAction(action interface{}, context *Context) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Try to detect a known reflection error from *this scope*, rather than
|
||||
// swallowing all panics that may happen when calling an Action func.
|
||||
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 {
|
||||
if a, ok := action.(func(*Context) error); ok {
|
||||
return a(context)
|
||||
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
|
||||
a(context)
|
||||
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
|
||||
}
|
||||
|
86
app_test.go
86
app_test.go
@ -90,7 +90,62 @@ func ExampleApp_Run_subcommand() {
|
||||
// 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
|
||||
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) {
|
||||
var parsedOption, firstArg string
|
||||
|
||||
@ -252,6 +313,23 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||
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) {
|
||||
var parsedOption string
|
||||
var args []string
|
||||
@ -1414,7 +1492,7 @@ func TestHandleAction_WithNonFuncAction(t *testing.T) {
|
||||
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())
|
||||
}
|
||||
|
||||
@ -1438,7 +1516,7 @@ func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
|
||||
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())
|
||||
}
|
||||
|
||||
@ -1462,7 +1540,7 @@ func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
|
@ -24,7 +24,7 @@ func NewMultiError(err ...error) MultiError {
|
||||
return MultiError{Errors: err}
|
||||
}
|
||||
|
||||
// Error implents the error interface.
|
||||
// Error implements the error interface.
|
||||
func (m MultiError) Error() string {
|
||||
errs := make([]string, len(m.Errors))
|
||||
for i, err := range m.Errors {
|
||||
|
15
flag.go
15
flag.go
@ -37,6 +37,21 @@ var HelpFlag = BoolFlag{
|
||||
// to display a flag.
|
||||
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.
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
||||
// this interface be implemented.
|
||||
|
30
help.go
30
help.go
@ -16,24 +16,28 @@ var AppHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.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 .Version}}{{if not .HideVersion}}
|
||||
{{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}}
|
||||
|
||||
VERSION:
|
||||
{{.Version}}
|
||||
{{end}}{{end}}{{if len .Authors}}
|
||||
AUTHOR(S):
|
||||
{{range .Authors}}{{.}}{{end}}
|
||||
{{end}}{{if .VisibleCommands}}
|
||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if len .Authors}}
|
||||
|
||||
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}}
|
||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
|
||||
{{end}}{{end}}{{if .VisibleFlags}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}{{if .Copyright}}
|
||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
||||
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
|
||||
|
||||
COPYRIGHT:
|
||||
{{.Copyright}}
|
||||
{{end}}
|
||||
{{.Copyright}}{{end}}
|
||||
`
|
||||
|
||||
// CommandHelpTemplate is the text template for the command help topic.
|
||||
|
Loading…
Reference in New Issue
Block a user