Merge branch 'v2' into show-bool-default

This commit is contained in:
Dan Buch 2016-05-28 04:49:10 -04:00
commit 3b03164c92
26 changed files with 1061 additions and 742 deletions

View File

@ -14,6 +14,8 @@ matrix:
allow_failures:
- go: master
include:
- go: 1.6.2
os: osx
- go: 1.1.2
install: go get -v .
before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION
@ -22,7 +24,7 @@ matrix:
- ./runtests test
before_script:
- go get github.com/meatballhat/gfmxr/...
- go get github.com/urfave/gfmxr/...
script:
- ./runtests vet

View File

@ -34,6 +34,8 @@
## [Unreleased] - (1.x series)
### Added
- `./runtests` test runner with coverage tracking by default
- testing on OS X
- testing on Windows
### Fixed
- Printing of command aliases in help text
@ -57,7 +59,7 @@
makes it easier to script around apps built using `cli` since they can trust
that a 0 exit code indicated a successful execution.
- cleanups based on [Go Report Card
feedback](https://goreportcard.com/report/github.com/codegangsta/cli)
feedback](https://goreportcard.com/report/github.com/urfave/cli)
## [1.16.0] - 2016-05-02
### Added
@ -317,28 +319,28 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
### Added
- Initial implementation.
[Unreleased]: https://github.com/codegangsta/cli/compare/v1.17.0...HEAD
[1.17.0]: https://github.com/codegangsta/cli/compare/v1.16.0...v1.17.0
[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0
[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0
[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0
[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0
[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0
[1.11.1]: https://github.com/codegangsta/cli/compare/v1.11.0...v1.11.1
[1.11.0]: https://github.com/codegangsta/cli/compare/v1.10.2...v1.11.0
[1.10.2]: https://github.com/codegangsta/cli/compare/v1.10.1...v1.10.2
[1.10.1]: https://github.com/codegangsta/cli/compare/v1.10.0...v1.10.1
[1.10.0]: https://github.com/codegangsta/cli/compare/v1.9.0...v1.10.0
[1.9.0]: https://github.com/codegangsta/cli/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/codegangsta/cli/compare/v1.7.1...v1.8.0
[1.7.1]: https://github.com/codegangsta/cli/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/codegangsta/cli/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/codegangsta/cli/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/codegangsta/cli/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/codegangsta/cli/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/codegangsta/cli/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/codegangsta/cli/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/codegangsta/cli/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/codegangsta/cli/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/codegangsta/cli/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/codegangsta/cli/compare/v0.1.0...v1.0.0
[Unreleased]: https://github.com/urfave/cli/compare/v1.17.0...HEAD
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0

109
README.md
View File

@ -1,13 +1,18 @@
[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli)
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli)
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli)
[![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli)
[![top level coverage](https://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) /
[![altsrc coverage](https://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc)
[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/meatballhat/cli)
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli)
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) /
[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc)
# cli
**Notice:** This is the library formally known as
`github.com/codegangsta/cli` -- Github will automatically redirect requests
to this repository, but we recommend updating your references for clarity.
cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
## Overview
@ -25,7 +30,7 @@ instructions](http://golang.org/doc/install.html).
To install cli, simply run:
```
$ go get github.com/codegangsta/cli
$ go get github.com/urfave/cli
```
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
@ -33,6 +38,12 @@ Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands c
export PATH=$PATH:$GOPATH/bin
```
### Supported platforms
cli is tested against multiple versions of Go on Linux, and against the latest
released version of Go on OS X and Windows. For full details, see
[`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml).
### Using the `v2` branch
There is currently a long-lived branch named `v2` that is intended to land as
@ -44,13 +55,13 @@ that, please use whatever version pinning of your preference, such as via
`gopkg.in`:
```
$ go get gopkg.in/codegangsta/cli.v2
$ go get gopkg.in/urfave/cli.v2
```
``` go
...
import (
"gopkg.in/codegangsta/cli.v2" // imports as package "cli"
"gopkg.in/urfave/cli.v2" // imports as package "cli"
)
...
```
@ -62,13 +73,13 @@ to avoid any unexpected compatibility pains once `v2` becomes `master`, then
pinning to the `v1` branch is an acceptable option, e.g.:
```
$ go get gopkg.in/codegangsta/cli.v1
$ go get gopkg.in/urfave/cli.v1
```
``` go
...
import (
"gopkg.in/codegangsta/cli.v1" // imports as package "cli"
"gopkg.in/urfave/cli.v1" // imports as package "cli"
)
...
```
@ -82,7 +93,7 @@ package main
import (
"os"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
func main() {
@ -102,7 +113,7 @@ import (
"fmt"
"os"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
func main() {
@ -136,7 +147,7 @@ import (
"fmt"
"os"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
func main() {
@ -205,7 +216,7 @@ Setting and querying flags is simple.
``` go
...
app.Flags = []cli.Flag {
cli.StringFlag{
&cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
@ -232,7 +243,7 @@ You can also set a destination variable for a flag, to which the content will be
...
var language string
app.Flags = []cli.Flag {
cli.StringFlag{
&cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
@ -254,7 +265,7 @@ app.Action = func(c *cli.Context) error {
...
```
See full list of flags at http://godoc.org/github.com/codegangsta/cli
See full list of flags at http://godoc.org/github.com/urfave/cli
#### Placeholder Values
@ -264,8 +275,9 @@ indicated with back quotes.
For example this:
```go
cli.StringFlag{
Name: "config, c",
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "Load configuration from `FILE`",
}
```
@ -284,8 +296,9 @@ You can set alternate (or short) names for flags by providing a comma-delimited
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
&cli.StringFlag{
Name: "lang",
Aliases: []string{"l"},
Value: "english",
Usage: "language for the greeting",
},
@ -296,28 +309,30 @@ That flag can then be set with `--lang spanish` or `-l spanish`. Note that givin
#### 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 `EnvVars`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
&cli.StringFlag{
Name: "lang",
Aliases: []string{"l"},
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
EnvVars: []string{"APP_LANG"},
},
}
```
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
If `EnvVars` contains more than one string, the first environment variable that resolves is used as the default.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
&cli.StringFlag{
Name: "lang",
Aliases: []string{"l"},
Value: "english",
Usage: "language for the greeting",
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"},
},
}
```
@ -329,7 +344,7 @@ There is a separate package altsrc that adds support for getting flag values fro
In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below:
``` go
altsrc.NewIntFlag(cli.IntFlag{Name: "test"})
altsrc.NewIntFlag(&cli.IntFlag{Name: "test"})
```
Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below.
@ -358,8 +373,8 @@ Here is a more complete sample of a command using YAML support:
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "test"}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
err := command.Run(c)
@ -371,7 +386,7 @@ Subcommands can be defined for a more git-like command line app.
```go
...
app.Commands = []cli.Command{
app.Commands = []*cli.Command{
{
Name: "add",
Aliases: []string{"a"},
@ -394,7 +409,7 @@ app.Commands = []cli.Command{
Name: "template",
Aliases: []string{"r"},
Usage: "options for task templates",
Subcommands: []cli.Command{
Subcommands: []*cli.Command{
{
Name: "add",
Usage: "add a new template",
@ -427,7 +442,7 @@ E.g.
```go
...
app.Commands = []cli.Command{
app.Commands = []*cli.Command{
{
Name: "noop",
},
@ -469,13 +484,13 @@ package main
import (
"os"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.BoolFlag{
&cli.BoolFlag{
Name: "ginger-crouton",
Value: true,
Usage: "is it in the soup?",
@ -483,7 +498,7 @@ func main() {
}
app.Action = func(ctx *cli.Context) error {
if !ctx.Bool("ginger-crouton") {
return cli.NewExitError("it is not in the soup", 86)
return cli.Exit("it is not in the soup", 86)
}
return nil
}
@ -504,7 +519,7 @@ the App or its subcommands.
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
app.Commands = []*cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
@ -569,7 +584,7 @@ import (
"io"
"os"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
func main() {
@ -618,8 +633,16 @@ VERSION:
## Contribution Guidelines
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
Feel free to put up a pull request to fix a bug or maybe add a feature. I will
give it a code review and make sure that it does not break backwards
compatibility. If I or any other collaborators agree that it is in line with
the vision of the project, we will work with you to get the code into
a mergeable state and merge it into the master branch.
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
If you have contributed something significant to the project, we will most
likely add you as a collaborator. As a collaborator you are given the ability
to merge others pull requests. It is very important that new code does not
break existing code, so be careful about what code you do choose to merge.
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.
If you feel like you have contributed to the project but have not yet been
added as a collaborator, we probably forgot to add you, please open an issue.

View File

@ -5,9 +5,8 @@ import (
"fmt"
"os"
"strconv"
"strings"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
// FlagInputSourceExtension is an extension interface of cli.Flag that
@ -63,30 +62,30 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context
}
}
// GenericFlag is the flag type that wraps cli.GenericFlag to allow
// GenericFlag is the flag type that wraps *cli.GenericFlag to allow
// for other values to be specified
type GenericFlag struct {
cli.GenericFlag
*cli.GenericFlag
set *flag.FlagSet
}
// NewGenericFlag creates a new GenericFlag
func NewGenericFlag(flag cli.GenericFlag) *GenericFlag {
func NewGenericFlag(flag *cli.GenericFlag) *GenericFlag {
return &GenericFlag{GenericFlag: flag, set: nil}
}
// ApplyInputSourceValue applies a generic value to the flagSet if required
func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil {
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
value, err := isc.Generic(f.GenericFlag.Name)
if err != nil {
return err
}
if value != nil {
eachName(f.Name, func(name string) {
f.set.Set(f.Name, value.String())
})
for _, name := range f.Names() {
f.set.Set(name, value.String())
}
}
}
}
@ -101,34 +100,34 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) {
f.GenericFlag.Apply(set)
}
// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow
// StringSliceFlag is the flag type that wraps *cli.StringSliceFlag to allow
// for other values to be specified
type StringSliceFlag struct {
cli.StringSliceFlag
*cli.StringSliceFlag
set *flag.FlagSet
}
// NewStringSliceFlag creates a new StringSliceFlag
func NewStringSliceFlag(flag cli.StringSliceFlag) *StringSliceFlag {
func NewStringSliceFlag(flag *cli.StringSliceFlag) *StringSliceFlag {
return &StringSliceFlag{StringSliceFlag: flag, set: nil}
}
// ApplyInputSourceValue applies a StringSlice value to the flagSet if required
func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil {
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
value, err := isc.StringSlice(f.StringSliceFlag.Name)
if err != nil {
return err
}
if value != nil {
var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...))
eachName(f.Name, func(name string) {
underlyingFlag := f.set.Lookup(f.Name)
for _, name := range f.Names() {
underlyingFlag := f.set.Lookup(name)
if underlyingFlag != nil {
underlyingFlag.Value = &sliceValue
}
})
}
}
}
}
@ -142,34 +141,34 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
f.StringSliceFlag.Apply(set)
}
// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow
// IntSliceFlag is the flag type that wraps *cli.IntSliceFlag to allow
// for other values to be specified
type IntSliceFlag struct {
cli.IntSliceFlag
*cli.IntSliceFlag
set *flag.FlagSet
}
// NewIntSliceFlag creates a new IntSliceFlag
func NewIntSliceFlag(flag cli.IntSliceFlag) *IntSliceFlag {
func NewIntSliceFlag(flag *cli.IntSliceFlag) *IntSliceFlag {
return &IntSliceFlag{IntSliceFlag: flag, set: nil}
}
// ApplyInputSourceValue applies a IntSlice value if required
func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil {
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
value, err := isc.IntSlice(f.IntSliceFlag.Name)
if err != nil {
return err
}
if value != nil {
var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...))
eachName(f.Name, func(name string) {
underlyingFlag := f.set.Lookup(f.Name)
for _, name := range f.Names() {
underlyingFlag := f.set.Lookup(name)
if underlyingFlag != nil {
underlyingFlag.Value = &sliceValue
}
})
}
}
}
}
@ -183,30 +182,30 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
f.IntSliceFlag.Apply(set)
}
// BoolFlag is the flag type that wraps cli.BoolFlag to allow
// BoolFlag is the flag type that wraps *cli.BoolFlag to allow
// for other values to be specified
type BoolFlag struct {
cli.BoolFlag
*cli.BoolFlag
set *flag.FlagSet
}
// NewBoolFlag creates a new BoolFlag
func NewBoolFlag(flag cli.BoolFlag) *BoolFlag {
func NewBoolFlag(flag *cli.BoolFlag) *BoolFlag {
return &BoolFlag{BoolFlag: flag, set: nil}
}
// ApplyInputSourceValue applies a Bool value to the flagSet if required
func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil {
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
value, err := isc.Bool(f.BoolFlag.Name)
if err != nil {
return err
}
if value {
eachName(f.Name, func(name string) {
f.set.Set(f.Name, strconv.FormatBool(value))
})
for _, name := range f.Names() {
f.set.Set(name, strconv.FormatBool(value))
}
}
}
}
@ -220,30 +219,30 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) {
f.BoolFlag.Apply(set)
}
// StringFlag is the flag type that wraps cli.StringFlag to allow
// StringFlag is the flag type that wraps *cli.StringFlag to allow
// for other values to be specified
type StringFlag struct {
cli.StringFlag
*cli.StringFlag
set *flag.FlagSet
}
// NewStringFlag creates a new StringFlag
func NewStringFlag(flag cli.StringFlag) *StringFlag {
func NewStringFlag(flag *cli.StringFlag) *StringFlag {
return &StringFlag{StringFlag: flag, set: nil}
}
// ApplyInputSourceValue applies a String value to the flagSet if required
func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil {
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
value, err := isc.String(f.StringFlag.Name)
if err != nil {
return err
}
if value != "" {
eachName(f.Name, func(name string) {
f.set.Set(f.Name, value)
})
for _, name := range f.Names() {
f.set.Set(name, value)
}
}
}
}
@ -258,30 +257,30 @@ func (f *StringFlag) Apply(set *flag.FlagSet) {
f.StringFlag.Apply(set)
}
// IntFlag is the flag type that wraps cli.IntFlag to allow
// IntFlag is the flag type that wraps *cli.IntFlag to allow
// for other values to be specified
type IntFlag struct {
cli.IntFlag
*cli.IntFlag
set *flag.FlagSet
}
// NewIntFlag creates a new IntFlag
func NewIntFlag(flag cli.IntFlag) *IntFlag {
func NewIntFlag(flag *cli.IntFlag) *IntFlag {
return &IntFlag{IntFlag: flag, set: nil}
}
// ApplyInputSourceValue applies a int value to the flagSet if required
func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil {
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
value, err := isc.Int(f.IntFlag.Name)
if err != nil {
return err
}
if value > 0 {
eachName(f.Name, func(name string) {
f.set.Set(f.Name, strconv.FormatInt(int64(value), 10))
})
for _, name := range f.Names() {
f.set.Set(name, strconv.FormatInt(int64(value), 10))
}
}
}
}
@ -295,30 +294,30 @@ func (f *IntFlag) Apply(set *flag.FlagSet) {
f.IntFlag.Apply(set)
}
// DurationFlag is the flag type that wraps cli.DurationFlag to allow
// DurationFlag is the flag type that wraps *cli.DurationFlag to allow
// for other values to be specified
type DurationFlag struct {
cli.DurationFlag
*cli.DurationFlag
set *flag.FlagSet
}
// NewDurationFlag creates a new DurationFlag
func NewDurationFlag(flag cli.DurationFlag) *DurationFlag {
func NewDurationFlag(flag *cli.DurationFlag) *DurationFlag {
return &DurationFlag{DurationFlag: flag, set: nil}
}
// ApplyInputSourceValue applies a Duration value to the flagSet if required
func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil {
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
value, err := isc.Duration(f.DurationFlag.Name)
if err != nil {
return err
}
if value > 0 {
eachName(f.Name, func(name string) {
f.set.Set(f.Name, value.String())
})
for _, name := range f.Names() {
f.set.Set(name, value.String())
}
}
}
}
@ -333,31 +332,31 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) {
f.DurationFlag.Apply(set)
}
// Float64Flag is the flag type that wraps cli.Float64Flag to allow
// Float64Flag is the flag type that wraps *cli.Float64Flag to allow
// for other values to be specified
type Float64Flag struct {
cli.Float64Flag
*cli.Float64Flag
set *flag.FlagSet
}
// NewFloat64Flag creates a new Float64Flag
func NewFloat64Flag(flag cli.Float64Flag) *Float64Flag {
func NewFloat64Flag(flag *cli.Float64Flag) *Float64Flag {
return &Float64Flag{Float64Flag: flag, set: nil}
}
// ApplyInputSourceValue applies a Float64 value to the flagSet if required
func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil {
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
value, err := isc.Float64(f.Float64Flag.Name)
if err != nil {
return err
}
if value > 0 {
floatStr := float64ToString(value)
eachName(f.Name, func(name string) {
f.set.Set(f.Name, floatStr)
})
for _, name := range f.Names() {
f.set.Set(name, floatStr)
}
}
}
}
@ -372,9 +371,8 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) {
f.Float64Flag.Apply(set)
}
func isEnvVarSet(envVars string) bool {
for _, envVar := range strings.Split(envVars, ",") {
envVar = strings.TrimSpace(envVar)
func isEnvVarSet(envVars []string) bool {
for _, envVar := range envVars {
if envVal := os.Getenv(envVar); envVal != "" {
// TODO: Can't use this for bools as
// set means that it was true or false based on
@ -391,11 +389,3 @@ func isEnvVarSet(envVars string) bool {
func float64ToString(f float64) string {
return fmt.Sprintf("%v", f)
}
func eachName(longName string, fn func(string)) {
parts := strings.Split(longName, ",")
for _, name := range parts {
name = strings.Trim(name, " ")
fn(name)
}
}

View File

@ -8,7 +8,7 @@ import (
"testing"
"time"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
type testApplyInputSource struct {
@ -26,7 +26,7 @@ type testApplyInputSource struct {
func TestGenericApplyInputSourceValue(t *testing.T) {
v := &Parser{"abc", "def"}
c := runTest(t, testApplyInputSource{
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
FlagName: "test",
MapValue: v,
})
@ -36,7 +36,7 @@ func TestGenericApplyInputSourceValue(t *testing.T) {
func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
p := &Parser{"abc", "def"}
c := runTest(t, testApplyInputSource{
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
FlagName: "test",
MapValue: &Parser{"efg", "hig"},
ContextValueString: p.String(),
@ -46,7 +46,11 @@ func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}),
Flag: NewGenericFlag(&cli.GenericFlag{
Name: "test",
Value: &Parser{},
EnvVars: []string{"TEST"},
}),
FlagName: "test",
MapValue: &Parser{"efg", "hij"},
EnvVarName: "TEST",
@ -57,7 +61,7 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestStringSliceApplyInputSourceValue(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
FlagName: "test",
MapValue: []string{"hello", "world"},
})
@ -66,7 +70,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) {
func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
FlagName: "test",
MapValue: []string{"hello", "world"},
ContextValueString: "ohno",
@ -76,7 +80,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}),
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test",
MapValue: []string{"hello", "world"},
EnvVarName: "TEST",
@ -87,7 +91,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestIntSliceApplyInputSourceValue(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
FlagName: "test",
MapValue: []int{1, 2},
})
@ -96,7 +100,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) {
func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
FlagName: "test",
MapValue: []int{1, 2},
ContextValueString: "3",
@ -106,7 +110,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}),
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test",
MapValue: []int{1, 2},
EnvVarName: "TEST",
@ -117,7 +121,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestBoolApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
FlagName: "test",
MapValue: true,
})
@ -126,7 +130,7 @@ func TestBoolApplyInputSourceMethodSet(t *testing.T) {
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
FlagName: "test",
MapValue: false,
ContextValueString: "true",
@ -136,7 +140,7 @@ func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}),
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test",
MapValue: false,
EnvVarName: "TEST",
@ -147,7 +151,7 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestStringApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
FlagName: "test",
MapValue: "hello",
})
@ -156,7 +160,7 @@ func TestStringApplyInputSourceMethodSet(t *testing.T) {
func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
FlagName: "test",
MapValue: "hello",
ContextValueString: "goodbye",
@ -166,7 +170,7 @@ func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}),
Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test",
MapValue: "hello",
EnvVarName: "TEST",
@ -177,7 +181,7 @@ func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestIntApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
FlagName: "test",
MapValue: 15,
})
@ -186,7 +190,7 @@ func TestIntApplyInputSourceMethodSet(t *testing.T) {
func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
FlagName: "test",
MapValue: 15,
ContextValueString: "7",
@ -196,7 +200,7 @@ func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}),
Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test",
MapValue: 15,
EnvVarName: "TEST",
@ -207,7 +211,7 @@ func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestDurationApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
FlagName: "test",
MapValue: time.Duration(30 * time.Second),
})
@ -216,7 +220,7 @@ func TestDurationApplyInputSourceMethodSet(t *testing.T) {
func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
FlagName: "test",
MapValue: time.Duration(30 * time.Second),
ContextValueString: time.Duration(15 * time.Second).String(),
@ -226,7 +230,7 @@ func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}),
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test",
MapValue: time.Duration(30 * time.Second),
EnvVarName: "TEST",
@ -237,7 +241,7 @@ func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
FlagName: "test",
MapValue: 1.3,
})
@ -246,7 +250,7 @@ func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
FlagName: "test",
MapValue: 1.3,
ContextValueString: fmt.Sprintf("%v", 1.4),
@ -256,7 +260,7 @@ func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}),
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test",
MapValue: 1.3,
EnvVarName: "TEST",

