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

View File

@ -34,6 +34,8 @@
## [Unreleased] - (1.x series) ## [Unreleased] - (1.x series)
### Added ### Added
- `./runtests` test runner with coverage tracking by default - `./runtests` test runner with coverage tracking by default
- testing on OS X
- testing on Windows
### Fixed ### Fixed
- Printing of command aliases in help text - 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 makes it easier to script around apps built using `cli` since they can trust
that a 0 exit code indicated a successful execution. that a 0 exit code indicated a successful execution.
- cleanups based on [Go Report Card - 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 ## [1.16.0] - 2016-05-02
### Added ### Added
@ -317,28 +319,28 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
### Added ### Added
- Initial implementation. - Initial implementation.
[Unreleased]: https://github.com/codegangsta/cli/compare/v1.17.0...HEAD [Unreleased]: https://github.com/urfave/cli/compare/v1.17.0...HEAD
[1.17.0]: https://github.com/codegangsta/cli/compare/v1.16.0...v1.17.0 [1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0 [1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0 [1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 [1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 [1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0 [1.12.0]: https://github.com/urfave/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.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
[1.11.0]: https://github.com/codegangsta/cli/compare/v1.10.2...v1.11.0 [1.11.0]: https://github.com/urfave/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.2]: https://github.com/urfave/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.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
[1.10.0]: https://github.com/codegangsta/cli/compare/v1.9.0...v1.10.0 [1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
[1.9.0]: https://github.com/codegangsta/cli/compare/v1.8.0...v1.9.0 [1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/codegangsta/cli/compare/v1.7.1...v1.8.0 [1.8.0]: https://github.com/urfave/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.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/codegangsta/cli/compare/v1.6.0...v1.7.0 [1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/codegangsta/cli/compare/v1.5.0...v1.6.0 [1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/codegangsta/cli/compare/v1.4.1...v1.5.0 [1.5.0]: https://github.com/urfave/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.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/codegangsta/cli/compare/v1.3.1...v1.4.0 [1.4.0]: https://github.com/urfave/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.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/codegangsta/cli/compare/v1.2.0...v1.3.0 [1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/codegangsta/cli/compare/v1.1.0...v1.2.0 [1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/codegangsta/cli/compare/v1.0.0...v1.1.0 [1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/codegangsta/cli/compare/v0.1.0...v1.0.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) [![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli)
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/meatballhat/cli)
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) [![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli)
[![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
[![top level coverage](https://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) / [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
[![altsrc coverage](https://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc) [![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 # 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. 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 ## Overview
@ -25,7 +30,7 @@ instructions](http://golang.org/doc/install.html).
To install cli, simply run: 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: 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 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 ### Using the `v2` branch
There is currently a long-lived branch named `v2` that is intended to land as 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`: `gopkg.in`:
``` ```
$ go get gopkg.in/codegangsta/cli.v2 $ go get gopkg.in/urfave/cli.v2
``` ```
``` go ``` go
... ...
import ( 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.: 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 ``` go
... ...
import ( 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 ( import (
"os" "os"
"github.com/codegangsta/cli" "github.com/urfave/cli"
) )
func main() { func main() {
@ -102,7 +113,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/codegangsta/cli" "github.com/urfave/cli"
) )
func main() { func main() {
@ -136,7 +147,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/codegangsta/cli" "github.com/urfave/cli"
) )
func main() { func main() {
@ -205,7 +216,7 @@ Setting and querying flags is simple.
``` go ``` go
... ...
app.Flags = []cli.Flag { app.Flags = []cli.Flag {
cli.StringFlag{ &cli.StringFlag{
Name: "lang", Name: "lang",
Value: "english", Value: "english",
Usage: "language for the greeting", 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 var language string
app.Flags = []cli.Flag { app.Flags = []cli.Flag {
cli.StringFlag{ &cli.StringFlag{
Name: "lang", Name: "lang",
Value: "english", Value: "english",
Usage: "language for the greeting", 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 #### Placeholder Values
@ -264,8 +275,9 @@ indicated with back quotes.
For example this: For example this:
```go ```go
cli.StringFlag{ &cli.StringFlag{
Name: "config, c", Name: "config",
Aliases: []string{"c"},
Usage: "Load configuration from `FILE`", Usage: "Load configuration from `FILE`",
} }
``` ```
@ -284,8 +296,9 @@ You can set alternate (or short) names for flags by providing a comma-delimited
``` go ``` go
app.Flags = []cli.Flag { app.Flags = []cli.Flag {
cli.StringFlag{ &cli.StringFlag{
Name: "lang, l", Name: "lang",
Aliases: []string{"l"},
Value: "english", Value: "english",
Usage: "language for the greeting", 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 #### 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 ``` go
app.Flags = []cli.Flag { app.Flags = []cli.Flag {
cli.StringFlag{ &cli.StringFlag{
Name: "lang, l", Name: "lang",
Aliases: []string{"l"},
Value: "english", Value: "english",
Usage: "language for the greeting", 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 ``` go
app.Flags = []cli.Flag { app.Flags = []cli.Flag {
cli.StringFlag{ &cli.StringFlag{
Name: "lang, l", Name: "lang",
Aliases: []string{"l"},
Value: "english", Value: "english",
Usage: "language for the greeting", 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: 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 ``` 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. 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 return nil
}, },
Flags: []cli.Flag{ Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}), NewIntFlag(&cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"}}, &cli.StringFlag{Name: "load"}},
} }
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
err := command.Run(c) err := command.Run(c)
@ -371,7 +386,7 @@ Subcommands can be defined for a more git-like command line app.
```go ```go
... ...
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "add", Name: "add",
Aliases: []string{"a"}, Aliases: []string{"a"},
@ -394,7 +409,7 @@ app.Commands = []cli.Command{
Name: "template", Name: "template",
Aliases: []string{"r"}, Aliases: []string{"r"},
Usage: "options for task templates", Usage: "options for task templates",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "add", Name: "add",
Usage: "add a new template", Usage: "add a new template",
@ -427,7 +442,7 @@ E.g.
```go ```go
... ...
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "noop", Name: "noop",
}, },
@ -469,13 +484,13 @@ package main
import ( import (
"os" "os"
"github.com/codegangsta/cli" "github.com/urfave/cli"
) )
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "ginger-crouton", Name: "ginger-crouton",
Value: true, Value: true,
Usage: "is it in the soup?", Usage: "is it in the soup?",
@ -483,7 +498,7 @@ func main() {
} }
app.Action = func(ctx *cli.Context) error { app.Action = func(ctx *cli.Context) error {
if !ctx.Bool("ginger-crouton") { 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 return nil
} }
@ -504,7 +519,7 @@ the App or its subcommands.
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp() app := cli.NewApp()
app.EnableBashCompletion = true app.EnableBashCompletion = true
app.Commands = []cli.Command{ app.Commands = []*cli.Command{
{ {
Name: "complete", Name: "complete",
Aliases: []string{"c"}, Aliases: []string{"c"},
@ -569,7 +584,7 @@ import (
"io" "io"
"os" "os"
"github.com/codegangsta/cli" "github.com/urfave/cli"
) )
func main() { func main() {
@ -618,8 +633,16 @@ VERSION:
## Contribution Guidelines ## 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" "fmt"
"os" "os"
"strconv" "strconv"
"strings"
"github.com/codegangsta/cli" "github.com/urfave/cli"
) )
// FlagInputSourceExtension is an extension interface of cli.Flag that // 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 // for other values to be specified
type GenericFlag struct { type GenericFlag struct {
cli.GenericFlag *cli.GenericFlag
set *flag.FlagSet set *flag.FlagSet
} }
// NewGenericFlag creates a new GenericFlag // NewGenericFlag creates a new GenericFlag
func NewGenericFlag(flag cli.GenericFlag) *GenericFlag { func NewGenericFlag(flag *cli.GenericFlag) *GenericFlag {
return &GenericFlag{GenericFlag: flag, set: nil} return &GenericFlag{GenericFlag: flag, set: nil}
} }
// ApplyInputSourceValue applies a generic value to the flagSet if required // ApplyInputSourceValue applies a generic value to the flagSet if required
func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil { 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) value, err := isc.Generic(f.GenericFlag.Name)
if err != nil { if err != nil {
return err return err
} }
if value != nil { if value != nil {
eachName(f.Name, func(name string) { for _, name := range f.Names() {
f.set.Set(f.Name, value.String()) f.set.Set(name, value.String())
}) }
} }
} }
} }
@ -101,34 +100,34 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) {
f.GenericFlag.Apply(set) 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 // for other values to be specified
type StringSliceFlag struct { type StringSliceFlag struct {
cli.StringSliceFlag *cli.StringSliceFlag
set *flag.FlagSet set *flag.FlagSet
} }
// NewStringSliceFlag creates a new StringSliceFlag // NewStringSliceFlag creates a new StringSliceFlag
func NewStringSliceFlag(flag cli.StringSliceFlag) *StringSliceFlag { func NewStringSliceFlag(flag *cli.StringSliceFlag) *StringSliceFlag {
return &StringSliceFlag{StringSliceFlag: flag, set: nil} return &StringSliceFlag{StringSliceFlag: flag, set: nil}
} }
// ApplyInputSourceValue applies a StringSlice value to the flagSet if required // ApplyInputSourceValue applies a StringSlice value to the flagSet if required
func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil { 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) value, err := isc.StringSlice(f.StringSliceFlag.Name)
if err != nil { if err != nil {
return err return err
} }
if value != nil { if value != nil {
var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...)) var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...))
eachName(f.Name, func(name string) { for _, name := range f.Names() {
underlyingFlag := f.set.Lookup(f.Name) underlyingFlag := f.set.Lookup(name)
if underlyingFlag != nil { if underlyingFlag != nil {
underlyingFlag.Value = &sliceValue underlyingFlag.Value = &sliceValue
} }
}) }
} }
} }
} }
@ -142,34 +141,34 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
f.StringSliceFlag.Apply(set) 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 // for other values to be specified
type IntSliceFlag struct { type IntSliceFlag struct {
cli.IntSliceFlag *cli.IntSliceFlag
set *flag.FlagSet set *flag.FlagSet
} }
// NewIntSliceFlag creates a new IntSliceFlag // NewIntSliceFlag creates a new IntSliceFlag
func NewIntSliceFlag(flag cli.IntSliceFlag) *IntSliceFlag { func NewIntSliceFlag(flag *cli.IntSliceFlag) *IntSliceFlag {
return &IntSliceFlag{IntSliceFlag: flag, set: nil} return &IntSliceFlag{IntSliceFlag: flag, set: nil}
} }
// ApplyInputSourceValue applies a IntSlice value if required // ApplyInputSourceValue applies a IntSlice value if required
func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil { 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) value, err := isc.IntSlice(f.IntSliceFlag.Name)
if err != nil { if err != nil {
return err return err
} }
if value != nil { if value != nil {
var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...)) var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...))
eachName(f.Name, func(name string) { for _, name := range f.Names() {
underlyingFlag := f.set.Lookup(f.Name) underlyingFlag := f.set.Lookup(name)
if underlyingFlag != nil { if underlyingFlag != nil {
underlyingFlag.Value = &sliceValue underlyingFlag.Value = &sliceValue
} }
}) }
} }
} }
} }
@ -183,30 +182,30 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
f.IntSliceFlag.Apply(set) 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 // for other values to be specified
type BoolFlag struct { type BoolFlag struct {
cli.BoolFlag *cli.BoolFlag
set *flag.FlagSet set *flag.FlagSet
} }
// NewBoolFlag creates a new BoolFlag // NewBoolFlag creates a new BoolFlag
func NewBoolFlag(flag cli.BoolFlag) *BoolFlag { func NewBoolFlag(flag *cli.BoolFlag) *BoolFlag {
return &BoolFlag{BoolFlag: flag, set: nil} return &BoolFlag{BoolFlag: flag, set: nil}
} }
// ApplyInputSourceValue applies a Bool value to the flagSet if required // ApplyInputSourceValue applies a Bool value to the flagSet if required
func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil { 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) value, err := isc.Bool(f.BoolFlag.Name)
if err != nil { if err != nil {
return err return err
} }
if value { if value {
eachName(f.Name, func(name string) { for _, name := range f.Names() {
f.set.Set(f.Name, strconv.FormatBool(value)) f.set.Set(name, strconv.FormatBool(value))
}) }
} }
} }
} }
@ -220,30 +219,30 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) {
f.BoolFlag.Apply(set) 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 // for other values to be specified
type StringFlag struct { type StringFlag struct {
cli.StringFlag *cli.StringFlag
set *flag.FlagSet set *flag.FlagSet
} }
// NewStringFlag creates a new StringFlag // NewStringFlag creates a new StringFlag
func NewStringFlag(flag cli.StringFlag) *StringFlag { func NewStringFlag(flag *cli.StringFlag) *StringFlag {
return &StringFlag{StringFlag: flag, set: nil} return &StringFlag{StringFlag: flag, set: nil}
} }
// ApplyInputSourceValue applies a String value to the flagSet if required // ApplyInputSourceValue applies a String value to the flagSet if required
func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil { 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) value, err := isc.String(f.StringFlag.Name)
if err != nil { if err != nil {
return err return err
} }
if value != "" { if value != "" {
eachName(f.Name, func(name string) { for _, name := range f.Names() {
f.set.Set(f.Name, value) f.set.Set(name, value)
}) }
} }
} }
} }
@ -258,30 +257,30 @@ func (f *StringFlag) Apply(set *flag.FlagSet) {
f.StringFlag.Apply(set) 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 // for other values to be specified
type IntFlag struct { type IntFlag struct {
cli.IntFlag *cli.IntFlag
set *flag.FlagSet set *flag.FlagSet
} }
// NewIntFlag creates a new IntFlag // NewIntFlag creates a new IntFlag
func NewIntFlag(flag cli.IntFlag) *IntFlag { func NewIntFlag(flag *cli.IntFlag) *IntFlag {
return &IntFlag{IntFlag: flag, set: nil} return &IntFlag{IntFlag: flag, set: nil}
} }
// ApplyInputSourceValue applies a int value to the flagSet if required // ApplyInputSourceValue applies a int value to the flagSet if required
func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil { 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) value, err := isc.Int(f.IntFlag.Name)
if err != nil { if err != nil {
return err return err
} }
if value > 0 { if value > 0 {
eachName(f.Name, func(name string) { for _, name := range f.Names() {
f.set.Set(f.Name, strconv.FormatInt(int64(value), 10)) f.set.Set(name, strconv.FormatInt(int64(value), 10))
}) }
} }
} }
} }
@ -295,30 +294,30 @@ func (f *IntFlag) Apply(set *flag.FlagSet) {
f.IntFlag.Apply(set) 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 // for other values to be specified
type DurationFlag struct { type DurationFlag struct {
cli.DurationFlag *cli.DurationFlag
set *flag.FlagSet set *flag.FlagSet
} }
// NewDurationFlag creates a new DurationFlag // NewDurationFlag creates a new DurationFlag
func NewDurationFlag(flag cli.DurationFlag) *DurationFlag { func NewDurationFlag(flag *cli.DurationFlag) *DurationFlag {
return &DurationFlag{DurationFlag: flag, set: nil} return &DurationFlag{DurationFlag: flag, set: nil}
} }
// ApplyInputSourceValue applies a Duration value to the flagSet if required // ApplyInputSourceValue applies a Duration value to the flagSet if required
func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil { 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) value, err := isc.Duration(f.DurationFlag.Name)
if err != nil { if err != nil {
return err return err
} }
if value > 0 { if value > 0 {
eachName(f.Name, func(name string) { for _, name := range f.Names() {
f.set.Set(f.Name, value.String()) f.set.Set(name, value.String())
}) }
} }
} }
} }
@ -333,31 +332,31 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) {
f.DurationFlag.Apply(set) 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 // for other values to be specified
type Float64Flag struct { type Float64Flag struct {
cli.Float64Flag *cli.Float64Flag
set *flag.FlagSet set *flag.FlagSet
} }
// NewFloat64Flag creates a new Float64Flag // NewFloat64Flag creates a new Float64Flag
func NewFloat64Flag(flag cli.Float64Flag) *Float64Flag { func NewFloat64Flag(flag *cli.Float64Flag) *Float64Flag {
return &Float64Flag{Float64Flag: flag, set: nil} return &Float64Flag{Float64Flag: flag, set: nil}
} }
// ApplyInputSourceValue applies a Float64 value to the flagSet if required // ApplyInputSourceValue applies a Float64 value to the flagSet if required
func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
if f.set != nil { 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) value, err := isc.Float64(f.Float64Flag.Name)
if err != nil { if err != nil {
return err return err
} }
if value > 0 { if value > 0 {
floatStr := float64ToString(value) floatStr := float64ToString(value)
eachName(f.Name, func(name string) { for _, name := range f.Names() {
f.set.Set(f.Name, floatStr) f.set.Set(name, floatStr)
}) }
} }
} }
} }
@ -372,9 +371,8 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) {
f.Float64Flag.Apply(set) f.Float64Flag.Apply(set)
} }
func isEnvVarSet(envVars string) bool { func isEnvVarSet(envVars []string) bool {
for _, envVar := range strings.Split(envVars, ",") { for _, envVar := range envVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
// TODO: Can't use this for bools as // TODO: Can't use this for bools as
// set means that it was true or false based on // set means that it was true or false based on
@ -391,11 +389,3 @@ func isEnvVarSet(envVars string) bool {
func float64ToString(f float64) string { func float64ToString(f float64) string {
return fmt.Sprintf("%v", f) 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" "testing"
"time" "time"
"github.com/codegangsta/cli" "github.com/urfave/cli"
) )
type testApplyInputSource struct { type testApplyInputSource struct {
@ -26,7 +26,7 @@ type testApplyInputSource struct {
func TestGenericApplyInputSourceValue(t *testing.T) { func TestGenericApplyInputSourceValue(t *testing.T) {
v := &Parser{"abc", "def"} v := &Parser{"abc", "def"}
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
FlagName: "test", FlagName: "test",
MapValue: v, MapValue: v,
}) })
@ -36,7 +36,7 @@ func TestGenericApplyInputSourceValue(t *testing.T) {
func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
p := &Parser{"abc", "def"} p := &Parser{"abc", "def"}
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
FlagName: "test", FlagName: "test",
MapValue: &Parser{"efg", "hig"}, MapValue: &Parser{"efg", "hig"},
ContextValueString: p.String(), ContextValueString: p.String(),
@ -46,7 +46,11 @@ func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ 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", FlagName: "test",
MapValue: &Parser{"efg", "hij"}, MapValue: &Parser{"efg", "hij"},
EnvVarName: "TEST", EnvVarName: "TEST",
@ -57,7 +61,7 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestStringSliceApplyInputSourceValue(t *testing.T) { func TestStringSliceApplyInputSourceValue(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []string{"hello", "world"},
}) })
@ -66,7 +70,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) {
func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []string{"hello", "world"},
ContextValueString: "ohno", ContextValueString: "ohno",
@ -76,7 +80,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: []string{"hello", "world"}, MapValue: []string{"hello", "world"},
EnvVarName: "TEST", EnvVarName: "TEST",
@ -87,7 +91,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestIntSliceApplyInputSourceValue(t *testing.T) { func TestIntSliceApplyInputSourceValue(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []int{1, 2},
}) })
@ -96,7 +100,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) {
func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []int{1, 2},
ContextValueString: "3", ContextValueString: "3",
@ -106,7 +110,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: []int{1, 2}, MapValue: []int{1, 2},
EnvVarName: "TEST", EnvVarName: "TEST",
@ -117,7 +121,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestBoolApplyInputSourceMethodSet(t *testing.T) { func TestBoolApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: true, MapValue: true,
}) })
@ -126,7 +130,7 @@ func TestBoolApplyInputSourceMethodSet(t *testing.T) {
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: false, MapValue: false,
ContextValueString: "true", ContextValueString: "true",
@ -136,7 +140,7 @@ func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}), Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: false, MapValue: false,
EnvVarName: "TEST", EnvVarName: "TEST",
@ -147,7 +151,7 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestStringApplyInputSourceMethodSet(t *testing.T) { func TestStringApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringFlag(cli.StringFlag{Name: "test"}), Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: "hello", MapValue: "hello",
}) })
@ -156,7 +160,7 @@ func TestStringApplyInputSourceMethodSet(t *testing.T) {
func TestStringApplyInputSourceMethodContextSet(t *testing.T) { func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringFlag(cli.StringFlag{Name: "test"}), Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: "hello", MapValue: "hello",
ContextValueString: "goodbye", ContextValueString: "goodbye",
@ -166,7 +170,7 @@ func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}), Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: "hello", MapValue: "hello",
EnvVarName: "TEST", EnvVarName: "TEST",
@ -177,7 +181,7 @@ func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestIntApplyInputSourceMethodSet(t *testing.T) { func TestIntApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntFlag(cli.IntFlag{Name: "test"}), Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: 15, MapValue: 15,
}) })
@ -186,7 +190,7 @@ func TestIntApplyInputSourceMethodSet(t *testing.T) {
func TestIntApplyInputSourceMethodContextSet(t *testing.T) { func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntFlag(cli.IntFlag{Name: "test"}), Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: 15, MapValue: 15,
ContextValueString: "7", ContextValueString: "7",
@ -196,7 +200,7 @@ func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}), Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: 15, MapValue: 15,
EnvVarName: "TEST", EnvVarName: "TEST",
@ -207,7 +211,7 @@ func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestDurationApplyInputSourceMethodSet(t *testing.T) { func TestDurationApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: time.Duration(30 * time.Second), MapValue: time.Duration(30 * time.Second),
}) })
@ -216,7 +220,7 @@ func TestDurationApplyInputSourceMethodSet(t *testing.T) {
func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: time.Duration(30 * time.Second), MapValue: time.Duration(30 * time.Second),
ContextValueString: time.Duration(15 * time.Second).String(), ContextValueString: time.Duration(15 * time.Second).String(),
@ -226,7 +230,7 @@ func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}), Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: time.Duration(30 * time.Second), MapValue: time.Duration(30 * time.Second),
EnvVarName: "TEST", EnvVarName: "TEST",
@ -237,7 +241,7 @@ func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: 1.3, MapValue: 1.3,
}) })
@ -246,7 +250,7 @@ func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
FlagName: "test", FlagName: "test",
MapValue: 1.3, MapValue: 1.3,
ContextValueString: fmt.Sprintf("%v", 1.4), ContextValueString: fmt.Sprintf("%v", 1.4),
@ -256,7 +260,7 @@ func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{ c := runTest(t, testApplyInputSource{
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}), Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test", FlagName: "test",
MapValue: 1.3, MapValue: 1.3,
EnvVarName: "TEST", EnvVarName: "TEST",

View File

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

View File

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

View File

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

View File

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

76
app.go
View File

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

View File

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

View File

@ -2,15 +2,24 @@ version: "{build}"
os: Windows Server 2012 R2 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: install:
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
- go version - go version
- go env - go env
- go get github.com/urfave/gfmxr/...
- go get -v -t ./...
build_script: build_script:
- cd %APPVEYOR_BUILD_FOLDER% - python runtests vet
- go vet ./... - python runtests test
- go test -v ./... - python runtests gfmxr
test: off
deploy: off

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 package cli
// CommandCategories is a slice of *CommandCategory. type CommandCategories interface {
type CommandCategories []*CommandCategory // 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. // CommandCategory is a category containing commands.
type CommandCategory struct { type CommandCategory interface {
Name string // Name returns the category name string
Commands Commands Name() string
}
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]
}
// 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
}
}
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
}
// VisibleCommands returns a slice of the Commands with Hidden=false // VisibleCommands returns a slice of the Commands with Hidden=false
func (c *CommandCategory) VisibleCommands() []Command { VisibleCommands() []*Command
ret := []Command{} }
for _, command := range c.Commands {
type commandCategory struct {
name string
commands []*Command
}
func newCommandCategory(name string) *commandCategory {
return &commandCategory{
name: name,
commands: []*Command{},
}
}
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 { if !command.Hidden {
ret = append(ret, command) ret = append(ret, command)
} }

View File

@ -36,7 +36,7 @@ type Command struct {
// Execute this function if a usage error occurs. // Execute this function if a usage error occurs.
OnUsageError OnUsageErrorFunc OnUsageError OnUsageErrorFunc
// List of child commands // List of child commands
Subcommands Commands Subcommands []*Command
// List of flags to parse // List of flags to parse
Flags []Flag Flags []Flag
// Treat all flags as normal arguments if true // Treat all flags as normal arguments if true
@ -53,32 +53,26 @@ type Command struct {
// FullName returns the full name of the command. // FullName returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path // 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 { if c.commandNamePath == nil {
return c.Name return c.Name
} }
return strings.Join(c.commandNamePath, " ") 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 // 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 { if len(c.Subcommands) > 0 {
return c.startApp(ctx) return c.startApp(ctx)
} }
if !c.HideHelp && (HelpFlag != BoolFlag{}) { if !c.HideHelp && HelpFlag != nil {
// append help to flags // append help to flags
c.Flags = append( c.appendFlag(HelpFlag)
c.Flags,
HelpFlag,
)
} }
if ctx.App.EnableBashCompletion { if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag) c.appendFlag(BashCompletionFlag)
} }
set := flagSet(c.Name, c.Flags) set := flagSet(c.Name, c.Flags)
@ -96,7 +90,7 @@ func (c Command) Run(ctx *Context) (err error) {
HandleExitCoder(err) HandleExitCoder(err)
return err return err
} }
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") fmt.Fprintf(ctx.App.Writer, "Incorrect Usage: %s\n\n", err)
fmt.Fprintln(ctx.App.Writer) fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name) ShowCommandHelp(ctx, c.Name)
return err return err
@ -126,7 +120,7 @@ func (c Command) Run(ctx *Context) (err error) {
if afterErr != nil { if afterErr != nil {
HandleExitCoder(err) HandleExitCoder(err)
if err != nil { if err != nil {
err = NewMultiError(err, afterErr) err = newMultiError(err, afterErr)
} else { } else {
err = afterErr err = afterErr
} }
@ -155,13 +149,12 @@ func (c Command) Run(ctx *Context) (err error) {
} }
// Names returns the names including short names and aliases. // Names returns the names including short names and aliases.
func (c Command) Names() []string { func (c *Command) Names() []string {
names := []string{c.Name} return append([]string{c.Name}, c.Aliases...)
return append(names, c.Aliases...)
} }
// HasName returns true if Command.Name matches given name // 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() { for _, n := range c.Names() {
if n == name { if n == name {
return true return true
@ -170,7 +163,7 @@ func (c Command) HasName(name string) bool {
return false return false
} }
func (c Command) startApp(ctx *Context) error { func (c *Command) startApp(ctx *Context) error {
app := NewApp() app := NewApp()
app.Metadata = ctx.App.Metadata app.Metadata = ctx.App.Metadata
// set the name and usage // set the name and usage
@ -200,12 +193,12 @@ func (c Command) startApp(ctx *Context) error {
app.Compiled = ctx.App.Compiled app.Compiled = ctx.App.Compiled
app.Writer = ctx.App.Writer app.Writer = ctx.App.Writer
app.categories = CommandCategories{} app.Categories = newCommandCategories()
for _, command := range c.Subcommands { 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 // bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion 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 // VisibleFlags returns a slice of the Flags with Hidden=false
func (c Command) VisibleFlags() []Flag { func (c *Command) VisibleFlags() []Flag {
return visibleFlags(c.Flags) 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) err := command.Run(context)
expect(t, err, c.expectedErr) 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) { func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
app := NewApp() app := NewApp()
app.Commands = []Command{ app.Commands = []*Command{
{ {
Name: "bar", Name: "bar",
Before: func(c *Context) error { Before: func(c *Context) error {
@ -75,11 +75,11 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
app := NewApp() app := NewApp()
app.Commands = []Command{ app.Commands = []*Command{
{ {
Name: "bar", Name: "bar",
Flags: []Flag{ Flags: []Flag{
IntFlag{Name: "flag"}, &IntFlag{Name: "flag"},
}, },
OnUsageError: func(c *Context, err error, _ bool) error { OnUsageError: func(c *Context, err error, _ bool) error {
if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {

View File

@ -10,11 +10,11 @@ import (
// Context is a type that is passed through to // Context is a type that is passed through to
// each Handler action in a cli application. Context // 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. // parsed command-line options.
type Context struct { type Context struct {
App *App App *App
Command Command Command *Command
flagSet *flag.FlagSet flagSet *flag.FlagSet
parentContext *Context parentContext *Context
@ -147,54 +147,15 @@ func (c *Context) Lineage() []*Context {
return lineage return lineage
} }
// Args contains apps console arguments
type Args []string
// Args returns the command line arguments associated with the context. // Args returns the command line arguments associated with the context.
func (c *Context) Args() Args { func (c *Context) Args() Args {
args := Args(c.flagSet.Args()) ret := args(c.flagSet.Args())
return args return &ret
} }
// NArg returns the number of the command line arguments. // NArg returns the number of the command line arguments.
func (c *Context) NArg() int { func (c *Context) NArg() int {
return len(c.Args()) return c.Args().Len()
}
// 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
} }
func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
@ -310,7 +271,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited[f.Name] = true visited[f.Name] = true
}) })
for _, f := range flags { for _, f := range flags {
parts := strings.Split(f.GetName(), ",") parts := f.Names()
if len(parts) == 1 { if len(parts) == 1 {
continue continue
} }

View File

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

View File

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

View File

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

328
flag.go
View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"reflect" "reflect"
"regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -14,25 +15,31 @@ import (
const defaultPlaceholder = "value" 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 // BashCompletionFlag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{ var BashCompletionFlag = &BoolFlag{
Name: "generate-bash-completion", Name: "generate-bash-completion",
Hidden: true, Hidden: true,
} }
// VersionFlag prints the version for the application // VersionFlag prints the version for the application
var VersionFlag = BoolFlag{ var VersionFlag = &BoolFlag{
Name: "version, v", Name: "version",
Aliases: []string{"v"},
Usage: "print the version", Usage: "print the version",
} }
// HelpFlag prints the help for all commands and subcommands // HelpFlag prints the help for all commands and subcommands.
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand // Set to nil to disable the flag. The subcommand
// unless HideHelp is set to true) // will still be added unless HideHelp is set to true.
var HelpFlag = BoolFlag{ var HelpFlag = &BoolFlag{
Name: "help, h", Name: "help",
Aliases: []string{"h"},
Usage: "show help", Usage: "show help",
} }
@ -52,7 +59,7 @@ type Flag interface {
fmt.Stringer fmt.Stringer
// Apply Flag settings to the given flag set // Apply Flag settings to the given flag set
Apply(*flag.FlagSet) Apply(*flag.FlagSet)
GetName() string Names() []string
} }
func flagSet(name string, flags []Flag) *flag.FlagSet { func flagSet(name string, flags []Flag) *flag.FlagSet {
@ -64,14 +71,6 @@ func flagSet(name string, flags []Flag) *flag.FlagSet {
return set 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 // Generic is a generic parseable type identified by a specific flag
type Generic interface { type Generic interface {
Set(value string) error Set(value string) error
@ -81,26 +80,26 @@ type Generic interface {
// GenericFlag is the flag type for types implementing Generic // GenericFlag is the flag type for types implementing Generic
type GenericFlag struct { type GenericFlag struct {
Name string Name string
Aliases []string
Value Generic Value Generic
Usage string Usage string
EnvVar string EnvVars []string
Hidden bool Hidden bool
} }
// String returns the string representation of the generic flag to display the // 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 // help text to the user (uses the String() method of the generic flag to show
// the value) // the value)
func (f GenericFlag) String() string { func (f *GenericFlag) String() string {
return FlagStringer(f) return FlagStringer(f)
} }
// Apply takes the flagset and calls Set on the generic flag with the value // Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag // 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 val := f.Value
if f.EnvVar != "" { if f.EnvVars != nil {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range f.EnvVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
val.Set(envVal) val.Set(envVal)
break break
@ -108,14 +107,14 @@ func (f GenericFlag) Apply(set *flag.FlagSet) {
} }
} }
eachName(f.Name, func(name string) { for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage) set.Var(val, name, f.Usage)
}) }
} }
// GetName returns the name of a flag. // Names returns the names of a flag.
func (f GenericFlag) GetName() string { func (f *GenericFlag) Names() []string {
return f.Name return flagNames(f)
} }
// StringSlice wraps a []string to satisfy flag.Value // StringSlice wraps a []string to satisfy flag.Value
@ -167,22 +166,22 @@ func (f *StringSlice) Value() []string {
// command-line // command-line
type StringSliceFlag struct { type StringSliceFlag struct {
Name string Name string
Aliases []string
Value *StringSlice Value *StringSlice
Usage string Usage string
EnvVar string EnvVars []string
Hidden bool Hidden bool
} }
// String returns the usage // String returns the usage
func (f StringSliceFlag) String() string { func (f *StringSliceFlag) String() string {
return FlagStringer(f) return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f StringSliceFlag) Apply(set *flag.FlagSet) { func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVars != nil {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range f.EnvVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
newVal := NewStringSlice() newVal := NewStringSlice()
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
@ -199,14 +198,14 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
f.Value = NewStringSlice() f.Value = NewStringSlice()
} }
eachName(f.Name, func(name string) { for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) }
} }
// GetName returns the name of a flag. // Names returns the name of a flag.
func (f StringSliceFlag) GetName() string { func (f *StringSliceFlag) Names() []string {
return f.Name return flagNames(f)
} }
// IntSlice wraps an []int to satisfy flag.Value // IntSlice wraps an []int to satisfy flag.Value
@ -273,22 +272,22 @@ func (i *IntSlice) Value() []int {
// command-line // command-line
type IntSliceFlag struct { type IntSliceFlag struct {
Name string Name string
Aliases []string
Value *IntSlice Value *IntSlice
Usage string Usage string
EnvVar string EnvVars []string
Hidden bool Hidden bool
} }
// String returns the usage // String returns the usage
func (f IntSliceFlag) String() string { func (f *IntSliceFlag) String() string {
return FlagStringer(f) return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f IntSliceFlag) Apply(set *flag.FlagSet) { func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVars != nil {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range f.EnvVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
newVal := NewIntSlice() newVal := NewIntSlice()
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
@ -308,36 +307,36 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
f.Value = NewIntSlice() f.Value = NewIntSlice()
} }
eachName(f.Name, func(name string) { for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) }
} }
// GetName returns the name of the flag. // Names returns the name of the flag.
func (f IntSliceFlag) GetName() string { func (f *IntSliceFlag) Names() []string {
return f.Name return flagNames(f)
} }
// BoolFlag is a switch that defaults to false // BoolFlag is a switch that defaults to false
type BoolFlag struct { type BoolFlag struct {
Name string Name string
Aliases []string
Value bool Value bool
Usage string Usage string
EnvVar string EnvVars []string
Destination *bool Destination *bool
Hidden bool Hidden bool
} }
// String returns a readable representation of this value (for usage defaults) // String returns a readable representation of this value (for usage defaults)
func (f BoolFlag) String() string { func (f *BoolFlag) String() string {
return FlagStringer(f) return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f BoolFlag) Apply(set *flag.FlagSet) { func (f *BoolFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVars != nil {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range f.EnvVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal) envValBool, err := strconv.ParseBool(envVal)
if err == nil { 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 { if f.Destination != nil {
set.BoolVar(f.Destination, name, f.Value, f.Usage) set.BoolVar(f.Destination, name, f.Value, f.Usage)
return continue
} }
set.Bool(name, f.Value, f.Usage) set.Bool(name, f.Value, f.Usage)
}) }
} }
// GetName returns the name of the flag. // Names returns the name of the flag.
func (f BoolFlag) GetName() string { func (f *BoolFlag) Names() []string {
return f.Name return flagNames(f)
} }
// StringFlag represents a flag that takes as string value // StringFlag represents a flag that takes as string value
type StringFlag struct { type StringFlag struct {
Name string Name string
Aliases []string
Value string Value string
Usage string Usage string
EnvVar string EnvVars []string
Destination *string Destination *string
Hidden bool Hidden bool
} }
// String returns the usage // String returns the usage
func (f StringFlag) String() string { func (f *StringFlag) String() string {
return FlagStringer(f) return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f StringFlag) Apply(set *flag.FlagSet) { func (f *StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVars != nil {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range f.EnvVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
f.Value = envVal f.Value = envVal
break 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 { if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage) set.StringVar(f.Destination, name, f.Value, f.Usage)
return continue
} }
set.String(name, f.Value, f.Usage) set.String(name, f.Value, f.Usage)
}) }
} }
// GetName returns the name of the flag. // Names returns the name of the flag.
func (f StringFlag) GetName() string { func (f *StringFlag) Names() []string {
return f.Name return flagNames(f)
} }
// IntFlag is a flag that takes an integer // IntFlag is a flag that takes an integer
// Errors if the value provided cannot be parsed // Errors if the value provided cannot be parsed
type IntFlag struct { type IntFlag struct {
Name string Name string
Aliases []string
Value int Value int
Usage string Usage string
EnvVar string EnvVars []string
Destination *int Destination *int
Hidden bool Hidden bool
} }
// String returns the usage // String returns the usage
func (f IntFlag) String() string { func (f *IntFlag) String() string {
return FlagStringer(f) return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f IntFlag) Apply(set *flag.FlagSet) { func (f *IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVars != nil {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range f.EnvVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
envValInt, err := strconv.ParseInt(envVal, 0, 64) envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil { 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 { if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage) set.IntVar(f.Destination, name, f.Value, f.Usage)
return continue
} }
set.Int(name, f.Value, f.Usage) set.Int(name, f.Value, f.Usage)
}) }
} }
// GetName returns the name of the flag. // Names returns the name of the flag.
func (f IntFlag) GetName() string { func (f *IntFlag) Names() []string {
return f.Name return flagNames(f)
} }
// DurationFlag is a flag that takes a duration specified in Go's duration // DurationFlag is a flag that takes a duration specified in Go's duration
// format: https://golang.org/pkg/time/#ParseDuration // format: https://golang.org/pkg/time/#ParseDuration
type DurationFlag struct { type DurationFlag struct {
Name string Name string
Aliases []string
Value time.Duration Value time.Duration
Usage string Usage string
EnvVar string EnvVars []string
Destination *time.Duration Destination *time.Duration
Hidden bool Hidden bool
} }
// String returns a readable representation of this value (for usage defaults) // String returns a readable representation of this value (for usage defaults)
func (f DurationFlag) String() string { func (f *DurationFlag) String() string {
return FlagStringer(f) return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f DurationFlag) Apply(set *flag.FlagSet) { func (f *DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVars != nil {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range f.EnvVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
envValDuration, err := time.ParseDuration(envVal) envValDuration, err := time.ParseDuration(envVal)
if err == nil { 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 { if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage) set.DurationVar(f.Destination, name, f.Value, f.Usage)
return continue
} }
set.Duration(name, f.Value, f.Usage) set.Duration(name, f.Value, f.Usage)
}) }
} }
// GetName returns the name of the flag. // Names returns the name of the flag.
func (f DurationFlag) GetName() string { func (f *DurationFlag) Names() []string {
return f.Name return flagNames(f)
} }
// Float64Flag is a flag that takes an float value // Float64Flag is a flag that takes an float value
// Errors if the value provided cannot be parsed // Errors if the value provided cannot be parsed
type Float64Flag struct { type Float64Flag struct {
Name string Name string
Aliases []string
Value float64 Value float64
Usage string Usage string
EnvVar string EnvVars []string
Destination *float64 Destination *float64
Hidden bool Hidden bool
} }
// String returns the usage // String returns the usage
func (f Float64Flag) String() string { func (f *Float64Flag) String() string {
return FlagStringer(f) return FlagStringer(f)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f Float64Flag) Apply(set *flag.FlagSet) { func (f *Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVars != nil {
for _, envVar := range strings.Split(f.EnvVar, ",") { for _, envVar := range f.EnvVars {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" { if envVal := os.Getenv(envVar); envVal != "" {
envValFloat, err := strconv.ParseFloat(envVal, 10) envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil { 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 { if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage) set.Float64Var(f.Destination, name, f.Value, f.Usage)
return continue
} }
set.Float64(name, f.Value, f.Usage) set.Float64(name, f.Value, f.Usage)
}) }
} }
// GetName returns the name of the flag. // Names returns the name of the flag.
func (f Float64Flag) GetName() string { func (f *Float64Flag) Names() []string {
return f.Name return flagNames(f)
} }
func visibleFlags(fl []Flag) []Flag { func visibleFlags(fl []Flag) []Flag {
visible := []Flag{} visible := []Flag{}
for _, flag := range fl { for _, flag := range fl {
if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { if !flagValue(flag).FieldByName("Hidden").Bool() {
visible = append(visible, flag) visible = append(visible, flag)
} }
} }
@ -574,25 +573,27 @@ func unquoteUsage(usage string) (string, string) {
return "", usage return "", usage
} }
func prefixedNames(fullName, placeholder string) string { func prefixedNames(names []string, placeholder string) string {
var prefixed string var prefixed string
parts := strings.Split(fullName, ",") for i, name := range names {
for i, name := range parts { if name == "" {
name = strings.Trim(name, " ") continue
}
prefixed += prefixFor(name) + name prefixed += prefixFor(name) + name
if placeholder != "" { if placeholder != "" {
prefixed += " " + placeholder prefixed += " " + placeholder
} }
if i < len(parts)-1 { if i < len(names)-1 {
prefixed += ", " prefixed += ", "
} }
} }
return prefixed return prefixed
} }
func withEnvHint(envVar, str string) string { func withEnvHint(envVars []string, str string) string {
envText := "" envText := ""
if envVar != "" { if envVars != nil && len(envVars) > 0 {
prefix := "$" prefix := "$"
suffix := "" suffix := ""
sep := ", $" sep := ", $"
@ -601,21 +602,66 @@ func withEnvHint(envVar, str string) string {
suffix = "%" suffix = "%"
sep = "%, %" 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 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) 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) { switch f.(type) {
case IntSliceFlag: case *IntSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(), return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(*IntSliceFlag)))
stringifyIntSliceFlag(f.(IntSliceFlag))) case *StringSliceFlag:
case StringSliceFlag: return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(*StringSliceFlag)))
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyStringSliceFlag(f.(StringSliceFlag)))
} }
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) 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)) usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
return withEnvHint(fv.FieldByName("EnvVar").String(), return withEnvHint(flagStringSliceField(f, "EnvVars"),
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault))
} }
func stringifyIntSliceFlag(f IntSliceFlag) string { func stringifyIntSliceFlag(f *IntSliceFlag) string {
defaultVals := []string{} defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 { if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() { 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{} defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 { if f.Value != nil && len(f.Value.Value()) > 0 {
for _, s := range f.Value.Value() { 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) placeholder, usage := unquoteUsage(usage)
if placeholder == "" { if placeholder == "" {
placeholder = defaultPlaceholder placeholder = defaultPlaceholder
@ -683,5 +729,15 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string {
} }
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) 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 package cli
import ( import (
"flag"
"fmt" "fmt"
"os" "os"
"reflect" "reflect"
@ -20,7 +21,7 @@ var boolFlagTests = []struct {
func TestBoolFlagHelpOutput(t *testing.T) { func TestBoolFlagHelpOutput(t *testing.T) {
for _, test := range boolFlagTests { for _, test := range boolFlagTests {
flag := BoolFlag{Name: test.name} flag := &BoolFlag{Name: test.name}
output := flag.String() output := flag.String()
if output != test.expected { 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 { var stringFlagTests = []struct {
name string name string
aliases []string
usage string usage string
value string value string
expected string expected string
}{ }{
{"foo", "", "", "--foo value\t"}, {"foo", nil, "", "", "--foo value\t"},
{"f", "", "", "-f value\t"}, {"f", nil, "", "", "-f value\t"},
{"f", "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"}, {"f", nil, "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"},
{"test", "", "Something", "--test value\t(default: \"Something\")"}, {"test", nil, "", "Something", "--test value\t(default: \"Something\")"},
{"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, {"config", []string{"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\")"}, {"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) { func TestStringFlagHelpOutput(t *testing.T) {
for _, test := range stringFlagTests { 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() output := flag.String()
if output != test.expected { if output != test.expected {
@ -58,7 +71,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_FOO", "derp") os.Setenv("APP_FOO", "derp")
for _, test := range stringFlagTests { 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() output := flag.String()
expectedSuffix := " [$APP_FOO]" 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 { var stringSliceFlagTests = []struct {
name string name string
aliases []string
value *StringSlice value *StringSlice
expected string expected string
}{ }{
{"foo", NewStringSlice(""), "--foo value\t"}, {"foo", nil, NewStringSlice(""), "--foo value\t"},
{"f", NewStringSlice(""), "-f value\t"}, {"f", nil, NewStringSlice(""), "-f value\t"},
{"f", NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"},
{"test", NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"},
{"d, dee", NewStringSlice("Inka", "Dinka", "dooo"), "-d value, --dee value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"},
} }
func TestStringSliceFlagHelpOutput(t *testing.T) { func TestStringSliceFlagHelpOutput(t *testing.T) {
for _, test := range stringSliceFlagTests { 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() output := flag.String()
if output != test.expected { if output != test.expected {
@ -98,7 +123,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_QWWX", "11,4") os.Setenv("APP_QWWX", "11,4")
for _, test := range stringSliceFlagTests { 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() output := flag.String()
expectedSuffix := " [$APP_QWWX]" 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 { var intFlagTests = []struct {
name string name string
expected string expected string
@ -121,7 +155,7 @@ var intFlagTests = []struct {
func TestIntFlagHelpOutput(t *testing.T) { func TestIntFlagHelpOutput(t *testing.T) {
for _, test := range intFlagTests { for _, test := range intFlagTests {
flag := IntFlag{Name: test.name, Value: 9} flag := &IntFlag{Name: test.name, Value: 9}
output := flag.String() output := flag.String()
if output != test.expected { if output != test.expected {
@ -134,7 +168,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_BAR", "2") os.Setenv("APP_BAR", "2")
for _, test := range intFlagTests { for _, test := range intFlagTests {
flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
output := flag.String() output := flag.String()
expectedSuffix := " [$APP_BAR]" 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 { var durationFlagTests = []struct {
name string name string
expected string expected string
@ -157,7 +202,7 @@ var durationFlagTests = []struct {
func TestDurationFlagHelpOutput(t *testing.T) { func TestDurationFlagHelpOutput(t *testing.T) {
for _, test := range durationFlagTests { 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() output := flag.String()
if output != test.expected { if output != test.expected {
@ -170,7 +215,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_BAR", "2h3m6s") os.Setenv("APP_BAR", "2h3m6s")
for _, test := range durationFlagTests { for _, test := range durationFlagTests {
flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
output := flag.String() output := flag.String()
expectedSuffix := " [$APP_BAR]" 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 { var intSliceFlagTests = []struct {
name string name string
aliases []string
value *IntSlice value *IntSlice
expected string expected string
}{ }{
{"heads", NewIntSlice(), "--heads value\t"}, {"heads", nil, NewIntSlice(), "--heads value\t"},
{"H", NewIntSlice(), "-H value\t"}, {"H", nil, NewIntSlice(), "-H value\t"},
{"H, heads", NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"}, {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"},
} }
func TestIntSliceFlagHelpOutput(t *testing.T) { func TestIntSliceFlagHelpOutput(t *testing.T) {
for _, test := range intSliceFlagTests { 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() output := flag.String()
if output != test.expected { if output != test.expected {
@ -208,7 +265,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_SMURF", "42,3") os.Setenv("APP_SMURF", "42,3")
for _, test := range intSliceFlagTests { 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() output := flag.String()
expectedSuffix := " [$APP_SMURF]" 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 { var float64FlagTests = []struct {
name string name string
expected string expected string
@ -231,7 +297,7 @@ var float64FlagTests = []struct {
func TestFloat64FlagHelpOutput(t *testing.T) { func TestFloat64FlagHelpOutput(t *testing.T) {
for _, test := range float64FlagTests { 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() output := flag.String()
if output != test.expected { if output != test.expected {
@ -244,7 +310,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_BAZ", "99.4") os.Setenv("APP_BAZ", "99.4")
for _, test := range float64FlagTests { for _, test := range float64FlagTests {
flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}}
output := flag.String() output := flag.String()
expectedSuffix := " [$APP_BAZ]" 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 { var genericFlagTests = []struct {
name string name string
value Generic value Generic
@ -268,7 +345,7 @@ var genericFlagTests = []struct {
func TestGenericFlagHelpOutput(t *testing.T) { func TestGenericFlagHelpOutput(t *testing.T) {
for _, test := range genericFlagTests { 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() output := flag.String()
if output != test.expected { if output != test.expected {
@ -281,7 +358,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv() os.Clearenv()
os.Setenv("APP_ZAP", "3") os.Setenv("APP_ZAP", "3")
for _, test := range genericFlagTests { for _, test := range genericFlagTests {
flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}}
output := flag.String() output := flag.String()
expectedSuffix := " [$APP_ZAP]" 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) { func TestParseMultiString(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ Flags: []Flag{
StringFlag{Name: "serve, s"}, &StringFlag{Name: "serve", Aliases: []string{"s"}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.String("serve") != "10" { if ctx.String("serve") != "10" {
@ -315,7 +401,7 @@ func TestParseDestinationString(t *testing.T) {
var dest string var dest string
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
StringFlag{ &StringFlag{
Name: "dest", Name: "dest",
Destination: &dest, Destination: &dest,
}, },
@ -335,7 +421,7 @@ func TestParseMultiStringFromEnv(t *testing.T) {
os.Setenv("APP_COUNT", "20") os.Setenv("APP_COUNT", "20")
(&App{ (&App{
Flags: []Flag{ Flags: []Flag{
StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.String("count") != "20" { if ctx.String("count") != "20" {
@ -354,7 +440,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) {
os.Setenv("APP_COUNT", "20") os.Setenv("APP_COUNT", "20")
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if ctx.String("count") != "20" { if ctx.String("count") != "20" {
@ -371,7 +457,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) {
func TestParseMultiStringSlice(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ Flags: []Flag{
StringSliceFlag{Name: "serve, s", Value: NewStringSlice()}, &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
expected := []string{"10", "20"} expected := []string{"10", "20"}
@ -389,7 +475,7 @@ func TestParseMultiStringSlice(t *testing.T) {
func TestParseMultiStringSliceWithDefaults(t *testing.T) { func TestParseMultiStringSliceWithDefaults(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
expected := []string{"10", "20"} expected := []string{"10", "20"}
@ -407,7 +493,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) {
func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) {
@ -427,7 +513,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@ -447,7 +533,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@ -467,7 +553,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@ -487,7 +573,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@ -504,7 +590,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) {
func TestParseMultiInt(t *testing.T) { func TestParseMultiInt(t *testing.T) {
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
IntFlag{Name: "serve, s"}, &IntFlag{Name: "serve", Aliases: []string{"s"}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.Int("serve") != 10 { if ctx.Int("serve") != 10 {
@ -523,7 +609,7 @@ func TestParseDestinationInt(t *testing.T) {
var dest int var dest int
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
IntFlag{ &IntFlag{
Name: "dest", Name: "dest",
Destination: &dest, Destination: &dest,
}, },
@ -543,7 +629,7 @@ func TestParseMultiIntFromEnv(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "10") os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := App{ a := App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if ctx.Int("timeout") != 10 { if ctx.Int("timeout") != 10 {
@ -563,7 +649,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "10") os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := App{ a := App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if ctx.Int("timeout") != 10 { if ctx.Int("timeout") != 10 {
@ -581,7 +667,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) {
func TestParseMultiIntSlice(t *testing.T) { func TestParseMultiIntSlice(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ Flags: []Flag{
IntSliceFlag{Name: "serve, s", Value: NewIntSlice()}, &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
@ -598,7 +684,7 @@ func TestParseMultiIntSlice(t *testing.T) {
func TestParseMultiIntSliceWithDefaults(t *testing.T) { func TestParseMultiIntSliceWithDefaults(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
@ -615,7 +701,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) {
func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) {
@ -635,7 +721,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@ -655,7 +741,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@ -675,7 +761,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
(&App{ (&App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@ -692,7 +778,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
func TestParseMultiFloat64(t *testing.T) { func TestParseMultiFloat64(t *testing.T) {
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
Float64Flag{Name: "serve, s"}, &Float64Flag{Name: "serve", Aliases: []string{"s"}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.Float64("serve") != 10.2 { if ctx.Float64("serve") != 10.2 {
@ -711,7 +797,7 @@ func TestParseDestinationFloat64(t *testing.T) {
var dest float64 var dest float64
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
Float64Flag{ &Float64Flag{
Name: "dest", Name: "dest",
Destination: &dest, Destination: &dest,
}, },
@ -731,7 +817,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := App{ a := App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if ctx.Float64("timeout") != 15.5 { if ctx.Float64("timeout") != 15.5 {
@ -751,7 +837,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := App{ a := App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if ctx.Float64("timeout") != 15.5 { if ctx.Float64("timeout") != 15.5 {
@ -769,7 +855,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
func TestParseMultiBool(t *testing.T) { func TestParseMultiBool(t *testing.T) {
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{Name: "serve, s"}, &BoolFlag{Name: "serve", Aliases: []string{"s"}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.Bool("serve") != true { if ctx.Bool("serve") != true {
@ -788,7 +874,7 @@ func TestParseDestinationBool(t *testing.T) {
var dest bool var dest bool
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{ &BoolFlag{
Name: "dest", Name: "dest",
Destination: &dest, Destination: &dest,
}, },
@ -808,7 +894,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) {
os.Setenv("APP_DEBUG", "1") os.Setenv("APP_DEBUG", "1")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.Bool("debug") != true { if ctx.Bool("debug") != true {
@ -828,7 +914,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) {
os.Setenv("APP_DEBUG", "1") os.Setenv("APP_DEBUG", "1")
a := App{ a := App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if ctx.Bool("debug") != true { if ctx.Bool("debug") != true {
@ -846,7 +932,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) {
func TestParseMultiBoolTrue(t *testing.T) { func TestParseMultiBoolTrue(t *testing.T) {
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{Name: "implode, i", Value: true}, &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.Bool("implode") { if ctx.Bool("implode") {
@ -866,7 +952,7 @@ func TestParseDestinationBoolTrue(t *testing.T) {
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{ &BoolFlag{
Name: "dest", Name: "dest",
Value: true, Value: true,
Destination: &dest, Destination: &dest,
@ -887,10 +973,11 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) {
os.Setenv("APP_DEBUG", "0") os.Setenv("APP_DEBUG", "0")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{ &BoolFlag{
Name: "debug, d", Name: "debug",
Aliases: []string{"d"},
Value: true, Value: true,
EnvVar: "APP_DEBUG", EnvVars: []string{"APP_DEBUG"},
}, },
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
@ -911,10 +998,11 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) {
os.Setenv("APP_DEBUG", "0") os.Setenv("APP_DEBUG", "0")
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{ &BoolFlag{
Name: "debug, d", Name: "debug",
Aliases: []string{"d"},
Value: true, Value: true,
EnvVar: "COMPAT_DEBUG,APP_DEBUG", EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"},
}, },
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
@ -951,7 +1039,7 @@ func (p *Parser) String() string {
func TestParseGeneric(t *testing.T) { func TestParseGeneric(t *testing.T) {
a := App{ a := App{
Flags: []Flag{ Flags: []Flag{
GenericFlag{Name: "serve, s", Value: &Parser{}}, &GenericFlag{Name: "serve", Aliases: []string{"s"}, Value: &Parser{}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) {
@ -971,7 +1059,12 @@ func TestParseGenericFromEnv(t *testing.T) {
os.Setenv("APP_SERVE", "20,30") os.Setenv("APP_SERVE", "20,30")
a := App{ a := App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
@ -991,7 +1084,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) {
os.Setenv("APP_FOO", "99,2000") os.Setenv("APP_FOO", "99,2000")
a := App{ a := App{
Flags: []Flag{ 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 { Action: func(ctx *Context) error {
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {

41
help.go
View File

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

View File

@ -59,14 +59,15 @@ func Test_Help_Custom_Flags(t *testing.T) {
HelpFlag = oldFlag HelpFlag = oldFlag
}() }()
HelpFlag = BoolFlag{ HelpFlag = &BoolFlag{
Name: "help, x", Name: "help",
Aliases: []string{"x"},
Usage: "show help", Usage: "show help",
} }
app := App{ app := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{Name: "foo, h"}, &BoolFlag{Name: "foo", Aliases: []string{"h"}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.Bool("h") != true { if ctx.Bool("h") != true {
@ -89,14 +90,15 @@ func Test_Version_Custom_Flags(t *testing.T) {
VersionFlag = oldFlag VersionFlag = oldFlag
}() }()
VersionFlag = BoolFlag{ VersionFlag = &BoolFlag{
Name: "version, V", Name: "version",
Aliases: []string{"V"},
Usage: "show version", Usage: "show version",
} }
app := App{ app := App{
Flags: []Flag{ Flags: []Flag{
BoolFlag{Name: "foo, v"}, &BoolFlag{Name: "foo", Aliases: []string{"v"}},
}, },
Action: func(ctx *Context) error { Action: func(ctx *Context) error {
if ctx.Bool("v") != true { 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") t.Fatalf("expected error from helpCommand.Action(), but got nil")
} }
exitErr, ok := err.(*ExitError) exitErr, ok := err.(*exitError)
if !ok { 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") { 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") t.Fatalf("expected error from helpCommand.Action(), but got nil")
} }
exitErr, ok := err.(*ExitError) exitErr, ok := err.(*exitError)
if !ok { 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") { 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) { func TestShowAppHelp_CommandAliases(t *testing.T) {
app := &App{ app := &App{
Commands: []Command{ Commands: []*Command{
{ {
Name: "frobbly", Name: "frobbly",
Aliases: []string{"fr", "frob"}, Aliases: []string{"fr", "frob"},
@ -193,7 +195,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) {
func TestShowCommandHelp_CommandAliases(t *testing.T) { func TestShowCommandHelp_CommandAliases(t *testing.T) {
app := &App{ app := &App{
Commands: []Command{ Commands: []*Command{
{ {
Name: "frobbly", Name: "frobbly",
Aliases: []string{"fr", "frob", "bork"}, Aliases: []string{"fr", "frob", "bork"},
@ -219,7 +221,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) {
func TestShowSubcommandHelp_CommandAliases(t *testing.T) { func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
app := &App{ app := &App{
Commands: []Command{ Commands: []*Command{
{ {
Name: "frobbly", Name: "frobbly",
Aliases: []string{"fr", "frob", "bork"}, Aliases: []string{"fr", "frob", "bork"},
@ -241,7 +243,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
func TestShowAppHelp_HiddenCommand(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) {
app := &App{ app := &App{
Commands: []Command{ Commands: []*Command{
{ {
Name: "frobbly", Name: "frobbly",
Action: func(ctx *Context) error { Action: func(ctx *Context) error {

View File

@ -12,6 +12,10 @@ var (
wd, _ = os.Getwd() wd, _ = os.Getwd()
) )
func init() {
os.Setenv("CLI_TEMPLATE_REPANIC", "1")
}
func expect(t *testing.T, a interface{}, b interface{}) { func expect(t *testing.T, a interface{}, b interface{}) {
_, fn, line, _ := runtime.Caller(1) _, fn, line, _ := runtime.Caller(1)
fn = strings.Replace(fn, wd+"/", "", -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( 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('/') ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
]) ])
combined = _combine_coverprofiles(coverprofiles) combined_name = _combine_coverprofiles(coverprofiles)
_run('go tool cover -func={}'.format(combined.name).split()) _run('go tool cover -func={}'.format(combined_name).split())
combined.close() os.remove(combined_name)
def _gfmxr(): def _gfmxr():
@ -78,7 +78,9 @@ def _is_go_runnable(line):
def _combine_coverprofiles(coverprofiles): def _combine_coverprofiles(coverprofiles):
combined = tempfile.NamedTemporaryFile(suffix='.coverprofile') combined = tempfile.NamedTemporaryFile(
suffix='.coverprofile', delete=False
)
combined.write('mode: set\n') combined.write('mode: set\n')
for coverprofile in coverprofiles: for coverprofile in coverprofiles:
@ -88,7 +90,9 @@ def _combine_coverprofiles(coverprofiles):
combined.write(line) combined.write(line)
combined.flush() combined.flush()
return combined name = combined.name
combined.close()
return name
if __name__ == '__main__': if __name__ == '__main__':