Merge branch 'master' into fix/handle-multierror
This commit is contained in:
commit
36053a9dfd
@ -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
|
||||||
|
51
README.md
51
README.md
@ -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": ["--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
66
app.go
@ -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
|
|
||||||
}
|
}
|
||||||
|
86
app_test.go
86
app_test.go
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
15
flag.go
@ -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
30
help.go
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user