Merge remote-tracking branch 'origin/master' into v2-merge
This commit is contained in:
commit
83497d2cda
@ -12,6 +12,7 @@ go:
|
||||
- 1.4.2
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- master
|
||||
|
||||
env: pip_install="pip install --user"
|
||||
@ -23,6 +24,9 @@ matrix:
|
||||
- go: 1.6.x
|
||||
os: osx
|
||||
env: pip_install="sudo pip install"
|
||||
- go: 1.7.x
|
||||
os: osx
|
||||
env: pip_install="sudo pip install"
|
||||
|
||||
before_script:
|
||||
- $pip_install flake8
|
||||
|
61
README.md
61
README.md
@ -23,13 +23,14 @@ applications in an expressive way.
|
||||
- [Installation](#installation)
|
||||
* [Supported platforms](#supported-platforms)
|
||||
* [Using the `v2` branch](#using-the-v2-branch)
|
||||
* [Pinning to the `v1` branch](#pinning-to-the-v1-branch)
|
||||
* [Pinning to the `v1` releases](#pinning-to-the-v1-releases)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Examples](#examples)
|
||||
* [Arguments](#arguments)
|
||||
* [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)
|
||||
+ [Default Values for help output](#default-values-for-help-output)
|
||||
@ -108,11 +109,11 @@ import (
|
||||
**NOTE**: There is a [migrator (python) script](./cli-v1-to-v2) available to aid
|
||||
with the transition from the v1 to v2 API.
|
||||
|
||||
### Pinning to the `v1` branch
|
||||
### Pinning to the `v1` releases
|
||||
|
||||
Similarly to the section above describing use of the `v2` branch, if one wants
|
||||
to avoid any unexpected compatibility pains once `v2` becomes `master`, then
|
||||
pinning to the `v1` branch is an acceptable option, e.g.:
|
||||
pinning to `v1` is an acceptable option, e.g.:
|
||||
|
||||
```
|
||||
$ go get gopkg.in/urfave/cli.v1
|
||||
@ -126,6 +127,8 @@ import (
|
||||
...
|
||||
```
|
||||
|
||||
This will pull the latest tagged `v1` release (e.g. `v1.18.1` at the time of writing).
|
||||
|
||||
## Getting Started
|
||||
|
||||
One of the philosophies behind cli is that an API should be playful and full of
|
||||
@ -454,6 +457,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 `EnvVars`. e.g.
|
||||
@ -1013,7 +1066,7 @@ is checked by the cli internals in order to print the `App.Version` via
|
||||
|
||||
#### Customization
|
||||
|
||||
The default flag may be cusomized to something other than `-v/--version` by
|
||||
The default flag may be customized to something other than `-v/--version` by
|
||||
setting `cli.VersionFlag`, e.g.:
|
||||
|
||||
<!-- {
|
||||
|
12
app.go
12
app.go
@ -25,6 +25,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
|
||||
@ -191,7 +193,7 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "Incorrect Usage: %s\n\n", err)
|
||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||
ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
@ -248,6 +250,8 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
||||
// generate command-specific flags
|
||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
a.Setup()
|
||||
|
||||
// append help to commands
|
||||
if len(a.Commands) > 0 {
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
@ -301,7 +305,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "Incorrect Usage: %s\n\n", err)
|
||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||
ShowSubcommandHelp(context)
|
||||
return err
|
||||
}
|
||||
@ -441,8 +445,8 @@ 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)
|
||||
}
|
||||
|
78
app_test.go
78
app_test.go
@ -91,7 +91,63 @@ 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 := &App{
|
||||
Name: "greet",
|
||||
Version: "0.1.0",
|
||||
Description: "This is how we describe greet the app",
|
||||
Authors: []*Author{
|
||||
{Name: "Harrison", Email: "harrison@lolwut.com"},
|
||||
{Name: "Oliver Allen", Email: "oliver@toyshop.com"},
|
||||
},
|
||||
Flags: []Flag{
|
||||
&StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||
},
|
||||
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 (default: false)
|
||||
// --version, -v print the version (default: false)
|
||||
}
|
||||
|
||||
func ExampleApp_Run_commandHelp() {
|
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "h", "describeit"}
|
||||
|
||||
@ -130,7 +186,7 @@ func ExampleApp_Run_shellComplete() {
|
||||
os.Args = []string{"greet", "--generate-completion"}
|
||||
|
||||
app := &App{
|
||||
Name: "greet",
|
||||
Name: "greet",
|
||||
EnableShellCompletion: true,
|
||||
Commands: []*Command{
|
||||
{
|
||||
@ -234,6 +290,24 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||
expect(t, context.String("lang"), "spanish")
|
||||
}
|
||||
|
||||
func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
|
||||
a := App{
|
||||
Name: "cmd",
|
||||
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 Args
|
||||
|
@ -90,7 +90,7 @@ func (c *Command) Run(ctx *Context) (err error) {
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Incorrect Usage: %s\n\n", err)
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
|
@ -15,10 +15,12 @@ func TestCommandFlagParsing(t *testing.T) {
|
||||
skipFlagParsing bool
|
||||
expectedErr error
|
||||
}{
|
||||
{[]string{"test-cmd", "-break", "blah", "blah"}, false, errors.New("flag provided but not defined: -break")}, // Test normal "not ignoring flags" flow
|
||||
{[]string{"test-cmd", "blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags
|
||||
{[]string{"test-cmd", "-break", "blah"}, true, nil}, // Test SkipFlagParsing with random flag arg
|
||||
{[]string{"test-cmd", "-help", "blah"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg
|
||||
// Test normal "not ignoring flags" flow
|
||||
{[]string{"test-cmd", "-break", "blah", "blah"}, false, errors.New("flag provided but not defined: -break")},
|
||||
|
||||
{[]string{"test-cmd", "blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags
|
||||
{[]string{"test-cmd", "blah", "-break"}, true, nil}, // Test SkipFlagParsing with random flag arg
|
||||
{[]string{"test-cmd", "blah", "-help"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@ -29,15 +31,14 @@ func TestCommandFlagParsing(t *testing.T) {
|
||||
context := NewContext(app, set, nil)
|
||||
|
||||
command := Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *Context) error { return nil },
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *Context) error { return nil },
|
||||
SkipFlagParsing: c.skipFlagParsing,
|
||||
}
|
||||
|
||||
command.SkipFlagParsing = c.skipFlagParsing
|
||||
|
||||
err := command.Run(context)
|
||||
|
||||
expect(t, err, c.expectedErr)
|
||||
|
68
context.go
68
context.go
@ -3,6 +3,8 @@ package cli
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -42,8 +44,44 @@ func (c *Context) IsSet(name string) bool {
|
||||
isSet = true
|
||||
}
|
||||
})
|
||||
return isSet
|
||||
if isSet {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// XXX hack to support IsSet for flags with EnvVar
|
||||
//
|
||||
// There isn't an easy way to do this with the current implementation since
|
||||
// whether a flag was set via an environment variable is very difficult to
|
||||
// determine here. Instead, we intend to introduce a backwards incompatible
|
||||
// change in version 2 to add `IsSet` to the Flag interface to push the
|
||||
// responsibility closer to where the information required to determine
|
||||
// whether a flag is set by non-standard means such as environment
|
||||
// variables is avaliable.
|
||||
//
|
||||
// See https://github.com/urfave/cli/issues/294 for additional discussion
|
||||
f := lookupFlag(name, c)
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(f)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
envVarValues := val.FieldByName("EnvVars")
|
||||
if !envVarValues.IsValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, envVar := range envVarValues.Interface().([]string) {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@ -87,6 +125,34 @@ func (c *Context) NArg() int {
|
||||
return c.Args().Len()
|
||||
}
|
||||
|
||||
func lookupFlag(name string, ctx *Context) Flag {
|
||||
for _, c := range ctx.Lineage() {
|
||||
if c.Command == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range c.Command.Flags {
|
||||
for _, n := range f.Names() {
|
||||
if n == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.App != nil {
|
||||
for _, f := range ctx.App.Flags {
|
||||
for _, n := range f.Names() {
|
||||
if n == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
|
||||
for _, c := range ctx.Lineage() {
|
||||
if f := c.flagSet.Lookup(name); f != nil {
|
||||
|
20
flag.go
20
flag.go
@ -58,6 +58,26 @@ type Serializeder interface {
|
||||
Serialized() string
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if len(f[j].Names()) == 0 {
|
||||
return false
|
||||
} else if len(f[i].Names()) == 0 {
|
||||
return true
|
||||
}
|
||||
return f[i].Names()[0] < f[j].Names()[0]
|
||||
}
|
||||
|
||||
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.
|
||||
|
41
help.go
41
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.
|
||||
@ -205,17 +209,6 @@ func printHelp(out io.Writer, templ string, data interface{}) {
|
||||
|
||||
errDebug := os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != ""
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if errDebug {
|
||||
fmt.Fprintf(ErrWriter, "CLI TEMPLATE PANIC: %#v\n", r)
|
||||
}
|
||||
if os.Getenv("CLI_TEMPLATE_REPANIC") != "" {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := t.Execute(w, data)
|
||||
if err != nil {
|
||||
if errDebug {
|
||||
|
Loading…
Reference in New Issue
Block a user