Merge branch 'master' into merging-jereksel-zsh
This commit is contained in:
commit
5fc8124af1
25
.travis.yml
25
.travis.yml
@ -1,28 +1,17 @@
|
||||
language: go
|
||||
|
||||
sudo: false
|
||||
dist: trusty
|
||||
osx_image: xcode8.3
|
||||
go: 1.8.x
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- master
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
include:
|
||||
- go: 1.6.x
|
||||
os: osx
|
||||
- go: 1.7.x
|
||||
os: osx
|
||||
- go: 1.8.x
|
||||
os: osx
|
||||
|
||||
before_script:
|
||||
- go get github.com/urfave/gfmrun/... || true
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
|
43
CHANGELOG.md
43
CHANGELOG.md
@ -4,6 +4,49 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## 1.20.0 - 2017-08-10
|
||||
|
||||
### Fixed
|
||||
|
||||
* `HandleExitCoder` is now correctly iterates over all errors in
|
||||
a `MultiError`. The exit code is the exit code of the last error or `1` if
|
||||
there are no `ExitCoder`s in the `MultiError`.
|
||||
* Fixed YAML file loading on Windows (previously would fail validate the file path)
|
||||
* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly
|
||||
propogated
|
||||
* `ErrWriter` is now passed downwards through command structure to avoid the
|
||||
need to redefine it
|
||||
* Pass `Command` context into `OnUsageError` rather than parent context so that
|
||||
all fields are avaiable
|
||||
* Errors occuring in `Before` funcs are no longer double printed
|
||||
* Use `UsageText` in the help templates for commands and subcommands if
|
||||
defined; otherwise build the usage as before (was previously ignoring this
|
||||
field)
|
||||
* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if
|
||||
a program calls `Set` or `GlobalSet` directly after flag parsing (would
|
||||
previously only return `true` if the flag was set during parsing)
|
||||
|
||||
### Changed
|
||||
|
||||
* No longer exit the program on command/subcommand error if the error raised is
|
||||
not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was
|
||||
determined to be a regression in functionality. See [the
|
||||
PR](https://github.com/urfave/cli/pull/595) for discussion.
|
||||
|
||||
### Added
|
||||
|
||||
* `CommandsByName` type was added to make it easy to sort `Command`s by name,
|
||||
alphabetically
|
||||
* `altsrc` now handles loading of string and int arrays from TOML
|
||||
* Support for definition of custom help templates for `App` via
|
||||
`CustomAppHelpTemplate`
|
||||
* Support for arbitrary key/value fields on `App` to be used with
|
||||
`CustomAppHelpTemplate` via `ExtraInfo`
|
||||
* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be
|
||||
`cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag`
|
||||
interface to be used.
|
||||
|
||||
|
||||
## [1.19.1] - 2016-11-21
|
||||
|
||||
### Fixed
|
||||
|
74
CODE_OF_CONDUCT.md
Normal file
74
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
education, socio-economic status, nationality, personal appearance, race,
|
||||
religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
|
||||
reviewed and investigated and will result in a response that is deemed necessary
|
||||
and appropriate to the circumstances. The project team is obligated to maintain
|
||||
confidentiality with regard to the reporter of an incident. Further details of
|
||||
specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
19
CONTRIBUTING.md
Normal file
19
CONTRIBUTING.md
Normal file
@ -0,0 +1,19 @@
|
||||
## Contributing
|
||||
|
||||
**NOTE**: the primary maintainer(s) may be found in
|
||||
[./MAINTAINERS.md](./MAINTAINERS.md).
|
||||
|
||||
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, 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, we probably forgot to add you :sweat_smile:. Please open an
|
||||
issue!
|
1
MAINTAINERS.md
Normal file
1
MAINTAINERS.md
Normal file
@ -0,0 +1 @@
|
||||
- @meatballhat
|
91
README.md
91
README.md
@ -9,9 +9,9 @@ cli
|
||||
[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) /
|
||||
[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc)
|
||||
|
||||
**Notice:** This is the library formerly known as
|
||||
`github.com/codegangsta/cli` -- Github will automatically redirect requests
|
||||
to this repository, but we recommend updating your references for clarity.
|
||||
This is the library formerly 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
|
||||
@ -32,7 +32,9 @@ applications in an expressive way.
|
||||
+ [Alternate Names](#alternate-names)
|
||||
+ [Ordering](#ordering)
|
||||
+ [Values from the Environment](#values-from-the-environment)
|
||||
+ [Values from files](#values-from-files)
|
||||
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
|
||||
+ [Precedence](#precedence)
|
||||
* [Subcommands](#subcommands)
|
||||
* [Subcommands categories](#subcommands-categories)
|
||||
* [Exit code](#exit-code)
|
||||
@ -45,6 +47,7 @@ applications in an expressive way.
|
||||
* [Version Flag](#version-flag)
|
||||
+ [Customization](#customization-2)
|
||||
+ [Full API Example](#full-api-example)
|
||||
* [Combining short Bool options](#combining-short-bool-options)
|
||||
- [Contribution Guidelines](#contribution-guidelines)
|
||||
|
||||
<!-- tocstop -->
|
||||
@ -586,6 +589,41 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Values from files
|
||||
|
||||
You can also have the default value set from file via `FilePath`. e.g.
|
||||
|
||||
<!-- {
|
||||
"args": ["--help"],
|
||||
"output": "password for the mysql database"
|
||||
} -->
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Flags = []cli.Flag {
|
||||
cli.StringFlag{
|
||||
Name: "password, p",
|
||||
Usage: "password for the mysql database",
|
||||
FilePath: "/etc/mysql/password",
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
```
|
||||
|
||||
Note that default values set from file (e.g. `FilePath`) take precedence over
|
||||
default values set from the enviornment (e.g. `EnvVar`).
|
||||
|
||||
#### Values from alternate input sources (YAML, TOML, and others)
|
||||
|
||||
There is a separate package altsrc that adds support for getting flag values
|
||||
@ -656,6 +694,15 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Precedence
|
||||
|
||||
The precedence for flag value sources is as follows (highest to lowest):
|
||||
|
||||
0. Command line flag value from user
|
||||
0. Environment variable (if specified)
|
||||
0. Configuration file (if specified)
|
||||
0. Default defined on the flag
|
||||
|
||||
### Subcommands
|
||||
|
||||
Subcommands can be defined for a more git-like command line app.
|
||||
@ -751,11 +798,11 @@ func main() {
|
||||
},
|
||||
{
|
||||
Name: "add",
|
||||
Category: "template",
|
||||
Category: "Template actions",
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Category: "template",
|
||||
Category: "Template actions",
|
||||
},
|
||||
}
|
||||
|
||||
@ -1364,18 +1411,26 @@ func wopAction(c *cli.Context) error {
|
||||
}
|
||||
```
|
||||
|
||||
### Combining short Bool options
|
||||
|
||||
Traditional use of boolean options using their shortnames look like this:
|
||||
```
|
||||
# cmd foobar -s -o
|
||||
```
|
||||
|
||||
Suppose you want users to be able to combine your bool options with their shortname. This
|
||||
can be done using the **UseShortOptionHandling** bool in your commands. Suppose your program
|
||||
has a two bool flags such as *serve* and *option* with the short options of *-o* and
|
||||
*-s* respectively. With **UseShortOptionHandling** set to *true*, a user can use a syntax
|
||||
like:
|
||||
```
|
||||
# cmd foobar -so
|
||||
```
|
||||
|
||||
If you enable the **UseShortOptionHandling*, then you must not use any flags that have a single
|
||||
leading *-* or this will result in failures. For example, **-option** can no longer be used. Flags
|
||||
with two leading dashes (such as **--options**) are still valid.
|
||||
|
||||
## 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.
|
||||
|
||||
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, we probably forgot to add you, please open an issue.
|
||||
See [./CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||
|
@ -22,15 +22,15 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool
|
||||
if sections := strings.Split(name, "."); len(sections) > 1 {
|
||||
node := tree
|
||||
for _, section := range sections[:len(sections)-1] {
|
||||
if child, ok := node[section]; !ok {
|
||||
child, ok := node[section]
|
||||
if !ok {
|
||||
return nil, false
|
||||
} else {
|
||||
if ctype, ok := child.(map[interface{}]interface{}); !ok {
|
||||
return nil, false
|
||||
} else {
|
||||
node = ctype
|
||||
}
|
||||
}
|
||||
ctype, ok := child.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
node = ctype
|
||||
}
|
||||
if val, ok := node[sections[len(sections)-1]]; ok {
|
||||
return val, true
|
||||
|
@ -66,9 +66,9 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *tomlMap) UnmarshalTOML(i interface{}) error {
|
||||
func (tm *tomlMap) UnmarshalTOML(i interface{}) error {
|
||||
if tmp, err := unmarshalMap(i); err == nil {
|
||||
self.Map = tmp
|
||||
tm.Map = tmp
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ func loadDataFrom(filePath string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
|
||||
}
|
||||
return ioutil.ReadFile(filePath)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
|
||||
}
|
||||
|
31
app.go
31
app.go
@ -83,6 +83,9 @@ type App struct {
|
||||
Writer io.Writer
|
||||
// ErrWriter writes error output
|
||||
ErrWriter io.Writer
|
||||
// Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to
|
||||
// function as a default, so this is optional.
|
||||
ExitErrHandler ExitErrHandlerFunc
|
||||
// Other custom info
|
||||
Metadata map[string]interface{}
|
||||
// Carries a function which returns app specific info.
|
||||
@ -207,7 +210,7 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err := a.OnUsageError(context, err, false)
|
||||
HandleExitCoder(err)
|
||||
a.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||
@ -240,8 +243,9 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
if a.Before != nil {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
|
||||
ShowAppHelp(context)
|
||||
HandleExitCoder(beforeErr)
|
||||
a.handleExitCoder(context, beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
@ -263,7 +267,7 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
// Run default Action
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
HandleExitCoder(err)
|
||||
a.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -330,7 +334,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err = a.OnUsageError(context, err, true)
|
||||
HandleExitCoder(err)
|
||||
a.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||
@ -352,7 +356,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
if afterErr != nil {
|
||||
HandleExitCoder(err)
|
||||
a.handleExitCoder(context, err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
@ -365,7 +369,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
if a.Before != nil {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
HandleExitCoder(beforeErr)
|
||||
a.handleExitCoder(context, beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
@ -383,7 +387,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
// Run default Action
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
HandleExitCoder(err)
|
||||
a.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -449,7 +453,6 @@ func (a *App) hasFlag(flag Flag) bool {
|
||||
}
|
||||
|
||||
func (a *App) errWriter() io.Writer {
|
||||
|
||||
// When the app ErrWriter is nil use the package level one.
|
||||
if a.ErrWriter == nil {
|
||||
return ErrWriter
|
||||
@ -464,6 +467,14 @@ func (a *App) appendFlag(flag Flag) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleExitCoder(context *Context, err error) {
|
||||
if a.ExitErrHandler != nil {
|
||||
a.ExitErrHandler(context, err)
|
||||
} else {
|
||||
HandleExitCoder(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Author represents someone who has contributed to a cli project.
|
||||
type Author struct {
|
||||
Name string // The Authors name
|
||||
@ -491,7 +502,7 @@ func HandleAction(action interface{}, context *Context) (err error) {
|
||||
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
|
||||
a(context)
|
||||
return nil
|
||||
} else {
|
||||
return errInvalidActionType
|
||||
}
|
||||
|
||||
return errInvalidActionType
|
||||
}
|
||||
|
72
app_test.go
72
app_test.go
@ -367,6 +367,39 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
|
||||
expect(t, firstArg, "my-arg")
|
||||
}
|
||||
|
||||
func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) {
|
||||
var parsedOption, parsedSecondOption, firstArg string
|
||||
var parsedBool, parsedSecondBool bool
|
||||
|
||||
app := NewApp()
|
||||
command := Command{
|
||||
Name: "cmd",
|
||||
Flags: []Flag{
|
||||
StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||
StringFlag{Name: "secondOption", Value: "", Usage: "another option"},
|
||||
BoolFlag{Name: "boolflag", Usage: "some bool"},
|
||||
BoolFlag{Name: "b", Usage: "another bool"},
|
||||
},
|
||||
Action: func(c *Context) error {
|
||||
parsedOption = c.String("option")
|
||||
parsedSecondOption = c.String("secondOption")
|
||||
parsedBool = c.Bool("boolflag")
|
||||
parsedSecondBool = c.Bool("b")
|
||||
firstArg = c.Args().First()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
app.Commands = []Command{command}
|
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"})
|
||||
|
||||
expect(t, parsedOption, "my-option")
|
||||
expect(t, parsedSecondOption, "fancy-option")
|
||||
expect(t, parsedBool, true)
|
||||
expect(t, parsedSecondBool, true)
|
||||
expect(t, firstArg, "my-arg")
|
||||
}
|
||||
|
||||
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||
var context *Context
|
||||
|
||||
@ -535,7 +568,6 @@ func TestApp_Float64Flag(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApp_ParseSliceFlags(t *testing.T) {
|
||||
var parsedOption, firstArg string
|
||||
var parsedIntSlice []int
|
||||
var parsedStringSlice []string
|
||||
|
||||
@ -549,8 +581,6 @@ func TestApp_ParseSliceFlags(t *testing.T) {
|
||||
Action: func(c *Context) error {
|
||||
parsedIntSlice = c.IntSlice("p")
|
||||
parsedStringSlice = c.StringSlice("ip")
|
||||
parsedOption = c.String("option")
|
||||
firstArg = c.Args().First()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@ -1699,6 +1729,42 @@ func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleExitCoder_Default(t *testing.T) {
|
||||
app := NewApp()
|
||||
fs, err := flagSet(app.Name, app.Flags)
|
||||
if err != nil {
|
||||
t.Errorf("error creating FlagSet: %s", err)
|
||||
}
|
||||
|
||||
ctx := NewContext(app, fs, nil)
|
||||
app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42))
|
||||
|
||||
output := fakeErrWriter.String()
|
||||
if !strings.Contains(output, "Default") {
|
||||
t.Fatalf("Expected Default Behavior from Error Handler but got: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleExitCoder_Custom(t *testing.T) {
|
||||
app := NewApp()
|
||||
fs, err := flagSet(app.Name, app.Flags)
|
||||
if err != nil {
|
||||
t.Errorf("error creating FlagSet: %s", err)
|
||||
}
|
||||
|
||||
app.ExitErrHandler = func(_ *Context, _ error) {
|
||||
fmt.Fprintln(ErrWriter, "I'm a Custom error handler, I print what I want!")
|
||||
}
|
||||
|
||||
ctx := NewContext(app, fs, nil)
|
||||
app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42))
|
||||
|
||||
output := fakeErrWriter.String()
|
||||
if !strings.Contains(output, "Custom") {
|
||||
t.Fatalf("Expected Custom Behavior from Error Handler but got: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleAction_WithUnknownPanic(t *testing.T) {
|
||||
defer func() { refute(t, recover(), nil) }()
|
||||
|
||||
|
10
appveyor.yml
10
appveyor.yml
@ -1,14 +1,16 @@
|
||||
version: "{build}"
|
||||
|
||||
os: Windows Server 2012 R2
|
||||
os: Windows Server 2016
|
||||
|
||||
image: Visual Studio 2017
|
||||
|
||||
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
|
||||
GOVERSION: 1.8.x
|
||||
PYTHON: C:\Python36-x64
|
||||
PYTHON_VERSION: 3.6.x
|
||||
PYTHON_ARCH: 64
|
||||
|
||||
install:
|
||||
|
@ -10,7 +10,7 @@ type CommandCategory struct {
|
||||
}
|
||||
|
||||
func (c CommandCategories) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
return lexicographicLess(c[i].Name, c[j].Name)
|
||||
}
|
||||
|
||||
func (c CommandCategories) Len() int {
|
||||
|
144
command.go
144
command.go
@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
@ -55,6 +56,10 @@ type Command struct {
|
||||
HideHelp bool
|
||||
// Boolean to hide this command from help or completion
|
||||
Hidden bool
|
||||
// Boolean to enable short-option handling so user can combine several
|
||||
// single-character bool arguements into one
|
||||
// i.e. foobar -o -v -> foobar -ov
|
||||
UseShortOptionHandling bool
|
||||
|
||||
// Full name of command for help, defaults to full command name, including parent commands.
|
||||
HelpName string
|
||||
@ -73,7 +78,7 @@ func (c CommandsByName) Len() int {
|
||||
}
|
||||
|
||||
func (c CommandsByName) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
return lexicographicLess(c[i].Name, c[j].Name)
|
||||
}
|
||||
|
||||
func (c CommandsByName) Swap(i, j int) {
|
||||
@ -106,57 +111,7 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
)
|
||||
}
|
||||
|
||||
set, err := flagSet(c.Name, c.Flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
set.SetOutput(ioutil.Discard)
|
||||
|
||||
if c.SkipFlagParsing {
|
||||
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
||||
} else if !c.SkipArgReorder {
|
||||
firstFlagIndex := -1
|
||||
terminatorIndex := -1
|
||||
for index, arg := range ctx.Args() {
|
||||
if arg == "--" {
|
||||
terminatorIndex = index
|
||||
break
|
||||
} else if arg == "-" {
|
||||
// Do nothing. A dash alone is not really a flag.
|
||||
continue
|
||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||
firstFlagIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
if firstFlagIndex > -1 {
|
||||
args := ctx.Args()
|
||||
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
||||
copy(regularArgs, args[1:firstFlagIndex])
|
||||
|
||||
var flagArgs []string
|
||||
if terminatorIndex > -1 {
|
||||
flagArgs = args[firstFlagIndex:terminatorIndex]
|
||||
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
||||
} else {
|
||||
flagArgs = args[firstFlagIndex:]
|
||||
}
|
||||
|
||||
err = set.Parse(append(flagArgs, regularArgs...))
|
||||
} else {
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
}
|
||||
} else {
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
}
|
||||
|
||||
nerr := normalizeFlags(c.Flags, set)
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, nerr)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return nerr
|
||||
}
|
||||
set, err := c.parseFlags(ctx.Args().Tail())
|
||||
|
||||
context := NewContext(ctx.App, set, ctx)
|
||||
context.Command = c
|
||||
@ -167,7 +122,7 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
if err != nil {
|
||||
if c.OnUsageError != nil {
|
||||
err := c.OnUsageError(context, err, false)
|
||||
HandleExitCoder(err)
|
||||
context.App.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
||||
@ -184,7 +139,7 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
defer func() {
|
||||
afterErr := c.After(context)
|
||||
if afterErr != nil {
|
||||
HandleExitCoder(err)
|
||||
context.App.handleExitCoder(context, err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
@ -198,7 +153,7 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
err = c.Before(context)
|
||||
if err != nil {
|
||||
ShowCommandHelp(context, c.Name)
|
||||
HandleExitCoder(err)
|
||||
context.App.handleExitCoder(context, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -210,11 +165,88 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
err = HandleAction(c.Action, context)
|
||||
|
||||
if err != nil {
|
||||
HandleExitCoder(err)
|
||||
context.App.handleExitCoder(context, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
|
||||
set, err := flagSet(c.Name, c.Flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set.SetOutput(ioutil.Discard)
|
||||
|
||||
if c.SkipFlagParsing {
|
||||
return set, set.Parse(append([]string{"--"}, args...))
|
||||
}
|
||||
|
||||
if c.UseShortOptionHandling {
|
||||
args = translateShortOptions(args)
|
||||
}
|
||||
|
||||
if !c.SkipArgReorder {
|
||||
args = reorderArgs(args)
|
||||
}
|
||||
|
||||
err = set.Parse(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = normalizeFlags(c.Flags, set)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// reorderArgs moves all flags before arguments as this is what flag expects
|
||||
func reorderArgs(args []string) []string {
|
||||
var nonflags, flags []string
|
||||
|
||||
readFlagValue := false
|
||||
for i, arg := range args {
|
||||
if arg == "--" {
|
||||
nonflags = append(nonflags, args[i:]...)
|
||||
break
|
||||
}
|
||||
|
||||
if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") {
|
||||
readFlagValue = false
|
||||
flags = append(flags, arg)
|
||||
continue
|
||||
}
|
||||
readFlagValue = false
|
||||
|
||||
if arg != "-" && strings.HasPrefix(arg, "-") {
|
||||
flags = append(flags, arg)
|
||||
|
||||
readFlagValue = !strings.Contains(arg, "=")
|
||||
} else {
|
||||
nonflags = append(nonflags, arg)
|
||||
}
|
||||
}
|
||||
|
||||
return append(flags, nonflags...)
|
||||
}
|
||||
|
||||
func translateShortOptions(flagArgs Args) []string {
|
||||
// separate combined flags
|
||||
var flagArgsSeparated []string
|
||||
for _, flagArg := range flagArgs {
|
||||
if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 {
|
||||
for _, flagChar := range flagArg[1:] {
|
||||
flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar))
|
||||
}
|
||||
} else {
|
||||
flagArgsSeparated = append(flagArgsSeparated, flagArg)
|
||||
}
|
||||
}
|
||||
return flagArgsSeparated
|
||||
}
|
||||
|
||||
// Names returns the names including short names and aliases.
|
||||
func (c Command) Names() []string {
|
||||
names := []string{c.Name}
|
||||
|
111
command_test.go
111
command_test.go
@ -11,20 +11,24 @@ import (
|
||||
|
||||
func TestCommandFlagParsing(t *testing.T) {
|
||||
cases := []struct {
|
||||
testArgs []string
|
||||
skipFlagParsing bool
|
||||
skipArgReorder bool
|
||||
expectedErr error
|
||||
testArgs []string
|
||||
skipFlagParsing bool
|
||||
skipArgReorder bool
|
||||
expectedErr error
|
||||
UseShortOptionHandling bool
|
||||
}{
|
||||
// Test normal "not ignoring flags" flow
|
||||
{[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break")},
|
||||
{[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break"), false},
|
||||
|
||||
// Test no arg reorder
|
||||
{[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil},
|
||||
{[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false},
|
||||
{[]string{"test-cmd", "blah", "blah", "-break", "ls", "-l"}, false, true, nil, true},
|
||||
|
||||
{[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags
|
||||
{[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg
|
||||
{[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg
|
||||
{[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling
|
||||
|
||||
{[]string{"test-cmd", "blah", "blah"}, true, false, nil}, // Test SkipFlagParsing without any args that look like flags
|
||||
{[]string{"test-cmd", "blah", "-break"}, true, false, nil}, // Test SkipFlagParsing with random flag arg
|
||||
{[]string{"test-cmd", "blah", "-help"}, true, false, nil}, // Test SkipFlagParsing with "special" help flag arg
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@ -36,13 +40,14 @@ func TestCommandFlagParsing(t *testing.T) {
|
||||
context := NewContext(app, set, nil)
|
||||
|
||||
command := Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *Context) error { return nil },
|
||||
SkipFlagParsing: c.skipFlagParsing,
|
||||
SkipArgReorder: c.skipArgReorder,
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *Context) error { return nil },
|
||||
SkipFlagParsing: c.skipFlagParsing,
|
||||
SkipArgReorder: c.skipArgReorder,
|
||||
UseShortOptionHandling: c.UseShortOptionHandling,
|
||||
}
|
||||
|
||||
err := command.Run(context)
|
||||
@ -238,3 +243,77 @@ func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagReordering(t *testing.T) {
|
||||
cases := []struct {
|
||||
testArgs []string
|
||||
expectedValue string
|
||||
expectedArgs []string
|
||||
expectedErr error
|
||||
}{
|
||||
{[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, "foo", []string{"some-arg"}, nil},
|
||||
{[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, "foo", []string{"some-arg"}, nil},
|
||||
{[]string{"some-exec", "some-command", "--flag=foo", "some-arg"}, "foo", []string{"some-arg"}, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
value := ""
|
||||
args := []string{}
|
||||
app := &App{
|
||||
Commands: []Command{
|
||||
{
|
||||
Name: "some-command",
|
||||
Flags: []Flag{
|
||||
StringFlag{Name: "flag"},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
fmt.Printf("%+v\n", c.String("flag"))
|
||||
value = c.String("flag")
|
||||
args = c.Args()
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run(c.testArgs)
|
||||
expect(t, err, c.expectedErr)
|
||||
expect(t, value, c.expectedValue)
|
||||
expect(t, args, c.expectedArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSkipFlagParsing(t *testing.T) {
|
||||
cases := []struct {
|
||||
testArgs []string
|
||||
expectedArgs []string
|
||||
expectedErr error
|
||||
}{
|
||||
{[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, []string{"some-arg", "--flag", "foo"}, nil},
|
||||
{[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, []string{"some-arg", "--flag=foo"}, nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
value := ""
|
||||
args := []string{}
|
||||
app := &App{
|
||||
Commands: []Command{
|
||||
{
|
||||
SkipFlagParsing: true,
|
||||
Name: "some-command",
|
||||
Flags: []Flag{
|
||||
StringFlag{Name: "flag"},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
fmt.Printf("%+v\n", c.String("flag"))
|
||||
value = c.String("flag")
|
||||
args = c.Args()
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run(c.testArgs)
|
||||
expect(t, err, c.expectedErr)
|
||||
expect(t, args, c.expectedArgs)
|
||||
}
|
||||
}
|
||||
|
31
context.go
31
context.go
@ -3,6 +3,7 @@ package cli
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -73,7 +74,7 @@ func (c *Context) IsSet(name string) bool {
|
||||
// change in version 2 to add `IsSet` to the Flag interface to push the
|
||||
// responsibility closer to where the information required to determine
|
||||
// whether a flag is set by non-standard means such as environment
|
||||
// variables is avaliable.
|
||||
// variables is available.
|
||||
//
|
||||
// See https://github.com/urfave/cli/issues/294 for additional discussion
|
||||
flags := c.Command.Flags
|
||||
@ -93,18 +94,26 @@ func (c *Context) IsSet(name string) bool {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
envVarValue := val.FieldByName("EnvVar")
|
||||
if !envVarValue.IsValid() {
|
||||
return
|
||||
filePathValue := val.FieldByName("FilePath")
|
||||
if filePathValue.IsValid() {
|
||||
eachName(filePathValue.String(), func(filePath string) {
|
||||
if _, err := os.Stat(filePath); err == nil {
|
||||
c.setFlags[name] = true
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
eachName(envVarValue.String(), func(envVar string) {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if _, ok := syscall.Getenv(envVar); ok {
|
||||
c.setFlags[name] = true
|
||||
return
|
||||
}
|
||||
})
|
||||
envVarValue := val.FieldByName("EnvVar")
|
||||
if envVarValue.IsValid() {
|
||||
eachName(envVarValue.String(), func(envVar string) {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if _, ok := syscall.Getenv(envVar); ok {
|
||||
c.setFlags[name] = true
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
335
flag.go
335
flag.go
@ -3,6 +3,7 @@ package cli
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@ -37,6 +38,18 @@ var HelpFlag Flag = BoolFlag{
|
||||
// to display a flag.
|
||||
var FlagStringer FlagStringFunc = stringifyFlag
|
||||
|
||||
// FlagNamePrefixer converts a full flag name and its placeholder into the help
|
||||
// message flag prefix. This is used by the default FlagStringer.
|
||||
var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
|
||||
|
||||
// FlagEnvHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagEnvHinter FlagEnvHintFunc = withEnvHint
|
||||
|
||||
// FlagFileHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagFileHinter FlagFileHintFunc = withFileHint
|
||||
|
||||
// FlagsByName is a slice of Flag.
|
||||
type FlagsByName []Flag
|
||||
|
||||
@ -45,7 +58,7 @@ func (f FlagsByName) Len() int {
|
||||
}
|
||||
|
||||
func (f FlagsByName) Less(i, j int) bool {
|
||||
return f[i].GetName() < f[j].GetName()
|
||||
return lexicographicLess(f[i].GetName(), f[j].GetName())
|
||||
}
|
||||
|
||||
func (f FlagsByName) Swap(i, j int) {
|
||||
@ -112,15 +125,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||
// provided by the user for parsing by the flag
|
||||
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
val := f.Value
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
if err := val.Set(envVal); err != nil {
|
||||
return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
if err := val.Set(fileEnvVal); err != nil {
|
||||
return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,21 +170,19 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
newVal := &StringSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
break
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
newVal := &StringSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
if f.Value == nil {
|
||||
f.Value = newVal
|
||||
} else {
|
||||
*f.Value = *newVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -226,21 +231,19 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
newVal := &IntSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
break
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
newVal := &IntSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
if f.Value == nil {
|
||||
f.Value = newVal
|
||||
} else {
|
||||
*f.Value = *newVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -289,21 +292,19 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
newVal := &Int64Slice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
break
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
newVal := &Int64Slice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if err := newVal.Set(s); err != nil {
|
||||
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
}
|
||||
if f.Value == nil {
|
||||
f.Value = newVal
|
||||
} else {
|
||||
*f.Value = *newVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -324,23 +325,15 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
val := false
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
if envVal == "" {
|
||||
val = false
|
||||
break
|
||||
}
|
||||
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
val = envValBool
|
||||
break
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
if envVal == "" {
|
||||
val = false
|
||||
} else {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
val = envValBool
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,23 +357,16 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
val := true
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
if envVal == "" {
|
||||
val = false
|
||||
break
|
||||
}
|
||||
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
val = envValBool
|
||||
break
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
if envVal == "" {
|
||||
val = false
|
||||
} else {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
val = envValBool
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,14 +389,8 @@ func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
f.Value = envVal
|
||||
break
|
||||
}
|
||||
}
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
f.Value = envVal
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -432,18 +412,12 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
f.Value = int(envValInt)
|
||||
break
|
||||
}
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
f.Value = int(envValInt)
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -465,19 +439,13 @@ func (f Int64Flag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValInt
|
||||
break
|
||||
}
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValInt
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -499,19 +467,13 @@ func (f UintFlag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = uint(envValInt)
|
||||
break
|
||||
}
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = uint(envValInt)
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -533,19 +495,13 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = uint64(envValInt)
|
||||
break
|
||||
}
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = uint64(envValInt)
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -567,19 +523,13 @@ func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValDuration, err := time.ParseDuration(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValDuration
|
||||
break
|
||||
}
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValDuration, err := time.ParseDuration(envVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = envValDuration
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -601,19 +551,13 @@ func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
||||
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = float64(envValFloat)
|
||||
break
|
||||
}
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
|
||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = float64(envValFloat)
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
@ -692,11 +636,19 @@ func withEnvHint(envVar, str string) string {
|
||||
suffix = "%"
|
||||
sep = "%, %"
|
||||
}
|
||||
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
|
||||
envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]"
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
|
||||
func withFileHint(filePath, str string) string {
|
||||
fileText := ""
|
||||
if filePath != "" {
|
||||
fileText = fmt.Sprintf(" [%s]", filePath)
|
||||
}
|
||||
return str + fileText
|
||||
}
|
||||
|
||||
func flagValue(f Flag) reflect.Value {
|
||||
fv := reflect.ValueOf(f)
|
||||
for fv.Kind() == reflect.Ptr {
|
||||
@ -710,14 +662,29 @@ func stringifyFlag(f Flag) string {
|
||||
|
||||
switch f.(type) {
|
||||
case IntSliceFlag:
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
stringifyIntSliceFlag(f.(IntSliceFlag)))
|
||||
return FlagFileHinter(
|
||||
fv.FieldByName("FilePath").String(),
|
||||
FlagEnvHinter(
|
||||
fv.FieldByName("EnvVar").String(),
|
||||
stringifyIntSliceFlag(f.(IntSliceFlag)),
|
||||
),
|
||||
)
|
||||
case Int64SliceFlag:
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
stringifyInt64SliceFlag(f.(Int64SliceFlag)))
|
||||
return FlagFileHinter(
|
||||
fv.FieldByName("FilePath").String(),
|
||||
FlagEnvHinter(
|
||||
fv.FieldByName("EnvVar").String(),
|
||||
stringifyInt64SliceFlag(f.(Int64SliceFlag)),
|
||||
),
|
||||
)
|
||||
case StringSliceFlag:
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
stringifyStringSliceFlag(f.(StringSliceFlag)))
|
||||
return FlagFileHinter(
|
||||
fv.FieldByName("FilePath").String(),
|
||||
FlagEnvHinter(
|
||||
fv.FieldByName("EnvVar").String(),
|
||||
stringifyStringSliceFlag(f.(StringSliceFlag)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
||||
@ -742,17 +709,22 @@ func stringifyFlag(f Flag) string {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
|
||||
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
|
||||
return FlagFileHinter(
|
||||
fv.FieldByName("FilePath").String(),
|
||||
FlagEnvHinter(
|
||||
fv.FieldByName("EnvVar").String(),
|
||||
FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func stringifyIntSliceFlag(f IntSliceFlag) string {
|
||||
defaultVals := []string{}
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, i := range f.Value.Value() {
|
||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
||||
defaultVals = append(defaultVals, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
@ -763,7 +735,7 @@ func stringifyInt64SliceFlag(f Int64SliceFlag) string {
|
||||
defaultVals := []string{}
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, i := range f.Value.Value() {
|
||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
||||
defaultVals = append(defaultVals, strconv.FormatInt(i, 10))
|
||||
}
|
||||
}
|
||||
|
||||
@ -775,7 +747,7 @@ func stringifyStringSliceFlag(f StringSliceFlag) string {
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, s := range f.Value.Value() {
|
||||
if len(s) > 0 {
|
||||
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
|
||||
defaultVals = append(defaultVals, strconv.Quote(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -794,6 +766,21 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string {
|
||||
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
|
||||
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultVal)
|
||||
return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault
|
||||
}
|
||||
|
||||
func flagFromFileEnv(filePath, envName string) (val string, ok bool) {
|
||||
for _, envVar := range strings.Split(envName, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal, ok := syscall.Getenv(envVar); ok {
|
||||
return envVal, true
|
||||
}
|
||||
}
|
||||
for _, fileVar := range strings.Split(filePath, ",") {
|
||||
if data, err := ioutil.ReadFile(fileVar); err == nil {
|
||||
return string(data), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ type BoolFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Destination *bool
|
||||
}
|
||||
@ -60,6 +61,7 @@ type BoolTFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Destination *bool
|
||||
}
|
||||
@ -107,6 +109,7 @@ type DurationFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value time.Duration
|
||||
Destination *time.Duration
|
||||
@ -155,6 +158,7 @@ type Float64Flag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value float64
|
||||
Destination *float64
|
||||
@ -200,11 +204,12 @@ func lookupFloat64(name string, set *flag.FlagSet) float64 {
|
||||
|
||||
// GenericFlag is a flag with type Generic
|
||||
type GenericFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
Value Generic
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value Generic
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
@ -250,6 +255,7 @@ type Int64Flag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value int64
|
||||
Destination *int64
|
||||
@ -298,6 +304,7 @@ type IntFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value int
|
||||
Destination *int
|
||||
@ -343,11 +350,12 @@ func lookupInt(name string, set *flag.FlagSet) int {
|
||||
|
||||
// IntSliceFlag is a flag with type *IntSlice
|
||||
type IntSliceFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
Value *IntSlice
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value *IntSlice
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
@ -390,11 +398,12 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
||||
|
||||
// Int64SliceFlag is a flag with type *Int64Slice
|
||||
type Int64SliceFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
Value *Int64Slice
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value *Int64Slice
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
@ -440,6 +449,7 @@ type StringFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value string
|
||||
Destination *string
|
||||
@ -485,11 +495,12 @@ func lookupString(name string, set *flag.FlagSet) string {
|
||||
|
||||
// StringSliceFlag is a flag with type *StringSlice
|
||||
type StringSliceFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
Value *StringSlice
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value *StringSlice
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
@ -535,6 +546,7 @@ type Uint64Flag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value uint64
|
||||
Destination *uint64
|
||||
@ -583,6 +595,7 @@ type UintFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
Value uint
|
||||
Destination *uint
|
||||
|
139
flag_test.go
139
flag_test.go
@ -2,6 +2,8 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@ -158,6 +160,83 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var prefixStringFlagTests = []struct {
|
||||
name string
|
||||
usage string
|
||||
value string
|
||||
prefixer FlagNamePrefixFunc
|
||||
expected string
|
||||
}{
|
||||
{"foo", "", "", func(a, b string) string {
|
||||
return fmt.Sprintf("name: %s, ph: %s", a, b)
|
||||
}, "name: foo, ph: value\t"},
|
||||
{"f", "", "", func(a, b string) string {
|
||||
return fmt.Sprintf("name: %s, ph: %s", a, b)
|
||||
}, "name: f, ph: value\t"},
|
||||
{"f", "The total `foo` desired", "all", func(a, b string) string {
|
||||
return fmt.Sprintf("name: %s, ph: %s", a, b)
|
||||
}, "name: f, ph: foo\tThe total foo desired (default: \"all\")"},
|
||||
{"test", "", "Something", func(a, b string) string {
|
||||
return fmt.Sprintf("name: %s, ph: %s", a, b)
|
||||
}, "name: test, ph: value\t(default: \"Something\")"},
|
||||
{"config,c", "Load configuration from `FILE`", "", func(a, b string) string {
|
||||
return fmt.Sprintf("name: %s, ph: %s", a, b)
|
||||
}, "name: config,c, ph: FILE\tLoad configuration from FILE"},
|
||||
{"config,c", "Load configuration from `CONFIG`", "config.json", func(a, b string) string {
|
||||
return fmt.Sprintf("name: %s, ph: %s", a, b)
|
||||
}, "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"},
|
||||
}
|
||||
|
||||
func TestFlagNamePrefixer(t *testing.T) {
|
||||
defer func() {
|
||||
FlagNamePrefixer = prefixedNames
|
||||
}()
|
||||
|
||||
for _, test := range prefixStringFlagTests {
|
||||
FlagNamePrefixer = test.prefixer
|
||||
flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value}
|
||||
output := flag.String()
|
||||
if output != test.expected {
|
||||
t.Errorf("%q does not match %q", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var envHintFlagTests = []struct {
|
||||
name string
|
||||
env string
|
||||
hinter FlagEnvHintFunc
|
||||
expected string
|
||||
}{
|
||||
{"foo", "", func(a, b string) string {
|
||||
return fmt.Sprintf("env: %s, str: %s", a, b)
|
||||
}, "env: , str: --foo value\t"},
|
||||
{"f", "", func(a, b string) string {
|
||||
return fmt.Sprintf("env: %s, str: %s", a, b)
|
||||
}, "env: , str: -f value\t"},
|
||||
{"foo", "ENV_VAR", func(a, b string) string {
|
||||
return fmt.Sprintf("env: %s, str: %s", a, b)
|
||||
}, "env: ENV_VAR, str: --foo value\t"},
|
||||
{"f", "ENV_VAR", func(a, b string) string {
|
||||
return fmt.Sprintf("env: %s, str: %s", a, b)
|
||||
}, "env: ENV_VAR, str: -f value\t"},
|
||||
}
|
||||
|
||||
func TestFlagEnvHinter(t *testing.T) {
|
||||
defer func() {
|
||||
FlagEnvHinter = withEnvHint
|
||||
}()
|
||||
|
||||
for _, test := range envHintFlagTests {
|
||||
FlagEnvHinter = test.hinter
|
||||
flag := StringFlag{Name: test.name, EnvVar: test.env}
|
||||
output := flag.String()
|
||||
if output != test.expected {
|
||||
t.Errorf("%q does not match %q", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stringSliceFlagTests = []struct {
|
||||
name string
|
||||
value *StringSlice
|
||||
@ -969,6 +1048,31 @@ func TestParseMultiBool(t *testing.T) {
|
||||
a.Run([]string{"run", "--serve"})
|
||||
}
|
||||
|
||||
func TestParseBoolShortOptionHandle(t *testing.T) {
|
||||
a := App{
|
||||
Commands: []Command{
|
||||
{
|
||||
Name: "foobar",
|
||||
UseShortOptionHandling: true,
|
||||
Action: func(ctx *Context) error {
|
||||
if ctx.Bool("serve") != true {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.Bool("option") != true {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Flags: []Flag{
|
||||
BoolFlag{Name: "serve, s"},
|
||||
BoolFlag{Name: "option, o"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run", "foobar", "-so"})
|
||||
}
|
||||
|
||||
func TestParseDestinationBool(t *testing.T) {
|
||||
var dest bool
|
||||
a := App{
|
||||
@ -1213,3 +1317,38 @@ func TestParseGenericFromEnvCascade(t *testing.T) {
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestFlagFromFile(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_FOO", "123")
|
||||
|
||||
temp, err := ioutil.TempFile("", "urfave_cli_test")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
io.WriteString(temp, "abc")
|
||||
temp.Close()
|
||||
defer func() {
|
||||
os.Remove(temp.Name())
|
||||
}()
|
||||
|
||||
var filePathTests = []struct {
|
||||
path string
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{"file-does-not-exist", "APP_BAR", ""},
|
||||
{"file-does-not-exist", "APP_FOO", "123"},
|
||||
{"file-does-not-exist", "APP_FOO,APP_BAR", "123"},
|
||||
{temp.Name(), "APP_FOO", "123"},
|
||||
{temp.Name(), "APP_BAR", "abc"},
|
||||
}
|
||||
|
||||
for _, filePathTest := range filePathTests {
|
||||
got, _ := flagFromFileEnv(filePathTest.path, filePathTest.name)
|
||||
if want := filePathTest.expected; got != want {
|
||||
t.Errorf("Did not expect %v - Want %v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
16
funcs.go
16
funcs.go
@ -23,6 +23,22 @@ type CommandNotFoundFunc func(*Context, string)
|
||||
// is displayed and the execution is interrupted.
|
||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
||||
|
||||
// ExitErrHandlerFunc is executed if provided in order to handle ExitError values
|
||||
// returned by Actions and Before/After functions.
|
||||
type ExitErrHandlerFunc func(context *Context, err error)
|
||||
|
||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
||||
// expected to be a single line.
|
||||
type FlagStringFunc func(Flag) string
|
||||
|
||||
// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
|
||||
// text for a flag's full name.
|
||||
type FlagNamePrefixFunc func(fullName, placeholder string) string
|
||||
|
||||
// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
|
||||
// with the environment variable details.
|
||||
type FlagEnvHintFunc func(envVar, str string) string
|
||||
|
||||
// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
|
||||
// with the file path details.
|
||||
type FlagFileHintFunc func(filePath, str string) string
|
||||
|
@ -142,6 +142,7 @@ def _write_cli_flag_types(outfile, types):
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
FilePath string
|
||||
Hidden bool
|
||||
""".format(**typedef))
|
||||
|
||||
|
1
help.go
1
help.go
@ -29,6 +29,7 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
|
||||
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
|
||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
|
29
sort.go
Normal file
29
sort.go
Normal file
@ -0,0 +1,29 @@
|
||||
package cli
|
||||
|
||||
import "unicode"
|
||||
|
||||
// lexicographicLess compares strings alphabetically considering case.
|
||||
func lexicographicLess(i, j string) bool {
|
||||
iRunes := []rune(i)
|
||||
jRunes := []rune(j)
|
||||
|
||||
lenShared := len(iRunes)
|
||||
if lenShared > len(jRunes) {
|
||||
lenShared = len(jRunes)
|
||||
}
|
||||
|
||||
for index := 0; index < lenShared; index++ {
|
||||
ir := iRunes[index]
|
||||
jr := jRunes[index]
|
||||
|
||||
if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr {
|
||||
return lir < ljr
|
||||
}
|
||||
|
||||
if ir != jr {
|
||||
return ir < jr
|
||||
}
|
||||
}
|
||||
|
||||
return i < j
|
||||
}
|
30
sort_test.go
Normal file
30
sort_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package cli
|
||||
|
||||
import "testing"
|
||||
|
||||
var lexicographicLessTests = []struct {
|
||||
i string
|
||||
j string
|
||||
expected bool
|
||||
}{
|
||||
{"", "a", true},
|
||||
{"a", "", false},
|
||||
{"a", "a", false},
|
||||
{"a", "A", false},
|
||||
{"A", "a", true},
|
||||
{"aa", "a", false},
|
||||
{"a", "aa", true},
|
||||
{"a", "b", true},
|
||||
{"a", "B", true},
|
||||
{"A", "b", true},
|
||||
{"A", "B", true},
|
||||
}
|
||||
|
||||
func TestLexicographicLess(t *testing.T) {
|
||||
for _, test := range lexicographicLessTests {
|
||||
actual := lexicographicLess(test.i, test.j)
|
||||
if test.expected != actual {
|
||||
t.Errorf(`expected string "%s" to come before "%s"`, test.i, test.j)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user