View File

@ -3,7 +3,7 @@ package altsrc
import (
"time"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
// InputSourceContext is an interface used to allow

View File

@ -6,7 +6,7 @@ import (
"strings"
"time"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
// MapInputSource implements InputSourceContext to return

View File

@ -11,7 +11,7 @@ import (
"os"
"testing"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
)
func TestCommandYamlFileTest(t *testing.T) {
@ -35,8 +35,8 @@ func TestCommandYamlFileTest(t *testing.T) {
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "test"}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
err := command.Run(c)
@ -68,8 +68,8 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) {
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
@ -103,8 +103,8 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) {
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
@ -135,8 +135,8 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "test"}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
@ -168,8 +168,8 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) {
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "top.test"}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "top.test"}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
@ -200,8 +200,8 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "test", Value: 7}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
@ -233,8 +233,8 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) {
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
@ -268,8 +268,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
err := command.Run(c)
@ -303,8 +303,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *tes
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}),
cli.StringFlag{Name: "load"}},
NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}),
&cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
err := command.Run(c)

View File

@ -12,7 +12,7 @@ import (
"net/url"
"os"
"github.com/codegangsta/cli"
"github.com/urfave/cli"
"gopkg.in/yaml.v2"
)

76
app.go
View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"time"
)
@ -26,7 +27,7 @@ type App struct {
// Version of the program
Version string
// List of commands to execute
Commands []Command
Commands []*Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
@ -35,8 +36,8 @@ type App struct {
HideHelp bool
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool
// Populate on app startup, only gettable through method Categories()
categories CommandCategories
// Categories contains the categorized commands and is populated on app startup
Categories CommandCategories
// An action to execute when the bash-completion flag is set
BashComplete BashCompleteFunc
// An action to execute before any subcommands are run, but after the context is ready
@ -54,7 +55,7 @@ type App struct {
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []Author
Authors []*Author
// Copyright of the binary if any
Copyright string
// Writer writer to write output to
@ -103,7 +104,7 @@ func (a *App) Setup() {
a.didSetup = true
newCmds := []Command{}
newCmds := []*Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
@ -112,16 +113,17 @@ func (a *App) Setup() {
}
a.Commands = newCmds
a.categories = CommandCategories{}
a.Categories = newCommandCategories()
for _, command := range a.Commands {
a.categories = a.categories.AddCommand(command.Category, command)
a.Categories.AddCommand(command.Category, command)
}
sort.Sort(a.categories)
sort.Sort(a.Categories.(*commandCategories))
// append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendCommand(helpCommand)
if HelpFlag != nil {
a.appendFlag(HelpFlag)
}
}
@ -163,7 +165,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, "Incorrect Usage: %s\n\n", err)
ShowAppHelp(context)
return err
}
@ -182,7 +184,7 @@ func (a *App) Run(arguments []string) (err error) {
defer func() {
if afterErr := a.After(context); afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
err = newMultiError(err, afterErr)
} else {
err = afterErr
}
@ -223,14 +225,15 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendCommand(helpCommand)
if HelpFlag != nil {
a.appendFlag(HelpFlag)
}
}
}
newCmds := []Command{}
newCmds := []*Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
@ -272,7 +275,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, "Incorrect Usage: %s\n\n", err)
ShowSubcommandHelp(context)
return err
}
@ -293,7 +296,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
if afterErr != nil {
HandleExitCoder(err)
if err != nil {
err = NewMultiError(err, afterErr)
err = newMultiError(err, afterErr)
} else {
err = afterErr
}
@ -330,29 +333,22 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
func (a *App) Command(name string) *Command {
for _, c := range a.Commands {
if c.HasName(name) {
return &c
return c
}
}
return nil
}
// Categories returns a slice containing all the categories with the commands they contain
func (a *App) Categories() CommandCategories {
return a.categories
}
// VisibleCategories returns a slice of categories and commands that are
// Hidden=false
func (a *App) VisibleCategories() []*CommandCategory {
ret := []*CommandCategory{}
for _, category := range a.categories {
if visible := func() *CommandCategory {
for _, command := range category.Commands {
if !command.Hidden {
func (a *App) VisibleCategories() []CommandCategory {
ret := []CommandCategory{}
for _, category := range a.Categories.Categories() {
if visible := func() CommandCategory {
if len(category.VisibleCommands()) > 0 {
return category
}
}
return nil
}(); visible != nil {
ret = append(ret, visible)
@ -362,8 +358,8 @@ func (a *App) VisibleCategories() []*CommandCategory {
}
// VisibleCommands returns a slice of the Commands with Hidden=false
func (a *App) VisibleCommands() []Command {
ret := []Command{}
func (a *App) VisibleCommands() []*Command {
ret := []*Command{}
for _, command := range a.Commands {
if !command.Hidden {
ret = append(ret, command)
@ -379,7 +375,7 @@ func (a *App) VisibleFlags() []Flag {
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {
if reflect.DeepEqual(flag, f) {
return true
}
}
@ -397,9 +393,15 @@ func (a *App) errWriter() io.Writer {
return a.ErrWriter
}
func (a *App) appendFlag(flag Flag) {
if !a.hasFlag(flag) {
a.Flags = append(a.Flags, flag)
func (a *App) appendFlag(fl Flag) {
if !hasFlag(a.Flags, fl) {
a.Flags = append(a.Flags, fl)
}
}
func (a *App) appendCommand(c *Command) {
if !hasCommand(a.Commands, c) {
a.Commands = append(a.Commands, c)
}
}
@ -410,7 +412,7 @@ type Author struct {
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
func (a *Author) String() string {
e := ""
if a.Email != "" {
e = "<" + a.Email + "> "

View File

@ -24,14 +24,14 @@ func ExampleApp_Run() {
app := NewApp()
app.Name = "greet"
app.Flags = []Flag{
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
&StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Action = func(c *Context) error {
fmt.Printf("Hello %v\n", c.String("name"))
return nil
}
app.UsageText = "app [first_arg] [second_arg]"
app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}}
app.Authors = []*Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}}
app.Run(os.Args)
// Output:
// Hello Jeremy
@ -42,20 +42,20 @@ func ExampleApp_Run_subcommand() {
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
app := NewApp()
app.Name = "say"
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "hello",
Aliases: []string{"hi"},
Usage: "use it to see a description",
Description: "This is how we describe hello the function",
Subcommands: []Command{
Subcommands: []*Command{
{
Name: "english",
Aliases: []string{"en"},
Usage: "sends a greeting in english",
Description: "greets someone in english",
Flags: []Flag{
StringFlag{
&StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
@ -82,9 +82,9 @@ func ExampleApp_Run_help() {
app := NewApp()
app.Name = "greet"
app.Flags = []Flag{
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
&StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "describeit",
Aliases: []string{"d"},
@ -115,7 +115,7 @@ func ExampleApp_Run_bashComplete() {
app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "describeit",
Aliases: []string{"d"},
@ -175,9 +175,9 @@ var commandAppTests = []struct {
func TestApp_Command(t *testing.T) {
app := NewApp()
fooCommand := Command{Name: "foobar", Aliases: []string{"f"}}
batCommand := Command{Name: "batbaz", Aliases: []string{"b"}}
app.Commands = []Command{
fooCommand := &Command{Name: "foobar", Aliases: []string{"f"}}
batCommand := &Command{Name: "batbaz", Aliases: []string{"b"}}
app.Commands = []*Command{
fooCommand,
batCommand,
}
@ -191,7 +191,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
var context *Context
a := NewApp()
a.Commands = []Command{
a.Commands = []*Command{
{
Name: "foo",
Action: func(c *Context) error {
@ -199,7 +199,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
return nil
},
Flags: []Flag{
StringFlag{
&StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
@ -216,13 +216,13 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
var parsedOption string
var args []string
var args Args
app := NewApp()
command := Command{
command := &Command{
Name: "cmd",
Flags: []Flag{
StringFlag{Name: "option", Value: "", Usage: "some option"},
&StringFlag{Name: "option", Value: "", Usage: "some option"},
},
Action: func(c *Context) error {
parsedOption = c.String("option")
@ -230,58 +230,58 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
return nil
},
}
app.Commands = []Command{command}
app.Commands = []*Command{command}
app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"})
expect(t, parsedOption, "my-option")
expect(t, args[0], "my-arg")
expect(t, args[1], "--")
expect(t, args[2], "--notARealFlag")
expect(t, args.Get(0), "my-arg")
expect(t, args.Get(1), "--")
expect(t, args.Get(2), "--notARealFlag")
}
func TestApp_CommandWithDash(t *testing.T) {
var args []string
var args Args
app := NewApp()
command := Command{
command := &Command{
Name: "cmd",
Action: func(c *Context) error {
args = c.Args()
return nil
},
}
app.Commands = []Command{command}
app.Commands = []*Command{command}
app.Run([]string{"", "cmd", "my-arg", "-"})
expect(t, args[0], "my-arg")
expect(t, args[1], "-")
expect(t, args.Get(0), "my-arg")
expect(t, args.Get(1), "-")
}
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
var args []string
var args Args
app := NewApp()
command := Command{
command := &Command{
Name: "cmd",
Action: func(c *Context) error {
args = c.Args()
return nil
},
}
app.Commands = []Command{command}
app.Commands = []*Command{command}
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
expect(t, args[0], "my-arg")
expect(t, args[1], "--")
expect(t, args[2], "notAFlagAtAll")
expect(t, args.Get(0), "my-arg")
expect(t, args.Get(1), "--")
expect(t, args.Get(2), "notAFlagAtAll")
}
func TestApp_VisibleCommands(t *testing.T) {
app := NewApp()
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "frob",
HelpName: "foo frob",
@ -296,7 +296,7 @@ func TestApp_VisibleCommands(t *testing.T) {
}
app.Setup()
expected := []Command{
expected := []*Command{
app.Commands[0],
app.Commands[2], // help
}
@ -310,14 +310,22 @@ func TestApp_VisibleCommands(t *testing.T) {
expect(t, fmt.Sprintf("%p", expectedCommand.Action), fmt.Sprintf("%p", actualCommand.Action))
}
func() {
// nil out funcs, as they cannot be compared
// (https://github.com/golang/go/issues/8554)
expectedAction := expectedCommand.Action
actualAction := actualCommand.Action
defer func() {
expectedCommand.Action = expectedAction
actualCommand.Action = actualAction
}()
expectedCommand.Action = nil
actualCommand.Action = nil
if !reflect.DeepEqual(expectedCommand, actualCommand) {
t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand)
}
}()
}
}
@ -326,7 +334,7 @@ func TestApp_Float64Flag(t *testing.T) {
app := NewApp()
app.Flags = []Flag{
Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
&Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
}
app.Action = func(c *Context) error {
meters = c.Float64("height")
@ -343,11 +351,11 @@ func TestApp_ParseSliceFlags(t *testing.T) {
var parsedStringSlice []string
app := NewApp()
command := Command{
command := &Command{
Name: "cmd",
Flags: []Flag{
IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"},
StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"},
&IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"},
&StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"},
},
Action: func(c *Context) error {
parsedIntSlice = c.IntSlice("p")
@ -357,7 +365,7 @@ func TestApp_ParseSliceFlags(t *testing.T) {
return nil
},
}
app.Commands = []Command{command}
app.Commands = []*Command{command}
app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"})
@ -401,11 +409,11 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
var parsedStringSlice []string
app := NewApp()
command := Command{
command := &Command{
Name: "cmd",
Flags: []Flag{
IntSliceFlag{Name: "a", Usage: "set numbers"},
StringSliceFlag{Name: "str", Usage: "set strings"},
&IntSliceFlag{Name: "a", Usage: "set numbers"},
&StringSliceFlag{Name: "str", Usage: "set strings"},
},
Action: func(c *Context) error {
parsedIntSlice = c.IntSlice("a")
@ -413,7 +421,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
return nil
},
}
app.Commands = []Command{command}
app.Commands = []*Command{command}
app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"})
@ -491,7 +499,7 @@ func TestApp_BeforeFunc(t *testing.T) {
return nil
}
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "sub",
Action: func(c *Context) error {
@ -503,7 +511,7 @@ func TestApp_BeforeFunc(t *testing.T) {
}
app.Flags = []Flag{
StringFlag{Name: "opt"},
&StringFlag{Name: "opt"},
}
// run with the Before() func succeeding
@ -583,7 +591,7 @@ func TestApp_AfterFunc(t *testing.T) {
return nil
}
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "sub",
Action: func(c *Context) error {
@ -595,7 +603,7 @@ func TestApp_AfterFunc(t *testing.T) {
}
app.Flags = []Flag{
StringFlag{Name: "opt"},
&StringFlag{Name: "opt"},
}
// run with the After() func succeeding
@ -639,7 +647,7 @@ func TestAppNoHelpFlag(t *testing.T) {
HelpFlag = oldFlag
}()
HelpFlag = BoolFlag{}
HelpFlag = nil
app := NewApp()
app.Writer = ioutil.Discard
@ -698,7 +706,7 @@ func TestApp_CommandNotFound(t *testing.T) {
counts.CommandNotFound = counts.Total
}
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "bar",
Action: func(c *Context) error {
@ -765,7 +773,7 @@ func TestApp_OrderOfOperations(t *testing.T) {
}
app.After = afterNoError
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "bar",
Action: func(c *Context) error {
@ -871,21 +879,21 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
buf := new(bytes.Buffer)
app.Writer = buf
subCmdBar := Command{
subCmdBar := &Command{
Name: "bar",
Usage: "does bar things",
}
subCmdBaz := Command{
subCmdBaz := &Command{
Name: "baz",
Usage: "does baz things",
}
cmd := Command{
cmd := &Command{
Name: "foo",
Description: "descriptive wall of text about how it does foo things",
Subcommands: []Command{subCmdBar, subCmdBaz},
Subcommands: []*Command{subCmdBar, subCmdBaz},
}
app.Commands = []Command{cmd}
app.Commands = []*Command{cmd}
err := app.Run(flagSet)
if err != nil {
@ -916,16 +924,16 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) {
buf := new(bytes.Buffer)
app.Writer = buf
app.Name = "command"
subCmd := Command{
subCmd := &Command{
Name: "bar",
Usage: "does bar things",
}
cmd := Command{
cmd := &Command{
Name: "foo",
Description: "foo commands",
Subcommands: []Command{subCmd},
Subcommands: []*Command{subCmd},
}
app.Commands = []Command{cmd}
app.Commands = []*Command{cmd}
err := app.Run([]string{"command", "foo", "bar", "--help"})
if err != nil {
@ -933,11 +941,14 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) {
}
output := buf.String()
if !strings.Contains(output, "command foo bar - does bar things") {
t.Errorf("expected full path to subcommand: %s", output)
expected := "command foo bar - does bar things"
if !strings.Contains(output, expected) {
t.Errorf("expected %q in output: %s", expected, output)
}
if !strings.Contains(output, "command foo bar [arguments...]") {
t.Errorf("expected full path to subcommand: %s", output)
expected = "command foo bar [command options] [arguments...]"
if !strings.Contains(output, expected) {
t.Errorf("expected %q in output: %s", expected, output)
}
}
@ -946,17 +957,17 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) {
buf := new(bytes.Buffer)
app.Writer = buf
app.Name = "command"
subCmd := Command{
subCmd := &Command{
Name: "bar",
HelpName: "custom",
Usage: "does bar things",
}
cmd := Command{
cmd := &Command{
Name: "foo",
Description: "foo commands",
Subcommands: []Command{subCmd},
Subcommands: []*Command{subCmd},
}
app.Commands = []Command{cmd}
app.Commands = []*Command{cmd}
err := app.Run([]string{"command", "foo", "bar", "--help"})
if err != nil {
@ -964,11 +975,15 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) {
}
output := buf.String()
if !strings.Contains(output, "custom - does bar things") {
t.Errorf("expected HelpName for subcommand: %s", output)
expected := "custom - does bar things"
if !strings.Contains(output, expected) {
t.Errorf("expected %q in output: %s", expected, output)
}
if !strings.Contains(output, "custom [arguments...]") {
t.Errorf("expected HelpName to subcommand: %s", output)
expected = "custom [command options] [arguments...]"
if !strings.Contains(output, expected) {
t.Errorf("expected %q in output: %s", expected, output)
}
}
@ -977,17 +992,17 @@ func TestApp_Run_CommandHelpName(t *testing.T) {
buf := new(bytes.Buffer)
app.Writer = buf
app.Name = "command"
subCmd := Command{
subCmd := &Command{
Name: "bar",
Usage: "does bar things",
}
cmd := Command{
cmd := &Command{
Name: "foo",
HelpName: "custom",
Description: "foo commands",
Subcommands: []Command{subCmd},
Subcommands: []*Command{subCmd},
}
app.Commands = []Command{cmd}
app.Commands = []*Command{cmd}
err := app.Run([]string{"command", "foo", "bar", "--help"})
if err != nil {
@ -995,11 +1010,15 @@ func TestApp_Run_CommandHelpName(t *testing.T) {
}
output := buf.String()
if !strings.Contains(output, "command foo bar - does bar things") {
t.Errorf("expected full path to subcommand: %s", output)
expected := "command foo bar - does bar things"
if !strings.Contains(output, expected) {
t.Errorf("expected %q in output: %s", expected, output)
}
if !strings.Contains(output, "command foo bar [arguments...]") {
t.Errorf("expected full path to subcommand: %s", output)
expected = "command foo bar [command options] [arguments...]"
if !strings.Contains(output, expected) {
t.Errorf("expected %q in output: %s", expected, output)
}
}
@ -1008,17 +1027,17 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) {
buf := new(bytes.Buffer)
app.Writer = buf
app.Name = "base"
subCmd := Command{
subCmd := &Command{
Name: "bar",
HelpName: "custom",
Usage: "does bar things",
}
cmd := Command{
cmd := &Command{
Name: "foo",
Description: "foo commands",
Subcommands: []Command{subCmd},
Subcommands: []*Command{subCmd},
}
app.Commands = []Command{cmd}
app.Commands = []*Command{cmd}
err := app.Run([]string{"command", "foo", "--help"})
if err != nil {
@ -1026,11 +1045,15 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) {
}
output := buf.String()
if !strings.Contains(output, "base foo - foo commands") {
t.Errorf("expected full path to subcommand: %s", output)
expected := "base foo - foo commands"
if !strings.Contains(output, expected) {
t.Errorf("expected %q in output: %q", expected, output)
}
if !strings.Contains(output, "base foo command [command options] [arguments...]") {
t.Errorf("expected full path to subcommand: %s", output)
expected = "base foo command [command options] [arguments...]"
if !strings.Contains(output, expected) {
t.Errorf("expected %q in output: %q", expected, output)
}
}
@ -1100,7 +1123,7 @@ func TestApp_Run_Version(t *testing.T) {
func TestApp_Run_Categories(t *testing.T) {
app := NewApp()
app.Name = "categories"
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "command1",
Category: "1",
@ -1119,23 +1142,24 @@ func TestApp_Run_Categories(t *testing.T) {
app.Run([]string{"categories"})
expect := CommandCategories{
&CommandCategory{
Name: "1",
Commands: []Command{
expect := commandCategories([]*commandCategory{
{
name: "1",
commands: []*Command{
app.Commands[0],
app.Commands[1],
},
},
&CommandCategory{
Name: "2",
Commands: []Command{
{
name: "2",
commands: []*Command{
app.Commands[2],
},
},
}
if !reflect.DeepEqual(app.Categories(), expect) {
t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect)
})
if !reflect.DeepEqual(app.Categories, &expect) {
t.Fatalf("expected categories %#v, to equal %#v", app.Categories, &expect)
}
output := buf.String()
@ -1149,7 +1173,7 @@ func TestApp_Run_Categories(t *testing.T) {
func TestApp_VisibleCategories(t *testing.T) {
app := NewApp()
app.Name = "visible-categories"
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "command1",
Category: "1",
@ -1168,16 +1192,16 @@ func TestApp_VisibleCategories(t *testing.T) {
},
}
expected := []*CommandCategory{
{
Name: "2",
Commands: []Command{
expected := []CommandCategory{
&commandCategory{
name: "2",
commands: []*Command{
app.Commands[1],
},
},
{
Name: "3",
Commands: []Command{
&commandCategory{
name: "3",
commands: []*Command{
app.Commands[2],
},
},
@ -1188,7 +1212,7 @@ func TestApp_VisibleCategories(t *testing.T) {
app = NewApp()
app.Name = "visible-categories"
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "command1",
Category: "1",
@ -1208,10 +1232,10 @@ func TestApp_VisibleCategories(t *testing.T) {
},
}
expected = []*CommandCategory{
{
Name: "3",
Commands: []Command{
expected = []CommandCategory{
&commandCategory{
name: "3",
commands: []*Command{
app.Commands[2],
},
},
@ -1222,7 +1246,7 @@ func TestApp_VisibleCategories(t *testing.T) {
app = NewApp()
app.Name = "visible-categories"
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "command1",
Category: "1",
@ -1243,10 +1267,8 @@ func TestApp_VisibleCategories(t *testing.T) {
},
}
expected = []*CommandCategory{}
app.Setup()
expect(t, expected, app.VisibleCategories())
expect(t, []CommandCategory{}, app.VisibleCategories())
}
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
@ -1270,9 +1292,9 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) {
app := NewApp()
app.Commands = []Command{
app.Commands = []*Command{
{
Subcommands: []Command{
Subcommands: []*Command{
{
Name: "sub",
},
@ -1299,7 +1321,7 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) {
func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) {
app := NewApp()
app.Flags = []Flag{
IntFlag{Name: "flag"},
&IntFlag{Name: "flag"},
}
app.OnUsageError = func(c *Context, err error, isSubcommand bool) error {
if isSubcommand {
@ -1310,7 +1332,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) {
}
return errors.New("intercepted: " + err.Error())
}
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "bar",
},
@ -1329,7 +1351,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) {
func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
app := NewApp()
app.Flags = []Flag{
IntFlag{Name: "flag"},
&IntFlag{Name: "flag"},
}
app.OnUsageError = func(c *Context, err error, isSubcommand bool) error {
if isSubcommand {
@ -1340,7 +1362,7 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) {
}
return errors.New("intercepted: " + err.Error())
}
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "bar",
},

View File

@ -2,15 +2,24 @@ version: "{build}"
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\urfave\cli
environment:
GOPATH: C:\gopath
GOVERSION: 1.6
PYTHON: C:\Python27-x64
PYTHON_VERSION: 2.7.x
PYTHON_ARCH: 64
GFMXR_DEBUG: 1
install:
- go version
- go env
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
- go version
- go env
- go get github.com/urfave/gfmxr/...
- go get -v -t ./...
build_script:
- cd %APPVEYOR_BUILD_FOLDER%
- go vet ./...
- go test -v ./...
test: off
deploy: off
- python runtests vet
- python runtests test
- python runtests gfmxr

60
args.go Normal file
View File

@ -0,0 +1,60 @@
package cli
import "errors"
var (
argsRangeErr = errors.New("index out of range")
)
type Args interface {
// Get returns the nth argument, or else a blank string
Get(n int) string
// First returns the first argument, or else a blank string
First() string
// Tail returns the rest of the arguments (not the first one)
// or else an empty string slice
Tail() []string
// Len returns the length of the wrapped slice
Len() int
// Present checks if there are any arguments present
Present() bool
// Slice returns a copy of the internal slice
Slice() []string
}
type args []string
func (a *args) Get(n int) string {
if len(*a) > n {
return (*a)[n]
}
return ""
}
func (a *args) First() string {
return a.Get(0)
}
func (a *args) Tail() []string {
if a.Len() >= 2 {
tail := []string((*a)[1:])
ret := make([]string, len(tail))
copy(ret, tail)
return ret
}
return []string{}
}
func (a *args) Len() int {
return len(*a)
}
func (a *args) Present() bool {
return a.Len() != 0
}
func (a *args) Slice() []string {
ret := make([]string, len(*a))
copy(ret, []string(*a))
return ret
}

View File

@ -1,41 +1,82 @@
package cli
// CommandCategories is a slice of *CommandCategory.
type CommandCategories []*CommandCategory
type CommandCategories interface {
// AddCommand adds a command to a category, creating a new category if necessary.
AddCommand(category string, command *Command)
// Categories returns a copy of the category slice
Categories() []CommandCategory
}
type commandCategories []*commandCategory
func newCommandCategories() CommandCategories {
ret := commandCategories([]*commandCategory{})
return &ret
}
func (c *commandCategories) Less(i, j int) bool {
return (*c)[i].Name() < (*c)[j].Name()
}
func (c *commandCategories) Len() int {
return len(*c)
}
func (c *commandCategories) Swap(i, j int) {
(*c)[i], (*c)[j] = (*c)[j], (*c)[i]
}
func (c *commandCategories) AddCommand(category string, command *Command) {
for _, commandCategory := range []*commandCategory(*c) {
if commandCategory.name == category {
commandCategory.commands = append(commandCategory.commands, command)
return
}
}
newVal := commandCategories(append(*c,
&commandCategory{name: category, commands: []*Command{command}}))
(*c) = newVal
}
func (c *commandCategories) Categories() []CommandCategory {
ret := make([]CommandCategory, len(*c))
for i, cat := range *c {
ret[i] = cat
}
return ret
}
// CommandCategory is a category containing commands.
type CommandCategory struct {
Name string
Commands Commands
type CommandCategory interface {
// Name returns the category name string
Name() string
// VisibleCommands returns a slice of the Commands with Hidden=false
VisibleCommands() []*Command
}
func (c CommandCategories) Less(i, j int) bool {
return c[i].Name < c[j].Name
type commandCategory struct {
name string
commands []*Command
}
func (c CommandCategories) Len() int {
return len(c)
}
func (c CommandCategories) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
// AddCommand adds a command to a category.
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
for _, commandCategory := range c {
if commandCategory.Name == category {
commandCategory.Commands = append(commandCategory.Commands, command)
return c
func newCommandCategory(name string) *commandCategory {
return &commandCategory{
name: name,
commands: []*Command{},
}
}
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
}
// VisibleCommands returns a slice of the Commands with Hidden=false
func (c *CommandCategory) VisibleCommands() []Command {
ret := []Command{}
for _, command := range c.Commands {
func (c *commandCategory) Name() string {
return c.name
}
func (c *commandCategory) VisibleCommands() []*Command {
if c.commands == nil {
c.commands = []*Command{}
}
ret := []*Command{}
for _, command := range c.commands {
if !command.Hidden {
ret = append(ret, command)
}

View File

@ -36,7 +36,7 @@ type Command struct {
// Execute this function if a usage error occurs.
OnUsageError OnUsageErrorFunc
// List of child commands
Subcommands Commands
Subcommands []*Command
// List of flags to parse
Flags []Flag
// Treat all flags as normal arguments if true
@ -53,32 +53,26 @@ type Command struct {
// FullName returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
func (c *Command) FullName() string {
if c.commandNamePath == nil {
return c.Name
}
return strings.Join(c.commandNamePath, " ")
}
// Commands is a slice of Command
type Commands []Command
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) (err error) {
func (c *Command) Run(ctx *Context) (err error) {
if len(c.Subcommands) > 0 {
return c.startApp(ctx)
}
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
if !c.HideHelp && HelpFlag != nil {
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
c.appendFlag(HelpFlag)
}
if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag)
c.appendFlag(BashCompletionFlag)
}
set := flagSet(c.Name, c.Flags)
@ -96,7 +90,7 @@ func (c Command) Run(ctx *Context) (err error) {
HandleExitCoder(err)
return err
}
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
fmt.Fprintf(ctx.App.Writer, "Incorrect Usage: %s\n\n", err)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
@ -126,7 +120,7 @@ func (c Command) Run(ctx *Context) (err error) {
if afterErr != nil {
HandleExitCoder(err)
if err != nil {
err = NewMultiError(err, afterErr)
err = newMultiError(err, afterErr)
} else {
err = afterErr
}
@ -155,13 +149,12 @@ func (c Command) Run(ctx *Context) (err error) {
}
// Names returns the names including short names and aliases.
func (c Command) Names() []string {
names := []string{c.Name}
return append(names, c.Aliases...)
func (c *Command) Names() []string {
return append([]string{c.Name}, c.Aliases...)
}
// HasName returns true if Command.Name matches given name
func (c Command) HasName(name string) bool {
func (c *Command) HasName(name string) bool {
for _, n := range c.Names() {
if n == name {
return true
@ -170,7 +163,7 @@ func (c Command) HasName(name string) bool {
return false
}
func (c Command) startApp(ctx *Context) error {
func (c *Command) startApp(ctx *Context) error {
app := NewApp()
app.Metadata = ctx.App.Metadata
// set the name and usage
@ -200,12 +193,12 @@ func (c Command) startApp(ctx *Context) error {
app.Compiled = ctx.App.Compiled
app.Writer = ctx.App.Writer
app.categories = CommandCategories{}
app.Categories = newCommandCategories()
for _, command := range c.Subcommands {
app.categories = app.categories.AddCommand(command.Category, command)
app.Categories.AddCommand(command.Category, command)
}
sort.Sort(app.categories)
sort.Sort(app.Categories.(*commandCategories))
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
@ -230,6 +223,22 @@ func (c Command) startApp(ctx *Context) error {
}
// VisibleFlags returns a slice of the Flags with Hidden=false
func (c Command) VisibleFlags() []Flag {
func (c *Command) VisibleFlags() []Flag {
return visibleFlags(c.Flags)
}
func (c *Command) appendFlag(fl Flag) {
if !hasFlag(c.Flags, fl) {
c.Flags = append(c.Flags, fl)
}
}
func hasCommand(commands []*Command, command *Command) bool {
for _, existing := range commands {
if command == existing {
return true
}
}
return false
}

View File

@ -42,13 +42,13 @@ func TestCommandFlagParsing(t *testing.T) {
err := command.Run(context)
expect(t, err, c.expectedErr)
expect(t, []string(context.Args()), c.testArgs)
expect(t, context.Args().Slice(), c.testArgs)
}
}
func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
app := NewApp()
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "bar",
Before: func(c *Context) error {
@ -75,11 +75,11 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
app := NewApp()
app.Commands = []Command{
app.Commands = []*Command{
{
Name: "bar",
Flags: []Flag{
IntFlag{Name: "flag"},
&IntFlag{Name: "flag"},
},
OnUsageError: func(c *Context, err error, _ bool) error {
if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {

View File

@ -10,11 +10,11 @@ import (
// Context is a type that is passed through to
// each Handler action in a cli application. Context
// can be used to retrieve context-specific Args and
// can be used to retrieve context-specific args and
// parsed command-line options.
type Context struct {
App *App
Command Command
Command *Command
flagSet *flag.FlagSet
parentContext *Context
@ -147,54 +147,15 @@ func (c *Context) Lineage() []*Context {
return lineage
}
// Args contains apps console arguments
type Args []string
// Args returns the command line arguments associated with the context.
func (c *Context) Args() Args {
args := Args(c.flagSet.Args())
return args
ret := args(c.flagSet.Args())
return &ret
}
// NArg returns the number of the command line arguments.
func (c *Context) NArg() int {
return len(c.Args())
}
// Get returns the nth argument, or else a blank string
func (a Args) Get(n int) string {
if len(a) > n {
return a[n]
}
return ""
}
// First returns the first argument, or else a blank string
func (a Args) First() string {
return a.Get(0)
}
// Tail returns the rest of the arguments (not the first one)
// or else an empty string slice
func (a Args) Tail() []string {
if len(a) >= 2 {
return []string(a)[1:]
}
return []string{}
}
// Present checks if there are any arguments present
func (a Args) Present() bool {
return len(a) != 0
}
// Swap swaps arguments at the given indexes
func (a Args) Swap(from, to int) error {
if from >= len(a) || to >= len(a) {
return errors.New("index out of range")
}
a[from], a[to] = a[to], a[from]
return nil
return c.Args().Len()
}
func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
@ -310,7 +271,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited[f.Name] = true
})
for _, f := range flags {
parts := strings.Split(f.GetName(), ",")
parts := f.Names()
if len(parts) == 1 {
continue
}

View File

@ -15,7 +15,7 @@ func TestNewContext(t *testing.T) {
globalSet.Int("myflag", 42, "doc")
globalSet.Float64("myflag64", float64(47), "doc")
globalCtx := NewContext(nil, globalSet, nil)
command := Command{Name: "mycommand"}
command := &Command{Name: "mycommand"}
c := NewContext(nil, set, globalCtx)
c.Command = command
expect(t, c.Int("myflag"), 12)
@ -63,7 +63,7 @@ func TestContext_Args(t *testing.T) {
set.Bool("myflag", false, "doc")
c := NewContext(nil, set, nil)
set.Parse([]string{"--myflag", "bat", "baz"})
expect(t, len(c.Args()), 2)
expect(t, c.Args().Len(), 2)
expect(t, c.Bool("myflag"), true)
}

View File

@ -15,25 +15,39 @@ var OsExiter = os.Exit
var ErrWriter io.Writer = os.Stderr
// MultiError is an error that wraps multiple errors.
type MultiError struct {
Errors []error
type MultiError interface {
error
// Errors returns a copy of the errors slice
Errors() []error
}
// NewMultiError creates a new MultiError. Pass in one or more errors.
func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err}
func newMultiError(err ...error) MultiError {
ret := multiError(err)
return &ret
}
// Error implents the error interface.
func (m MultiError) Error() string {
errs := make([]string, len(m.Errors))
for i, err := range m.Errors {
type multiError []error
// Error implements the error interface.
func (m *multiError) Error() string {
errs := make([]string, len(*m))
for i, err := range *m {
errs[i] = err.Error()
}
return strings.Join(errs, "\n")
}
// Errors returns a copy of the errors slice
func (m *multiError) Errors() []error {
errs := make([]error, len(*m))
for _, err := range *m {
errs = append(errs, err)
}
return errs
}
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
// code
type ExitCoder interface {
@ -41,29 +55,25 @@ type ExitCoder interface {
ExitCode() int
}
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
type ExitError struct {
type exitError struct {
exitCode int
message string
}
// NewExitError makes a new *ExitError
func NewExitError(message string, exitCode int) *ExitError {
return &ExitError{
// Exit wraps a message and exit code into an ExitCoder suitable for handling by
// HandleExitCoder
func Exit(message string, exitCode int) ExitCoder {
return &exitError{
exitCode: exitCode,
message: message,
}
}
// Error returns the string message, fulfilling the interface required by
// `error`
func (ee *ExitError) Error() string {
func (ee *exitError) Error() string {
return ee.message
}
// ExitCode returns the exit code, fulfilling the interface required by
// `ExitCoder`
func (ee *ExitError) ExitCode() int {
func (ee *exitError) ExitCode() int {
return ee.exitCode
}
@ -85,7 +95,7 @@ func HandleExitCoder(err error) {
}
if multiErr, ok := err.(MultiError); ok {
for _, merr := range multiErr.Errors {
for _, merr := range multiErr.Errors() {
HandleExitCoder(merr)
}
}

View File

@ -34,7 +34,7 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) {
defer func() { OsExiter = os.Exit }()
HandleExitCoder(NewExitError("galactic perimeter breach", 9))
HandleExitCoder(Exit("galactic perimeter breach", 9))
expect(t, exitCode, 9)
expect(t, called, true)
@ -51,8 +51,8 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
defer func() { OsExiter = os.Exit }()
exitErr := NewExitError("galactic perimeter breach", 9)
err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr)
exitErr := Exit("galactic perimeter breach", 9)
err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr)
HandleExitCoder(err)
expect(t, exitCode, 9)

328
flag.go
View File

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
@ -14,25 +15,31 @@ import (
const defaultPlaceholder = "value"
var slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
var (
slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
commaWhitespace = regexp.MustCompile("[, ]+.*")
)
// BashCompletionFlag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{
var BashCompletionFlag = &BoolFlag{
Name: "generate-bash-completion",
Hidden: true,
}
// VersionFlag prints the version for the application
var VersionFlag = BoolFlag{
Name: "version, v",
var VersionFlag = &BoolFlag{
Name: "version",
Aliases: []string{"v"},
Usage: "print the version",
}
// HelpFlag prints the help for all commands and subcommands
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
// unless HideHelp is set to true)
var HelpFlag = BoolFlag{
Name: "help, h",
// HelpFlag prints the help for all commands and subcommands.
// Set to nil to disable the flag. The subcommand
// will still be added unless HideHelp is set to true.
var HelpFlag = &BoolFlag{
Name: "help",
Aliases: []string{"h"},
Usage: "show help",
}
@ -52,7 +59,7 @@ type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet)
GetName() string
Names() []string
}
func flagSet(name string, flags []Flag) *flag.FlagSet {
@ -64,14 +71,6 @@ func flagSet(name string, flags []Flag) *flag.FlagSet {
return set
}
func eachName(longName string, fn func(string)) {
parts := strings.Split(longName, ",")
for _, name := range parts {
name = strings.Trim(name, " ")
fn(name)
}
}
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
@ -81,26 +80,26 @@ type Generic interface {
// GenericFlag is the flag type for types implementing Generic
type GenericFlag struct {
Name string
Aliases []string
Value Generic
Usage string
EnvVar string
EnvVars []string
Hidden bool
}
// String returns the string representation of the generic flag to display the
// help text to the user (uses the String() method of the generic flag to show
// the value)
func (f GenericFlag) String() string {
func (f *GenericFlag) String() string {
return FlagStringer(f)
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) Apply(set *flag.FlagSet) {
func (f *GenericFlag) Apply(set *flag.FlagSet) {
val := f.Value
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" {
val.Set(envVal)
break
@ -108,14 +107,14 @@ func (f GenericFlag) Apply(set *flag.FlagSet) {
}
}
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
for _, name := range f.Names() {
set.Var(val, name, f.Usage)
}
}
// GetName returns the name of a flag.
func (f GenericFlag) GetName() string {
return f.Name
// Names returns the names of a flag.
func (f *GenericFlag) Names() []string {
return flagNames(f)
}
// StringSlice wraps a []string to satisfy flag.Value
@ -167,22 +166,22 @@ func (f *StringSlice) Value() []string {
// command-line
type StringSliceFlag struct {
Name string
Aliases []string
Value *StringSlice
Usage string
EnvVar string
EnvVars []string
Hidden bool
}
// String returns the usage
func (f StringSliceFlag) String() string {
func (f *StringSliceFlag) String() string {
return FlagStringer(f)
}
// Apply populates the flag given the flag set and environment
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" {
newVal := NewStringSlice()
for _, s := range strings.Split(envVal, ",") {
@ -199,14 +198,14 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
f.Value = NewStringSlice()
}
eachName(f.Name, func(name string) {
for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
})
}
}
// GetName returns the name of a flag.
func (f StringSliceFlag) GetName() string {
return f.Name
// Names returns the name of a flag.
func (f *StringSliceFlag) Names() []string {
return flagNames(f)
}
// IntSlice wraps an []int to satisfy flag.Value
@ -273,22 +272,22 @@ func (i *IntSlice) Value() []int {
// command-line
type IntSliceFlag struct {
Name string
Aliases []string
Value *IntSlice
Usage string
EnvVar string
EnvVars []string
Hidden bool
}
// String returns the usage
func (f IntSliceFlag) String() string {
func (f *IntSliceFlag) String() string {
return FlagStringer(f)
}
// Apply populates the flag given the flag set and environment
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" {
newVal := NewIntSlice()
for _, s := range strings.Split(envVal, ",") {
@ -308,36 +307,36 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
f.Value = NewIntSlice()
}
eachName(f.Name, func(name string) {
for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
})
}
}
// GetName returns the name of the flag.
func (f IntSliceFlag) GetName() string {
return f.Name
// Names returns the name of the flag.
func (f *IntSliceFlag) Names() []string {
return flagNames(f)
}
// BoolFlag is a switch that defaults to false
type BoolFlag struct {
Name string
Aliases []string
Value bool
Usage string
EnvVar string
EnvVars []string
Destination *bool
Hidden bool
}
// String returns a readable representation of this value (for usage defaults)
func (f BoolFlag) String() string {
func (f *BoolFlag) String() string {
return FlagStringer(f)
}
// Apply populates the flag given the flag set and environment
func (f BoolFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
func (f *BoolFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
@ -348,40 +347,40 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
}
}
eachName(f.Name, func(name string) {
for _, name := range f.Names() {
if f.Destination != nil {
set.BoolVar(f.Destination, name, f.Value, f.Usage)
return
continue
}
set.Bool(name, f.Value, f.Usage)
})
}
}
// GetName returns the name of the flag.
func (f BoolFlag) GetName() string {
return f.Name
// Names returns the name of the flag.
func (f *BoolFlag) Names() []string {
return flagNames(f)
}
// StringFlag represents a flag that takes as string value
type StringFlag struct {
Name string
Aliases []string
Value string
Usage string
EnvVar string
EnvVars []string
Destination *string
Hidden bool
}
// String returns the usage
func (f StringFlag) String() string {
func (f *StringFlag) String() string {
return FlagStringer(f)
}
// Apply populates the flag given the flag set and environment
func (f StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
func (f *StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" {
f.Value = envVal
break
@ -389,41 +388,41 @@ func (f StringFlag) Apply(set *flag.FlagSet) {
}
}
eachName(f.Name, func(name string) {
for _, name := range f.Names() {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
return
continue
}
set.String(name, f.Value, f.Usage)
})
}
}
// GetName returns the name of the flag.
func (f StringFlag) GetName() string {
return f.Name
// Names returns the name of the flag.
func (f *StringFlag) Names() []string {
return flagNames(f)
}
// IntFlag is a flag that takes an integer
// Errors if the value provided cannot be parsed
type IntFlag struct {
Name string
Aliases []string
Value int
Usage string
EnvVar string
EnvVars []string
Destination *int
Hidden bool
}
// String returns the usage
func (f IntFlag) String() string {
func (f *IntFlag) String() string {
return FlagStringer(f)
}
// Apply populates the flag given the flag set and environment
func (f IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
func (f *IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil {
@ -434,41 +433,41 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
}
}
eachName(f.Name, func(name string) {
for _, name := range f.Names() {
if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage)
return
continue
}
set.Int(name, f.Value, f.Usage)
})
}
}
// GetName returns the name of the flag.
func (f IntFlag) GetName() string {
return f.Name
// Names returns the name of the flag.
func (f *IntFlag) Names() []string {
return flagNames(f)
}
// DurationFlag is a flag that takes a duration specified in Go's duration
// format: https://golang.org/pkg/time/#ParseDuration
type DurationFlag struct {
Name string
Aliases []string
Value time.Duration
Usage string
EnvVar string
EnvVars []string
Destination *time.Duration
Hidden bool
}
// String returns a readable representation of this value (for usage defaults)
func (f DurationFlag) String() string {
func (f *DurationFlag) String() string {
return FlagStringer(f)
}
// Apply populates the flag given the flag set and environment
func (f DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
func (f *DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" {
envValDuration, err := time.ParseDuration(envVal)
if err == nil {
@ -479,41 +478,41 @@ func (f DurationFlag) Apply(set *flag.FlagSet) {
}
}
eachName(f.Name, func(name string) {
for _, name := range f.Names() {
if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage)
return
continue
}
set.Duration(name, f.Value, f.Usage)
})
}
}
// GetName returns the name of the flag.
func (f DurationFlag) GetName() string {
return f.Name
// Names returns the name of the flag.
func (f *DurationFlag) Names() []string {
return flagNames(f)
}
// Float64Flag is a flag that takes an float value
// Errors if the value provided cannot be parsed
type Float64Flag struct {
Name string
Aliases []string
Value float64
Usage string
EnvVar string
EnvVars []string
Destination *float64
Hidden bool
}
// String returns the usage
func (f Float64Flag) String() string {
func (f *Float64Flag) String() string {
return FlagStringer(f)
}
// Apply populates the flag given the flag set and environment
func (f Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
func (f *Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal := os.Getenv(envVar); envVal != "" {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil {
@ -523,24 +522,24 @@ func (f Float64Flag) Apply(set *flag.FlagSet) {
}
}
eachName(f.Name, func(name string) {
for _, name := range f.Names() {
if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage)
return
continue
}
set.Float64(name, f.Value, f.Usage)
})
}
}
// GetName returns the name of the flag.
func (f Float64Flag) GetName() string {
return f.Name
// Names returns the name of the flag.
func (f *Float64Flag) Names() []string {
return flagNames(f)
}
func visibleFlags(fl []Flag) []Flag {
visible := []Flag{}
for _, flag := range fl {
if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() {
if !flagValue(flag).FieldByName("Hidden").Bool() {
visible = append(visible, flag)
}
}
@ -574,25 +573,27 @@ func unquoteUsage(usage string) (string, string) {
return "", usage
}
func prefixedNames(fullName, placeholder string) string {
func prefixedNames(names []string, placeholder string) string {
var prefixed string
parts := strings.Split(fullName, ",")
for i, name := range parts {
name = strings.Trim(name, " ")
for i, name := range names {
if name == "" {
continue
}
prefixed += prefixFor(name) + name
if placeholder != "" {
prefixed += " " + placeholder
}
if i < len(parts)-1 {
if i < len(names)-1 {
prefixed += ", "
}
}
return prefixed
}
func withEnvHint(envVar, str string) string {
func withEnvHint(envVars []string, str string) string {
envText := ""
if envVar != "" {
if envVars != nil && len(envVars) > 0 {
prefix := "$"
suffix := ""
sep := ", $"
@ -601,21 +602,66 @@ func withEnvHint(envVar, str string) string {
suffix = "%"
sep = "%, %"
}
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix)
}
return str + envText
}
func stringifyFlag(f Flag) string {
func flagNames(f Flag) []string {
ret := []string{}
name := flagStringField(f, "Name")
aliases := flagStringSliceField(f, "Aliases")
for _, part := range append([]string{name}, aliases...) {
// v1 -> v2 migration warning zone:
// Strip off anything after the first found comma or space, which
// *hopefully* makes it a tiny bit more obvious that unexpected behavior is
// caused by using the v1 form of stringly typed "Name".
ret = append(ret, commaWhitespace.ReplaceAllString(part, ""))
}
return ret
}
func flagStringSliceField(f Flag, name string) []string {
fv := flagValue(f)
field := fv.FieldByName(name)
if field.IsValid() {
return field.Interface().([]string)
}
return []string{}
}
func flagStringField(f Flag, name string) string {
fv := flagValue(f)
field := fv.FieldByName(name)
if field.IsValid() {
return field.String()
}
return ""
}
func flagValue(f Flag) reflect.Value {
fv := reflect.ValueOf(f)
for fv.Kind() == reflect.Ptr {
fv = reflect.Indirect(fv)
}
return fv
}
func stringifyFlag(f Flag) string {
fv := flagValue(f)
switch f.(type) {
case IntSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyIntSliceFlag(f.(IntSliceFlag)))
case StringSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyStringSliceFlag(f.(StringSliceFlag)))
case *IntSliceFlag:
return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(*IntSliceFlag)))
case *StringSliceFlag:
return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(*StringSliceFlag)))
}
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
@ -643,11 +689,11 @@ func stringifyFlag(f Flag) string {
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
return withEnvHint(fv.FieldByName("EnvVar").String(),
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
return withEnvHint(flagStringSliceField(f, "EnvVars"),
fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault))
}
func stringifyIntSliceFlag(f IntSliceFlag) string {
func stringifyIntSliceFlag(f *IntSliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
@ -655,10 +701,10 @@ func stringifyIntSliceFlag(f IntSliceFlag) string {
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals)
}
func stringifyStringSliceFlag(f StringSliceFlag) string {
func stringifyStringSliceFlag(f *StringSliceFlag) string {
defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, s := range f.Value.Value() {
@ -668,10 +714,10 @@ func stringifyStringSliceFlag(f StringSliceFlag) string {
}
}
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals)
}
func stringifySliceFlag(usage, name string, defaultVals []string) string {
func stringifySliceFlag(usage string, names, defaultVals []string) string {
placeholder, usage := unquoteUsage(usage)
if placeholder == "" {
placeholder = defaultPlaceholder
@ -683,5 +729,15 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string {
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault)
}
func hasFlag(flags []Flag, fl Flag) bool {
for _, existing := range flags {
if fl == existing {
return true
}
}
return false
}

View File

@ -1,6 +1,7 @@
package cli
import (
"flag"
"fmt"
"os"
"reflect"
@ -20,7 +21,7 @@ var boolFlagTests = []struct {
func TestBoolFlagHelpOutput(t *testing.T) {
for _, test := range boolFlagTests {
flag := BoolFlag{Name: test.name}
flag := &BoolFlag{Name: test.name}
output := flag.String()
if output != test.expected {
@ -29,23 +30,35 @@ func TestBoolFlagHelpOutput(t *testing.T) {
}
}
func TestBoolFlagApply_SetsAllNames(t *testing.T) {
v := false
fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v}
set := flag.NewFlagSet("test", 0)
fl.Apply(set)
err := set.Parse([]string{"--wat", "-W", "--huh"})
expect(t, err, nil)
expect(t, v, true)
}
var stringFlagTests = []struct {
name string
aliases []string
usage string
value string
expected string
}{
{"foo", "", "", "--foo value\t"},
{"f", "", "", "-f value\t"},
{"f", "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"},
{"test", "", "Something", "--test value\t(default: \"Something\")"},
{"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"},
{"config,c", "Load configuration from `CONFIG`", "config.json", "--config CONFIG, -c CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"},
{"foo", nil, "", "", "--foo value\t"},
{"f", nil, "", "", "-f value\t"},
{"f", nil, "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"},
{"test", nil, "", "Something", "--test value\t(default: \"Something\")"},
{"config", []string{"c"}, "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"},
{"config", []string{"c"}, "Load configuration from `CONFIG`", "config.json", "--config CONFIG, -c CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"},
}
func TestStringFlagHelpOutput(t *testing.T) {
for _, test := range stringFlagTests {
flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value}
flag := &StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value}
output := flag.String()
if output != test.expected {
@ -58,7 +71,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_FOO", "derp")
for _, test := range stringFlagTests {
flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
flag := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}}
output := flag.String()
expectedSuffix := " [$APP_FOO]"
@ -71,21 +84,33 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestStringFlagApply_SetsAllNames(t *testing.T) {
v := "mmm"
fl := StringFlag{Name: "hay", Aliases: []string{"H", "hayyy"}, Destination: &v}
set := flag.NewFlagSet("test", 0)
fl.Apply(set)
err := set.Parse([]string{"--hay", "u", "-H", "yuu", "--hayyy", "YUUUU"})
expect(t, err, nil)
expect(t, v, "YUUUU")
}
var stringSliceFlagTests = []struct {
name string
aliases []string
value *StringSlice
expected string
}{
{"foo", NewStringSlice(""), "--foo value\t"},
{"f", NewStringSlice(""), "-f value\t"},
{"f", NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"},
{"test", NewStringSlice("Something"), "--test value\t(default: \"Something\")"},
{"d, dee", NewStringSlice("Inka", "Dinka", "dooo"), "-d value, --dee value\t(default: \"Inka\", \"Dinka\", \"dooo\")"},
{"foo", nil, NewStringSlice(""), "--foo value\t"},
{"f", nil, NewStringSlice(""), "-f value\t"},
{"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"},
{"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"},
{"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"},
}
func TestStringSliceFlagHelpOutput(t *testing.T) {
for _, test := range stringSliceFlagTests {
flag := StringSliceFlag{Name: test.name, Value: test.value}
flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value}
output := flag.String()
if output != test.expected {
@ -98,7 +123,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_QWWX", "11,4")
for _, test := range stringSliceFlagTests {
flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}}
output := flag.String()
expectedSuffix := " [$APP_QWWX]"
@ -111,6 +136,15 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestStringSliceFlagApply_SetsAllNames(t *testing.T) {
fl := StringSliceFlag{Name: "goat", Aliases: []string{"G", "gooots"}}
set := flag.NewFlagSet("test", 0)
fl.Apply(set)
err := set.Parse([]string{"--goat", "aaa", "-G", "bbb", "--gooots", "eeeee"})
expect(t, err, nil)
}
var intFlagTests = []struct {
name string
expected string
@ -121,7 +155,7 @@ var intFlagTests = []struct {
func TestIntFlagHelpOutput(t *testing.T) {
for _, test := range intFlagTests {
flag := IntFlag{Name: test.name, Value: 9}
flag := &IntFlag{Name: test.name, Value: 9}
output := flag.String()
if output != test.expected {
@ -134,7 +168,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAR", "2")
for _, test := range intFlagTests {
flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"}
flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
output := flag.String()
expectedSuffix := " [$APP_BAR]"
@ -147,6 +181,17 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestIntFlagApply_SetsAllNames(t *testing.T) {
v := 3
fl := IntFlag{Name: "banana", Aliases: []string{"B", "banannanana"}, Destination: &v}
set := flag.NewFlagSet("test", 0)
fl.Apply(set)
err := set.Parse([]string{"--banana", "1", "-B", "2", "--banannanana", "5"})
expect(t, err, nil)
expect(t, v, 5)
}
var durationFlagTests = []struct {
name string
expected string
@ -157,7 +202,7 @@ var durationFlagTests = []struct {
func TestDurationFlagHelpOutput(t *testing.T) {
for _, test := range durationFlagTests {
flag := DurationFlag{Name: test.name, Value: 1 * time.Second}
flag := &DurationFlag{Name: test.name, Value: 1 * time.Second}
output := flag.String()
if output != test.expected {
@ -170,7 +215,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAR", "2h3m6s")
for _, test := range durationFlagTests {
flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
output := flag.String()
expectedSuffix := " [$APP_BAR]"
@ -183,19 +228,31 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestDurationFlagApply_SetsAllNames(t *testing.T) {
v := time.Second * 20
fl := DurationFlag{Name: "howmuch", Aliases: []string{"H", "whyyy"}, Destination: &v}
set := flag.NewFlagSet("test", 0)
fl.Apply(set)
err := set.Parse([]string{"--howmuch", "30s", "-H", "5m", "--whyyy", "30h"})
expect(t, err, nil)
expect(t, v, time.Hour*30)
}
var intSliceFlagTests = []struct {
name string
aliases []string
value *IntSlice
expected string
}{
{"heads", NewIntSlice(), "--heads value\t"},
{"H", NewIntSlice(), "-H value\t"},
{"H, heads", NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"},
{"heads", nil, NewIntSlice(), "--heads value\t"},
{"H", nil, NewIntSlice(), "-H value\t"},
{"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"},
}
func TestIntSliceFlagHelpOutput(t *testing.T) {
for _, test := range intSliceFlagTests {
flag := IntSliceFlag{Name: test.name, Value: test.value}
flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value}
output := flag.String()
if output != test.expected {
@ -208,7 +265,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_SMURF", "42,3")
for _, test := range intSliceFlagTests {
flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}}
output := flag.String()
expectedSuffix := " [$APP_SMURF]"
@ -221,6 +278,15 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestIntSliceFlagApply_SetsAllNames(t *testing.T) {
fl := IntSliceFlag{Name: "bits", Aliases: []string{"B", "bips"}}
set := flag.NewFlagSet("test", 0)
fl.Apply(set)
err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"})
expect(t, err, nil)
}
var float64FlagTests = []struct {
name string
expected string
@ -231,7 +297,7 @@ var float64FlagTests = []struct {
func TestFloat64FlagHelpOutput(t *testing.T) {
for _, test := range float64FlagTests {
flag := Float64Flag{Name: test.name, Value: float64(0.1)}
flag := &Float64Flag{Name: test.name, Value: float64(0.1)}
output := flag.String()
if output != test.expected {
@ -244,7 +310,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAZ", "99.4")
for _, test := range float64FlagTests {
flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}}
output := flag.String()
expectedSuffix := " [$APP_BAZ]"
@ -257,6 +323,17 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestFloat64FlagApply_SetsAllNames(t *testing.T) {
v := float64(99.1)
fl := Float64Flag{Name: "noodles", Aliases: []string{"N", "nurbles"}, Destination: &v}
set := flag.NewFlagSet("test", 0)
fl.Apply(set)
err := set.Parse([]string{"--noodles", "1.3", "-N", "11", "--nurbles", "43.33333"})
expect(t, err, nil)
expect(t, v, float64(43.33333))
}
var genericFlagTests = []struct {
name string
value Generic
@ -268,7 +345,7 @@ var genericFlagTests = []struct {
func TestGenericFlagHelpOutput(t *testing.T) {
for _, test := range genericFlagTests {
flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
flag := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
output := flag.String()
if output != test.expected {
@ -281,7 +358,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_ZAP", "3")
for _, test := range genericFlagTests {
flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}}
output := flag.String()
expectedSuffix := " [$APP_ZAP]"
@ -294,10 +371,19 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestGenericFlagApply_SetsAllNames(t *testing.T) {
fl := GenericFlag{Name: "orbs", Aliases: []string{"O", "obrs"}, Value: &Parser{}}
set := flag.NewFlagSet("test", 0)
fl.Apply(set)
err := set.Parse([]string{"--orbs", "eleventy,3", "-O", "4,bloop", "--obrs", "19,s"})
expect(t, err, nil)
}
func TestParseMultiString(t *testing.T) {
(&App{
Flags: []Flag{
StringFlag{Name: "serve, s"},
&StringFlag{Name: "serve", Aliases: []string{"s"}},
},
Action: func(ctx *Context) error {
if ctx.String("serve") != "10" {
@ -315,7 +401,7 @@ func TestParseDestinationString(t *testing.T) {
var dest string
a := App{
Flags: []Flag{
StringFlag{
&StringFlag{
Name: "dest",
Destination: &dest,
},
@ -335,7 +421,7 @@ func TestParseMultiStringFromEnv(t *testing.T) {
os.Setenv("APP_COUNT", "20")
(&App{
Flags: []Flag{
StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
&StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}},
},
Action: func(ctx *Context) error {
if ctx.String("count") != "20" {
@ -354,7 +440,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) {
os.Setenv("APP_COUNT", "20")
(&App{
Flags: []Flag{
StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"},
&StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}},
},
Action: func(ctx *Context) error {
if ctx.String("count") != "20" {
@ -371,7 +457,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) {
func TestParseMultiStringSlice(t *testing.T) {
(&App{
Flags: []Flag{
StringSliceFlag{Name: "serve, s", Value: NewStringSlice()},
&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()},
},
Action: func(ctx *Context) error {
expected := []string{"10", "20"}
@ -389,7 +475,7 @@ func TestParseMultiStringSlice(t *testing.T) {
func TestParseMultiStringSliceWithDefaults(t *testing.T) {
(&App{
Flags: []Flag{
StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")},
&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")},
},
Action: func(ctx *Context) error {
expected := []string{"10", "20"}
@ -407,7 +493,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) {
func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) {
(&App{
Flags: []Flag{
StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")},
&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) {
@ -427,7 +513,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) {
(&App{
Flags: []Flag{
StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "APP_INTERVALS"},
&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"APP_INTERVALS"}},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@ -447,7 +533,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) {
(&App{
Flags: []Flag{
StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "APP_INTERVALS"},
&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"APP_INTERVALS"}},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@ -467,7 +553,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
(&App{
Flags: []Flag{
StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@ -487,7 +573,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) {
(&App{
Flags: []Flag{
StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@ -504,7 +590,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) {
func TestParseMultiInt(t *testing.T) {
a := App{
Flags: []Flag{
IntFlag{Name: "serve, s"},
&IntFlag{Name: "serve", Aliases: []string{"s"}},
},
Action: func(ctx *Context) error {
if ctx.Int("serve") != 10 {
@ -523,7 +609,7 @@ func TestParseDestinationInt(t *testing.T) {
var dest int
a := App{
Flags: []Flag{
IntFlag{
&IntFlag{
Name: "dest",
Destination: &dest,
},
@ -543,7 +629,7 @@ func TestParseMultiIntFromEnv(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := App{
Flags: []Flag{
IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
&IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}},
},
Action: func(ctx *Context) error {
if ctx.Int("timeout") != 10 {
@ -563,7 +649,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := App{
Flags: []Flag{
IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
&IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}},
},
Action: func(ctx *Context) error {
if ctx.Int("timeout") != 10 {
@ -581,7 +667,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) {
func TestParseMultiIntSlice(t *testing.T) {
(&App{
Flags: []Flag{
IntSliceFlag{Name: "serve, s", Value: NewIntSlice()},
&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
@ -598,7 +684,7 @@ func TestParseMultiIntSlice(t *testing.T) {
func TestParseMultiIntSliceWithDefaults(t *testing.T) {
(&App{
Flags: []Flag{
IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)},
&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
@ -615,7 +701,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) {
func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) {
(&App{
Flags: []Flag{
IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)},
&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) {
@ -635,7 +721,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) {
(&App{
Flags: []Flag{
IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "APP_INTERVALS"},
&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"APP_INTERVALS"}},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@ -655,7 +741,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) {
(&App{
Flags: []Flag{
IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(1, 2, 5), EnvVar: "APP_INTERVALS"},
&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(1, 2, 5), EnvVars: []string{"APP_INTERVALS"}},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@ -675,7 +761,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
(&App{
Flags: []Flag{
IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@ -692,7 +778,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
func TestParseMultiFloat64(t *testing.T) {
a := App{
Flags: []Flag{
Float64Flag{Name: "serve, s"},
&Float64Flag{Name: "serve", Aliases: []string{"s"}},
},
Action: func(ctx *Context) error {
if ctx.Float64("serve") != 10.2 {
@ -711,7 +797,7 @@ func TestParseDestinationFloat64(t *testing.T) {
var dest float64
a := App{
Flags: []Flag{
Float64Flag{
&Float64Flag{
Name: "dest",
Destination: &dest,
},
@ -731,7 +817,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := App{
Flags: []Flag{
Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
&Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}},
},
Action: func(ctx *Context) error {
if ctx.Float64("timeout") != 15.5 {
@ -751,7 +837,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := App{
Flags: []Flag{
Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
&Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}},
},
Action: func(ctx *Context) error {
if ctx.Float64("timeout") != 15.5 {
@ -769,7 +855,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
func TestParseMultiBool(t *testing.T) {
a := App{
Flags: []Flag{
BoolFlag{Name: "serve, s"},
&BoolFlag{Name: "serve", Aliases: []string{"s"}},
},
Action: func(ctx *Context) error {
if ctx.Bool("serve") != true {
@ -788,7 +874,7 @@ func TestParseDestinationBool(t *testing.T) {
var dest bool
a := App{
Flags: []Flag{
BoolFlag{
&BoolFlag{
Name: "dest",
Destination: &dest,
},
@ -808,7 +894,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) {
os.Setenv("APP_DEBUG", "1")
a := App{
Flags: []Flag{
BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
&BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}},
},
Action: func(ctx *Context) error {
if ctx.Bool("debug") != true {
@ -828,7 +914,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) {
os.Setenv("APP_DEBUG", "1")
a := App{
Flags: []Flag{
BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
&BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}},
},
Action: func(ctx *Context) error {
if ctx.Bool("debug") != true {
@ -846,7 +932,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) {
func TestParseMultiBoolTrue(t *testing.T) {
a := App{
Flags: []Flag{
BoolFlag{Name: "implode, i", Value: true},
&BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true},
},
Action: func(ctx *Context) error {
if ctx.Bool("implode") {
@ -866,7 +952,7 @@ func TestParseDestinationBoolTrue(t *testing.T) {
a := App{
Flags: []Flag{
BoolFlag{
&BoolFlag{
Name: "dest",
Value: true,
Destination: &dest,
@ -887,10 +973,11 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) {
os.Setenv("APP_DEBUG", "0")
a := App{
Flags: []Flag{
BoolFlag{
Name: "debug, d",
&BoolFlag{
Name: "debug",
Aliases: []string{"d"},
Value: true,
EnvVar: "APP_DEBUG",
EnvVars: []string{"APP_DEBUG"},
},
},
Action: func(ctx *Context) error {
@ -911,10 +998,11 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) {
os.Setenv("APP_DEBUG", "0")
a := App{
Flags: []Flag{
BoolFlag{
Name: "debug, d",
&BoolFlag{
Name: "debug",
Aliases: []string{"d"},
Value: true,
EnvVar: "COMPAT_DEBUG,APP_DEBUG",
EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"},
},
},
Action: func(ctx *Context) error {
@ -951,7 +1039,7 @@ func (p *Parser) String() string {
func TestParseGeneric(t *testing.T) {
a := App{
Flags: []Flag{
GenericFlag{Name: "serve, s", Value: &Parser{}},
&GenericFlag{Name: "serve", Aliases: []string{"s"}, Value: &Parser{}},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) {
@ -971,7 +1059,12 @@ func TestParseGenericFromEnv(t *testing.T) {
os.Setenv("APP_SERVE", "20,30")
a := App{
Flags: []Flag{
GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
&GenericFlag{
Name: "serve",
Aliases: []string{"s"},
Value: &Parser{},
EnvVars: []string{"APP_SERVE"},
},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
@ -991,7 +1084,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) {
os.Setenv("APP_FOO", "99,2000")
a := App{
Flags: []Flag{
GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"},
&GenericFlag{
Name: "foos",
Value: &Parser{},
EnvVars: []string{"COMPAT_FOO", "APP_FOO"},
},
},
Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {

41
help.go
View File

@ -74,7 +74,7 @@ OPTIONS:
{{end}}{{end}}
`
var helpCommand = Command{
var helpCommand = &Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
@ -90,7 +90,7 @@ var helpCommand = Command{
},
}
var helpSubcommand = Command{
var helpSubcommand = &Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
@ -149,7 +149,7 @@ func ShowCommandHelp(ctx *Context, command string) error {
}
if ctx.App.CommandNotFound == nil {
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
return Exit(fmt.Sprintf("No help topic for '%v'", command), 3)
}
ctx.App.CommandNotFound(ctx, command)
@ -158,7 +158,15 @@ func ShowCommandHelp(ctx *Context, command string) error {
// ShowSubcommandHelp prints help for the given subcommand
func ShowSubcommandHelp(c *Context) error {
if c == nil {
return nil
}
if c.Command != nil {
return ShowCommandHelp(c, c.Command.Name)
}
return ShowCommandHelp(c, "")
}
// ShowVersion prints the version number of the App
@ -193,26 +201,39 @@ func printHelp(out io.Writer, templ string, data interface{}) {
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
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 the writer is closed, t.Execute will fail, and there's nothing
// we can do to recover.
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
if errDebug {
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
}
return
}
w.Flush()
}
func checkVersion(c *Context) bool {
found := false
if VersionFlag.Name != "" {
eachName(VersionFlag.Name, func(name string) {
for _, name := range VersionFlag.Names() {
if c.Bool(name) {
found = true
}
})
}
}
return found
}
@ -220,11 +241,11 @@ func checkVersion(c *Context) bool {
func checkHelp(c *Context) bool {
found := false
if HelpFlag.Name != "" {
eachName(HelpFlag.Name, func(name string) {
for _, name := range HelpFlag.Names() {
if c.Bool(name) {
found = true
}
})
}
}
return found
}

View File

@ -59,14 +59,15 @@ func Test_Help_Custom_Flags(t *testing.T) {
HelpFlag = oldFlag
}()
HelpFlag = BoolFlag{
Name: "help, x",
HelpFlag = &BoolFlag{
Name: "help",
Aliases: []string{"x"},
Usage: "show help",
}
app := App{
Flags: []Flag{
BoolFlag{Name: "foo, h"},
&BoolFlag{Name: "foo", Aliases: []string{"h"}},
},
Action: func(ctx *Context) error {
if ctx.Bool("h") != true {
@ -89,14 +90,15 @@ func Test_Version_Custom_Flags(t *testing.T) {
VersionFlag = oldFlag
}()
VersionFlag = BoolFlag{
Name: "version, V",
VersionFlag = &BoolFlag{
Name: "version",
Aliases: []string{"V"},
Usage: "show version",
}
app := App{
Flags: []Flag{
BoolFlag{Name: "foo, v"},
&BoolFlag{Name: "foo", Aliases: []string{"v"}},
},
Action: func(ctx *Context) error {
if ctx.Bool("v") != true {
@ -127,9 +129,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) {
t.Fatalf("expected error from helpCommand.Action(), but got nil")
}
exitErr, ok := err.(*ExitError)
exitErr, ok := err.(*exitError)
if !ok {
t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error())
t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error())
}
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
@ -155,9 +157,9 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
t.Fatalf("expected error from helpCommand.Action(), but got nil")
}
exitErr, ok := err.(*ExitError)
exitErr, ok := err.(*exitError)
if !ok {
t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error())
t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error())
}
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
@ -171,7 +173,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
func TestShowAppHelp_CommandAliases(t *testing.T) {
app := &App{
Commands: []Command{
Commands: []*Command{
{
Name: "frobbly",
Aliases: []string{"fr", "frob"},
@ -193,7 +195,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) {
func TestShowCommandHelp_CommandAliases(t *testing.T) {
app := &App{
Commands: []Command{
Commands: []*Command{
{
Name: "frobbly",
Aliases: []string{"fr", "frob", "bork"},
@ -219,7 +221,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) {
func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
app := &App{
Commands: []Command{
Commands: []*Command{
{
Name: "frobbly",
Aliases: []string{"fr", "frob", "bork"},
@ -241,7 +243,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
func TestShowAppHelp_HiddenCommand(t *testing.T) {
app := &App{
Commands: []Command{
Commands: []*Command{
{
Name: "frobbly",
Action: func(ctx *Context) error {

View File

@ -12,6 +12,10 @@ var (
wd, _ = os.Getwd()
)
func init() {
os.Setenv("CLI_TEMPLATE_REPANIC", "1")
}
func expect(t *testing.T, a interface{}, b interface{}) {
_, fn, line, _ := runtime.Caller(1)
fn = strings.Replace(fn, wd+"/", "", -1)

View File

@ -10,7 +10,7 @@ from subprocess import check_call, check_output
PACKAGE_NAME = os.environ.get(
'CLI_PACKAGE_NAME', 'github.com/codegangsta/cli'
'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
)
@ -49,9 +49,9 @@ def _test():
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
])
combined = _combine_coverprofiles(coverprofiles)
_run('go tool cover -func={}'.format(combined.name).split())
combined.close()
combined_name = _combine_coverprofiles(coverprofiles)
_run('go tool cover -func={}'.format(combined_name).split())
os.remove(combined_name)
def _gfmxr():
@ -78,7 +78,9 @@ def _is_go_runnable(line):
def _combine_coverprofiles(coverprofiles):
combined = tempfile.NamedTemporaryFile(suffix='.coverprofile')
combined = tempfile.NamedTemporaryFile(
suffix='.coverprofile', delete=False
)
combined.write('mode: set\n')
for coverprofile in coverprofiles:
@ -88,7 +90,9 @@ def _combine_coverprofiles(coverprofiles):
combined.write(line)
combined.flush()
return combined
name = combined.name
combined.close()
return name
if __name__ == '__main__':