Merge pull request #555 from urfave/v2-merge

Merge origin/master into v2
main
Jesse Szwedko 8 years ago committed by GitHub
commit e485446237

@ -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
env: pip_install="pip install --user" env: pip_install="pip install --user"
@ -23,6 +24,9 @@ matrix:
- go: 1.6.x - go: 1.6.x
os: osx os: osx
env: pip_install="sudo pip install" env: pip_install="sudo pip install"
- go: 1.7.x
os: osx
env: pip_install="sudo pip install"
before_script: before_script:
- $pip_install flake8 - $pip_install flake8

@ -23,13 +23,14 @@ applications in an expressive way.
- [Installation](#installation) - [Installation](#installation)
* [Supported platforms](#supported-platforms) * [Supported platforms](#supported-platforms)
* [Using the `v2` branch](#using-the-v2-branch) * [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) - [Getting Started](#getting-started)
- [Examples](#examples) - [Examples](#examples)
* [Arguments](#arguments) * [Arguments](#arguments)
* [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)
+ [Default Values for help output](#default-values-for-help-output) + [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 **NOTE**: There is a [migrator (python) script](./cli-v1-to-v2) available to aid
with the transition from the v1 to v2 API. 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 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 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 $ 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 ## Getting Started
One of the philosophies behind cli is that an API should be playful and full of 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 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.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 `EnvVars`. e.g. 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 #### 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.: setting `cli.VersionFlag`, e.g.:
<!-- { <!-- {

@ -25,6 +25,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
@ -191,7 +193,7 @@ func (a *App) Run(arguments []string) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return 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) ShowAppHelp(context)
return err 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 // RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
// generate command-specific flags // generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) { func (a *App) RunAsSubcommand(ctx *Context) (err error) {
a.Setup()
// append help to commands // append help to commands
if len(a.Commands) > 0 { if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp { if a.Command(helpCommand.Name) == nil && !a.HideHelp {
@ -301,7 +305,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return 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) ShowSubcommandHelp(context)
return err return err
} }
@ -441,8 +445,8 @@ 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)
} }

@ -91,7 +91,63 @@ 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 := &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 // set args for examples sake
os.Args = []string{"greet", "h", "describeit"} os.Args = []string{"greet", "h", "describeit"}
@ -130,7 +186,7 @@ func ExampleApp_Run_shellComplete() {
os.Args = []string{"greet", "--generate-completion"} os.Args = []string{"greet", "--generate-completion"}
app := &App{ app := &App{
Name: "greet", Name: "greet",
EnableShellCompletion: true, EnableShellCompletion: true,
Commands: []*Command{ Commands: []*Command{
{ {
@ -234,6 +290,24 @@ 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{
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) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
var parsedOption string var parsedOption string
var args Args var args Args

@ -90,7 +90,7 @@ func (c *Command) Run(ctx *Context) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return 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) fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name) ShowCommandHelp(ctx, c.Name)
return err return err

@ -15,10 +15,12 @@ func TestCommandFlagParsing(t *testing.T) {
skipFlagParsing bool skipFlagParsing bool
expectedErr error expectedErr error
}{ }{
{[]string{"test-cmd", "-break", "blah", "blah"}, false, errors.New("flag provided but not defined: -break")}, // Test normal "not ignoring flags" flow // 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", "blah"}, false, errors.New("flag provided but not defined: -break")},
{[]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 {[]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 { for _, c := range cases {
@ -29,15 +31,14 @@ func TestCommandFlagParsing(t *testing.T) {
context := NewContext(app, set, nil) context := NewContext(app, set, nil)
command := Command{ command := Command{
Name: "test-cmd", Name: "test-cmd",
Aliases: []string{"tc"}, Aliases: []string{"tc"},
Usage: "this is for testing", Usage: "this is for testing",
Description: "testing", Description: "testing",
Action: func(_ *Context) error { return nil }, Action: func(_ *Context) error { return nil },
SkipFlagParsing: c.skipFlagParsing,
} }
command.SkipFlagParsing = c.skipFlagParsing
err := command.Run(context) err := command.Run(context)
expect(t, err, c.expectedErr) expect(t, err, c.expectedErr)

@ -3,6 +3,8 @@ package cli
import ( import (
"errors" "errors"
"flag" "flag"
"os"
"reflect"
"strings" "strings"
) )
@ -42,8 +44,44 @@ func (c *Context) IsSet(name string) bool {
isSet = true 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 return false
} }
@ -87,6 +125,34 @@ func (c *Context) NArg() int {
return c.Args().Len() 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 { func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
for _, c := range ctx.Lineage() { for _, c := range ctx.Lineage() {
if f := c.flagSet.Lookup(name); f != nil { if f := c.flagSet.Lookup(name); f != nil {

@ -58,6 +58,26 @@ type Serializeder interface {
Serialized() string 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. // 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.

@ -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.
@ -205,17 +209,6 @@ func printHelp(out io.Writer, templ string, data interface{}) {
errDebug := os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" 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) err := t.Execute(w, data)
if err != nil { if err != nil {
if errDebug { if errDebug {

Loading…
Cancel
Save