Merge remote-tracking branch 'origin/master' into string-slice-flag-default-160
This commit is contained in:
commit
a1e5328e30
49
CHANGELOG.md
49
CHANGELOG.md
@ -2,14 +2,47 @@
|
|||||||
|
|
||||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
||||||
|
|
||||||
## [Unreleased]
|
## 2.0.0 - (unreleased 2.x series)
|
||||||
|
### Added
|
||||||
|
- `NewStringSlice` and `NewIntSlice` for creating their related types
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`.
|
||||||
|
To migrate to the new API, you may choose to run [this helper
|
||||||
|
(python) script](./cli-migrate-slice-types).
|
||||||
|
|
||||||
|
## [Unreleased] - (1.x series)
|
||||||
|
### Added
|
||||||
|
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
|
||||||
|
quoted in help text output.
|
||||||
|
- All flag types now include `(default: {value})` strings following usage when a
|
||||||
|
default value can be (reasonably) detected.
|
||||||
|
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
|
||||||
|
with non-slice flag types
|
||||||
|
|
||||||
|
## [1.16.0] - 2016-05-02
|
||||||
|
### Added
|
||||||
|
- `Hidden` field on all flag struct types to omit from generated help text
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
|
||||||
|
generated help text via the `Hidden` field
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- handling of error values in `HandleAction` and `HandleExitCoder`
|
||||||
|
|
||||||
|
## [1.15.0] - 2016-04-30
|
||||||
### Added
|
### Added
|
||||||
- This file!
|
- This file!
|
||||||
- Support for placeholders in flag usage strings
|
- Support for placeholders in flag usage strings
|
||||||
- `App.Metadata` map for arbitrary data/state management
|
- `App.Metadata` map for arbitrary data/state management
|
||||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
|
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
|
||||||
parsing.
|
parsing.
|
||||||
|
- Support for nested lookup of dot-delimited keys in structures loaded from
|
||||||
|
YAML.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- The `App.Action` and `Command.Action` now prefer a return signature of
|
- The `App.Action` and `Command.Action` now prefer a return signature of
|
||||||
@ -36,15 +69,6 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Added missing `*cli.Context.GlobalFloat64` method
|
- Added missing `*cli.Context.GlobalFloat64` method
|
||||||
|
|
||||||
## [2.0.0]
|
|
||||||
### Added
|
|
||||||
- `NewStringSlice` and `NewIntSlice` for creating their related types
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
- the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`.
|
|
||||||
To migrate to the new API, you may choose to run [this helper
|
|
||||||
(python) script](./cli-migrate-slice-types).
|
|
||||||
|
|
||||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
|
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
|
||||||
### Added
|
### Added
|
||||||
- Codebeat badge
|
- Codebeat badge
|
||||||
@ -257,8 +281,9 @@ To migrate to the new API, you may choose to run [this helper
|
|||||||
### Added
|
### Added
|
||||||
- Initial implementation.
|
- Initial implementation.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/codegangsta/cli/compare/v1.14.0...HEAD
|
[Unreleased]: https://github.com/codegangsta/cli/compare/v1.16.0...HEAD
|
||||||
[2.0.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v2.0.0
|
[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0
|
||||||
|
[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0
|
||||||
[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0
|
[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0
|
||||||
[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0
|
[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0
|
||||||
[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0
|
[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0
|
||||||
|
91
README.md
91
README.md
@ -3,21 +3,21 @@
|
|||||||
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli)
|
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli)
|
||||||
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli)
|
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli)
|
||||||
|
|
||||||
# cli.go
|
# cli
|
||||||
|
|
||||||
`cli.go` is 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
|
||||||
|
|
||||||
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
|
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
|
||||||
|
|
||||||
**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
|
**This is where cli comes into play.** cli makes command line programming fun, organized, and expressive!
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
|
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
|
||||||
|
|
||||||
To install `cli.go`, simply run:
|
To install cli, simply run:
|
||||||
```
|
```
|
||||||
$ go get github.com/codegangsta/cli
|
$ go get github.com/codegangsta/cli
|
||||||
```
|
```
|
||||||
@ -29,7 +29,7 @@ export PATH=$PATH:$GOPATH/bin
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
|
One of the philosophies behind cli is that an API should be playful and full of discovery. So a cli app can be as little as one line of code in `main()`.
|
||||||
|
|
||||||
``` go
|
``` go
|
||||||
package main
|
package main
|
||||||
@ -113,7 +113,7 @@ $ greet
|
|||||||
Hello friend!
|
Hello friend!
|
||||||
```
|
```
|
||||||
|
|
||||||
`cli.go` also generates neat help text:
|
cli also generates neat help text:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ greet help
|
$ greet help
|
||||||
@ -302,6 +302,7 @@ Here is a more complete sample of a command using YAML support:
|
|||||||
Description: "testing",
|
Description: "testing",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
// Action to run
|
// Action to run
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
NewIntFlag(cli.IntFlag{Name: "test"}),
|
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||||
@ -322,16 +323,18 @@ app.Commands = []cli.Command{
|
|||||||
Name: "add",
|
Name: "add",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "add a task to the list",
|
Usage: "add a task to the list",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) error {
|
||||||
fmt.Println("added task: ", c.Args().First())
|
fmt.Println("added task: ", c.Args().First())
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "complete",
|
Name: "complete",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Usage: "complete a task on the list",
|
Usage: "complete a task on the list",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) error {
|
||||||
fmt.Println("completed task: ", c.Args().First())
|
fmt.Println("completed task: ", c.Args().First())
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -342,15 +345,17 @@ app.Commands = []cli.Command{
|
|||||||
{
|
{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Usage: "add a new template",
|
Usage: "add a new template",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) error {
|
||||||
fmt.Println("new task template: ", c.Args().First())
|
fmt.Println("new task template: ", c.Args().First())
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "remove",
|
Name: "remove",
|
||||||
Usage: "remove an existing template",
|
Usage: "remove an existing template",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) error {
|
||||||
fmt.Println("removed task template: ", c.Args().First())
|
fmt.Println("removed task template: ", c.Args().First())
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -450,8 +455,9 @@ app.Commands = []cli.Command{
|
|||||||
Name: "complete",
|
Name: "complete",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Usage: "complete a task on the list",
|
Usage: "complete a task on the list",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) error {
|
||||||
fmt.Println("completed task: ", c.Args().First())
|
fmt.Println("completed task: ", c.Args().First())
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
BashComplete: func(c *cli.Context) {
|
BashComplete: func(c *cli.Context) {
|
||||||
// This will complete if no args are passed
|
// This will complete if no args are passed
|
||||||
@ -490,6 +496,69 @@ Alternatively, you can just document that users should source the generic
|
|||||||
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
|
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
|
||||||
to the name of their program (as above).
|
to the name of their program (as above).
|
||||||
|
|
||||||
|
### Generated Help Text Customization
|
||||||
|
|
||||||
|
All of the help text generation may be customized, and at multiple levels. The
|
||||||
|
templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and
|
||||||
|
`SubcommandHelpTemplate` which may be reassigned or augmented, and full override
|
||||||
|
is possible by assigning a compatible func to the `cli.HelpPrinter` variable,
|
||||||
|
e.g.:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// EXAMPLE: Append to an existing template
|
||||||
|
cli.AppHelpTemplate = fmt.Sprintf(`%s
|
||||||
|
|
||||||
|
WEBSITE: http://awesometown.example.com
|
||||||
|
|
||||||
|
SUPPORT: support@awesometown.example.com
|
||||||
|
|
||||||
|
`, cli.AppHelpTemplate)
|
||||||
|
|
||||||
|
// EXAMPLE: Override a template
|
||||||
|
cli.AppHelpTemplate = `NAME:
|
||||||
|
{{.Name}} - {{.Usage}}
|
||||||
|
USAGE:
|
||||||
|
{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command
|
||||||
|
[command options]{{end}} {{if
|
||||||
|
.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||||
|
{{if len .Authors}}
|
||||||
|
AUTHOR(S):
|
||||||
|
{{range .Authors}}{{ . }}{{end}}
|
||||||
|
{{end}}{{if .Commands}}
|
||||||
|
COMMANDS:
|
||||||
|
{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"
|
||||||
|
}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
||||||
|
GLOBAL OPTIONS:
|
||||||
|
{{range .VisibleFlags}}{{.}}
|
||||||
|
{{end}}{{end}}{{if .Copyright }}
|
||||||
|
COPYRIGHT:
|
||||||
|
{{.Copyright}}
|
||||||
|
{{end}}{{if .Version}}
|
||||||
|
VERSION:
|
||||||
|
{{.Version}}
|
||||||
|
{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
// EXAMPLE: Replace the `HelpPrinter` func
|
||||||
|
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
|
||||||
|
fmt.Println("Ha HA. I pwnd the help!!1")
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.NewApp().Run(os.Args)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 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.
|
||||||
|
@ -296,7 +296,7 @@ func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
||||||
inputSource := &MapInputSource{valueMap: map[string]interface{}{test.FlagName: test.MapValue}}
|
inputSource := &MapInputSource{valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}}
|
||||||
set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError)
|
set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError)
|
||||||
c := cli.NewContext(nil, set, nil)
|
c := cli.NewContext(nil, set, nil)
|
||||||
if test.EnvVarName != "" && test.EnvVarValue != "" {
|
if test.EnvVarName != "" && test.EnvVarValue != "" {
|
||||||
|
@ -3,6 +3,7 @@ package altsrc
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
@ -11,7 +12,31 @@ import (
|
|||||||
// MapInputSource implements InputSourceContext to return
|
// MapInputSource implements InputSourceContext to return
|
||||||
// data from the map that is loaded.
|
// data from the map that is loaded.
|
||||||
type MapInputSource struct {
|
type MapInputSource struct {
|
||||||
valueMap map[string]interface{}
|
valueMap map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nestedVal checks if the name has '.' delimiters.
|
||||||
|
// If so, it tries to traverse the tree by the '.' delimited sections to find
|
||||||
|
// a nested value for the key.
|
||||||
|
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 {
|
||||||
|
return nil, false
|
||||||
|
} else {
|
||||||
|
if ctype, ok := child.(map[interface{}]interface{}); !ok {
|
||||||
|
return nil, false
|
||||||
|
} else {
|
||||||
|
node = ctype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := node[sections[len(sections)-1]]; ok {
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Int returns an int from the map if it exists otherwise returns 0
|
// Int returns an int from the map if it exists otherwise returns 0
|
||||||
@ -22,7 +47,14 @@ func (fsm *MapInputSource) Int(name string) (int, error) {
|
|||||||
if !isType {
|
if !isType {
|
||||||
return 0, incorrectTypeForFlagError(name, "int", otherGenericValue)
|
return 0, incorrectTypeForFlagError(name, "int", otherGenericValue)
|
||||||
}
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.(int)
|
||||||
|
if !isType {
|
||||||
|
return 0, incorrectTypeForFlagError(name, "int", nestedGenericValue)
|
||||||
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +71,14 @@ func (fsm *MapInputSource) Duration(name string) (time.Duration, error) {
|
|||||||
}
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.(time.Duration)
|
||||||
|
if !isType {
|
||||||
|
return 0, incorrectTypeForFlagError(name, "duration", nestedGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
@ -53,6 +93,14 @@ func (fsm *MapInputSource) Float64(name string) (float64, error) {
|
|||||||
}
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.(float64)
|
||||||
|
if !isType {
|
||||||
|
return 0, incorrectTypeForFlagError(name, "float64", nestedGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
@ -67,6 +115,14 @@ func (fsm *MapInputSource) String(name string) (string, error) {
|
|||||||
}
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.(string)
|
||||||
|
if !isType {
|
||||||
|
return "", incorrectTypeForFlagError(name, "string", nestedGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@ -81,6 +137,14 @@ func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.([]string)
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -95,6 +159,14 @@ func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
|
|||||||
}
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.([]int)
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -109,6 +181,14 @@ func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) {
|
|||||||
}
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.(cli.Generic)
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(name, "cli.Generic", nestedGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -123,6 +203,14 @@ func (fsm *MapInputSource) Bool(name string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.(bool)
|
||||||
|
if !isType {
|
||||||
|
return false, incorrectTypeForFlagError(name, "bool", nestedGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@ -137,6 +225,14 @@ func (fsm *MapInputSource) BoolT(name string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return otherValue, nil
|
return otherValue, nil
|
||||||
}
|
}
|
||||||
|
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := nestedGenericValue.(bool)
|
||||||
|
if !isType {
|
||||||
|
return true, incorrectTypeForFlagError(name, "bool", nestedGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,41 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte(`top:
|
||||||
|
test: 15`), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
|
||||||
|
os.Setenv("THE_TEST", "10")
|
||||||
|
defer os.Setenv("THE_TEST", "")
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
val := c.Int("top.test")
|
||||||
|
expect(t, val, 10)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
|
func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
set := flag.NewFlagSet("test", 0)
|
set := flag.NewFlagSet("test", 0)
|
||||||
@ -110,6 +145,39 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte(`top:
|
||||||
|
test: 15`), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml", "--top.test", "7"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
val := c.Int("top.test")
|
||||||
|
expect(t, val, 7)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "top.test"}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
|
func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
set := flag.NewFlagSet("test", 0)
|
set := flag.NewFlagSet("test", 0)
|
||||||
@ -142,6 +210,39 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte(`top:
|
||||||
|
test: 15`), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
val := c.Int("top.test")
|
||||||
|
expect(t, val, 15)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) {
|
func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
set := flag.NewFlagSet("test", 0)
|
set := flag.NewFlagSet("test", 0)
|
||||||
@ -175,3 +276,38 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T
|
|||||||
|
|
||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte(`top:
|
||||||
|
test: 15`), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
|
||||||
|
os.Setenv("THE_TEST", "11")
|
||||||
|
defer os.Setenv("THE_TEST", "")
|
||||||
|
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
val := c.Int("top.test")
|
||||||
|
expect(t, val, 11)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
||||||
|
@ -24,7 +24,7 @@ type yamlSourceContext struct {
|
|||||||
// NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath.
|
// NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath.
|
||||||
func NewYamlSourceFromFile(file string) (InputSourceContext, error) {
|
func NewYamlSourceFromFile(file string) (InputSourceContext, error) {
|
||||||
ysc := &yamlSourceContext{FilePath: file}
|
ysc := &yamlSourceContext{FilePath: file}
|
||||||
var results map[string]interface{}
|
var results map[interface{}]interface{}
|
||||||
err := readCommandYaml(ysc.FilePath, &results)
|
err := readCommandYaml(ysc.FilePath, &results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error())
|
return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error())
|
||||||
|
36
app.go
36
app.go
@ -12,7 +12,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
appActionDeprecationURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-action-signature"
|
changeLogURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md"
|
||||||
|
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
||||||
|
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
|
||||||
|
|
||||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
||||||
|
|
||||||
@ -166,9 +168,7 @@ func (a *App) Run(arguments []string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if a.OnUsageError != nil {
|
if a.OnUsageError != nil {
|
||||||
err := a.OnUsageError(context, err, false)
|
err := a.OnUsageError(context, err, false)
|
||||||
if err != nil {
|
HandleExitCoder(err)
|
||||||
HandleExitCoder(err)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||||
@ -222,20 +222,18 @@ func (a *App) Run(arguments []string) (err error) {
|
|||||||
// Run default Action
|
// Run default Action
|
||||||
err = HandleAction(a.Action, context)
|
err = HandleAction(a.Action, context)
|
||||||
|
|
||||||
if err != nil {
|
HandleExitCoder(err)
|
||||||
HandleExitCoder(err)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling
|
// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling
|
||||||
func (a *App) RunAndExitOnError() {
|
func (a *App) RunAndExitOnError() {
|
||||||
fmt.Fprintln(os.Stderr,
|
fmt.Fprintf(os.Stderr,
|
||||||
"DEPRECATED cli.App.RunAndExitOnError. "+
|
"DEPRECATED cli.App.RunAndExitOnError. %s See %s\n",
|
||||||
"See https://github.com/codegangsta/cli/blob/master/CHANGELOG.md#deprecated-cli-app-runandexitonerror")
|
contactSysadmin, runAndExitOnErrorDeprecationURL)
|
||||||
if err := a.Run(os.Args); err != nil {
|
if err := a.Run(os.Args); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
OsExiter(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,9 +342,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|||||||
// Run default Action
|
// Run default Action
|
||||||
err = HandleAction(a.Action, context)
|
err = HandleAction(a.Action, context)
|
||||||
|
|
||||||
if err != nil {
|
HandleExitCoder(err)
|
||||||
HandleExitCoder(err)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,6 +362,11 @@ func (a *App) Categories() CommandCategories {
|
|||||||
return a.categories
|
return a.categories
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||||
|
func (a *App) VisibleFlags() []Flag {
|
||||||
|
return visibleFlags(a.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
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 flag == f {
|
||||||
@ -421,8 +422,9 @@ func HandleAction(action interface{}, context *Context) (err error) {
|
|||||||
vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)})
|
vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)})
|
||||||
|
|
||||||
if len(vals) == 0 {
|
if len(vals) == 0 {
|
||||||
fmt.Fprintln(os.Stderr,
|
fmt.Fprintf(os.Stderr,
|
||||||
"DEPRECATED Action signature. Must be `cli.ActionFunc`")
|
"DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n",
|
||||||
|
contactSysadmin, appActionDeprecationURL)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +432,7 @@ func HandleAction(action interface{}, context *Context) (err error) {
|
|||||||
return errInvalidActionSignature
|
return errInvalidActionSignature
|
||||||
}
|
}
|
||||||
|
|
||||||
if retErr, ok := reflect.ValueOf(vals[0]).Interface().(error); ok {
|
if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok {
|
||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
cli.go
2
cli.go
@ -10,7 +10,7 @@
|
|||||||
// app := cli.NewApp()
|
// app := cli.NewApp()
|
||||||
// app.Name = "greet"
|
// app.Name = "greet"
|
||||||
// app.Usage = "say a greeting"
|
// app.Usage = "say a greeting"
|
||||||
// app.Action = func(c *cli.Context) {
|
// app.Action = func(c *cli.Context) error {
|
||||||
// println("Greetings")
|
// println("Greetings")
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
@ -269,3 +269,8 @@ func (c Command) startApp(ctx *Context) error {
|
|||||||
|
|
||||||
return app.RunAsSubcommand(ctx)
|
return app.RunAsSubcommand(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||||
|
func (c Command) VisibleFlags() []Flag {
|
||||||
|
return visibleFlags(c.Flags)
|
||||||
|
}
|
||||||
|
12
errors.go
12
errors.go
@ -6,6 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var OsExiter = os.Exit
|
||||||
|
|
||||||
type MultiError struct {
|
type MultiError struct {
|
||||||
Errors []error
|
Errors []error
|
||||||
}
|
}
|
||||||
@ -26,6 +28,7 @@ func (m MultiError) Error() string {
|
|||||||
// 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 {
|
||||||
|
error
|
||||||
ExitCode() int
|
ExitCode() int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,15 +59,20 @@ func (ee *ExitError) ExitCode() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
||||||
// so prints the error to stderr (if it is non-empty) and calls os.Exit with the
|
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
||||||
// given exit code. If the given error is a MultiError, then this func is
|
// given exit code. If the given error is a MultiError, then this func is
|
||||||
// called on all members of the Errors slice.
|
// called on all members of the Errors slice.
|
||||||
func HandleExitCoder(err error) {
|
func HandleExitCoder(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if exitErr, ok := err.(ExitCoder); ok {
|
if exitErr, ok := err.(ExitCoder); ok {
|
||||||
if err.Error() != "" {
|
if err.Error() != "" {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
os.Exit(exitErr.ExitCode())
|
OsExiter(exitErr.ExitCode())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if multiErr, ok := err.(MultiError); ok {
|
if multiErr, ok := err.(MultiError); ok {
|
||||||
|
60
errors_test.go
Normal file
60
errors_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleExitCoder_nil(t *testing.T) {
|
||||||
|
exitCode := 0
|
||||||
|
called := false
|
||||||
|
|
||||||
|
OsExiter = func(rc int) {
|
||||||
|
exitCode = rc
|
||||||
|
called = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { OsExiter = os.Exit }()
|
||||||
|
|
||||||
|
HandleExitCoder(nil)
|
||||||
|
|
||||||
|
expect(t, exitCode, 0)
|
||||||
|
expect(t, called, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleExitCoder_ExitCoder(t *testing.T) {
|
||||||
|
exitCode := 0
|
||||||
|
called := false
|
||||||
|
|
||||||
|
OsExiter = func(rc int) {
|
||||||
|
exitCode = rc
|
||||||
|
called = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { OsExiter = os.Exit }()
|
||||||
|
|
||||||
|
HandleExitCoder(NewExitError("galactic perimiter breach", 9))
|
||||||
|
|
||||||
|
expect(t, exitCode, 9)
|
||||||
|
expect(t, called, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
|
||||||
|
exitCode := 0
|
||||||
|
called := false
|
||||||
|
|
||||||
|
OsExiter = func(rc int) {
|
||||||
|
exitCode = rc
|
||||||
|
called = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { OsExiter = os.Exit }()
|
||||||
|
|
||||||
|
exitErr := NewExitError("galactic perimiter breach", 9)
|
||||||
|
err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr)
|
||||||
|
HandleExitCoder(err)
|
||||||
|
|
||||||
|
expect(t, exitCode, 9)
|
||||||
|
expect(t, called, true)
|
||||||
|
}
|
161
flag.go
161
flag.go
@ -5,19 +5,21 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const defaultPlaceholder = "value"
|
||||||
slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
|
|
||||||
)
|
var slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
|
||||||
|
|
||||||
// This flag enables bash-completion for all commands and subcommands
|
// This flag enables bash-completion for all commands and subcommands
|
||||||
var BashCompletionFlag = BoolFlag{
|
var BashCompletionFlag = BoolFlag{
|
||||||
Name: "generate-bash-completion",
|
Name: "generate-bash-completion",
|
||||||
|
Hidden: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This flag prints the version for the application
|
// This flag prints the version for the application
|
||||||
@ -34,6 +36,8 @@ var HelpFlag = BoolFlag{
|
|||||||
Usage: "show help",
|
Usage: "show help",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var FlagStringer FlagStringFunc = stringifyFlag
|
||||||
|
|
||||||
// Serializeder is used to circumvent the limitations of flag.FlagSet.Set
|
// Serializeder is used to circumvent the limitations of flag.FlagSet.Set
|
||||||
type Serializeder interface {
|
type Serializeder interface {
|
||||||
Serialized() string
|
Serialized() string
|
||||||
@ -78,25 +82,14 @@ type GenericFlag struct {
|
|||||||
Value Generic
|
Value Generic
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
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 {
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
return FlagStringer(f)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name, placeholder), f.FormatValueHelp(), usage))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f GenericFlag) FormatValueHelp() string {
|
|
||||||
if f.Value == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
s := f.Value.String()
|
|
||||||
if len(s) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("\"%s\"", s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -174,14 +167,12 @@ type StringSliceFlag struct {
|
|||||||
Value *StringSlice
|
Value *StringSlice
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the usage
|
// String returns the usage
|
||||||
func (f StringSliceFlag) String() string {
|
func (f StringSliceFlag) String() string {
|
||||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
return FlagStringer(f)
|
||||||
pref := prefixFor(firstName)
|
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name, placeholder), pref+firstName+" option "+pref+firstName+" option", usage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
@ -281,14 +272,12 @@ type IntSliceFlag struct {
|
|||||||
Value *IntSlice
|
Value *IntSlice
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the usage
|
// String returns the usage
|
||||||
func (f IntSliceFlag) String() string {
|
func (f IntSliceFlag) String() string {
|
||||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
return FlagStringer(f)
|
||||||
pref := prefixFor(firstName)
|
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name, placeholder), pref+firstName+" option "+pref+firstName+" option", usage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
@ -330,12 +319,12 @@ type BoolFlag struct {
|
|||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
Destination *bool
|
Destination *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 {
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
return FlagStringer(f)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name, placeholder), usage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
@ -374,12 +363,12 @@ type BoolTFlag struct {
|
|||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
Destination *bool
|
Destination *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 BoolTFlag) String() string {
|
func (f BoolTFlag) String() string {
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
return FlagStringer(f)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name, placeholder), usage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
@ -418,20 +407,12 @@ type StringFlag struct {
|
|||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
Destination *string
|
Destination *string
|
||||||
|
Hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the usage
|
// String returns the usage
|
||||||
func (f StringFlag) String() string {
|
func (f StringFlag) String() string {
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
return FlagStringer(f)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name, placeholder), f.FormatValueHelp(), usage))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f StringFlag) FormatValueHelp() string {
|
|
||||||
s := f.Value
|
|
||||||
if len(s) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("\"%s\"", s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
@ -467,12 +448,12 @@ type IntFlag struct {
|
|||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
Destination *int
|
Destination *int
|
||||||
|
Hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the usage
|
// String returns the usage
|
||||||
func (f IntFlag) String() string {
|
func (f IntFlag) String() string {
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
return FlagStringer(f)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
@ -511,12 +492,12 @@ type DurationFlag struct {
|
|||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
Destination *time.Duration
|
Destination *time.Duration
|
||||||
|
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 {
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
return FlagStringer(f)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
@ -555,12 +536,12 @@ type Float64Flag struct {
|
|||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
Destination *float64
|
Destination *float64
|
||||||
|
Hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the usage
|
// String returns the usage
|
||||||
func (f Float64Flag) String() string {
|
func (f Float64Flag) String() string {
|
||||||
placeholder, usage := unquoteUsage(f.Usage)
|
return FlagStringer(f)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name, placeholder), f.Value, usage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
@ -590,6 +571,16 @@ func (f Float64Flag) GetName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func visibleFlags(fl []Flag) []Flag {
|
||||||
|
visible := []Flag{}
|
||||||
|
for _, flag := range fl {
|
||||||
|
if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() {
|
||||||
|
visible = append(visible, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return visible
|
||||||
|
}
|
||||||
|
|
||||||
func prefixFor(name string) (prefix string) {
|
func prefixFor(name string) (prefix string) {
|
||||||
if len(name) == 1 {
|
if len(name) == 1 {
|
||||||
prefix = "-"
|
prefix = "-"
|
||||||
@ -648,3 +639,83 @@ func withEnvHint(envVar, str string) string {
|
|||||||
}
|
}
|
||||||
return str + envText
|
return str + envText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stringifyFlag(f Flag) string {
|
||||||
|
fv := reflect.ValueOf(f)
|
||||||
|
|
||||||
|
switch f.(type) {
|
||||||
|
case IntSliceFlag:
|
||||||
|
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||||
|
stringifyIntSliceFlag(f.(IntSliceFlag)))
|
||||||
|
case StringSliceFlag:
|
||||||
|
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||||
|
stringifyStringSliceFlag(f.(StringSliceFlag)))
|
||||||
|
}
|
||||||
|
|
||||||
|
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
||||||
|
|
||||||
|
needsPlaceholder := false
|
||||||
|
defaultValueString := ""
|
||||||
|
val := fv.FieldByName("Value")
|
||||||
|
|
||||||
|
if val.IsValid() {
|
||||||
|
needsPlaceholder = true
|
||||||
|
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
||||||
|
|
||||||
|
if val.Kind() == reflect.String && val.String() != "" {
|
||||||
|
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultValueString == " (default: )" {
|
||||||
|
defaultValueString = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if needsPlaceholder && placeholder == "" {
|
||||||
|
placeholder = defaultPlaceholder
|
||||||
|
}
|
||||||
|
|
||||||
|
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
|
||||||
|
|
||||||
|
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||||
|
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringifyStringSliceFlag(f StringSliceFlag) string {
|
||||||
|
defaultVals := []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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringifySliceFlag(usage, name string, defaultVals []string) string {
|
||||||
|
placeholder, usage := unquoteUsage(usage)
|
||||||
|
if placeholder == "" {
|
||||||
|
placeholder = defaultPlaceholder
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultVal := ""
|
||||||
|
if len(defaultVals) > 0 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
65
flag_test.go
65
flag_test.go
@ -7,6 +7,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var boolFlagTests = []struct {
|
var boolFlagTests = []struct {
|
||||||
@ -18,13 +19,12 @@ 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 {
|
||||||
t.Errorf("%s does not match %s", output, test.expected)
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,11 +35,12 @@ var stringFlagTests = []struct {
|
|||||||
value string
|
value string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", "", "", "--help \t"},
|
{"foo", "", "", "--foo value\t"},
|
||||||
{"h", "", "", "-h \t"},
|
{"f", "", "", "-f value\t"},
|
||||||
{"h", "", "", "-h \t"},
|
{"f", "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"},
|
||||||
{"test", "", "Something", "--test \"Something\"\t"},
|
{"test", "", "Something", "--test value\t(default: \"Something\")"},
|
||||||
{"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE \tLoad configuration from FILE"},
|
{"config,c", "Load configuration from `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\")"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringFlagHelpOutput(t *testing.T) {
|
func TestStringFlagHelpOutput(t *testing.T) {
|
||||||
@ -48,7 +49,7 @@ func TestStringFlagHelpOutput(t *testing.T) {
|
|||||||
output := flag.String()
|
output := flag.String()
|
||||||
|
|
||||||
if output != test.expected {
|
if output != test.expected {
|
||||||
t.Errorf("%s does not match %s", output, test.expected)
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,11 +76,11 @@ var stringSliceFlagTests = []struct {
|
|||||||
value *StringSlice
|
value *StringSlice
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", NewStringSlice(""), "--help [--help option --help option]\t"},
|
{"foo", NewStringSlice(""), "--foo value\t"},
|
||||||
{"h", NewStringSlice(""), "-h [-h option -h option]\t"},
|
{"f", NewStringSlice(""), "-f value\t"},
|
||||||
{"h", NewStringSlice(""), "-h [-h option -h option]\t"},
|
{"f", NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"},
|
||||||
{"test", NewStringSlice("Something"), "--test [--test option --test option]\t"},
|
{"test", NewStringSlice("Something"), "--test value\t(default: \"Something\")"},
|
||||||
{"d, dee", NewStringSlice("Inka", "Dinka", "dooo"), "-d, --dee [-d option -d option]\t"},
|
{"d, dee", NewStringSlice("Inka", "Dinka", "dooo"), "-d value, --dee value\t(default: \"Inka\", \"Dinka\", \"dooo\")"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||||
@ -114,14 +115,13 @@ var intFlagTests = []struct {
|
|||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", "--help \"0\"\t"},
|
{"hats", "--hats value\t(default: 9)"},
|
||||||
{"h", "-h \"0\"\t"},
|
{"H", "-H value\t(default: 9)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntFlagHelpOutput(t *testing.T) {
|
func TestIntFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range intFlagTests {
|
for _, test := range intFlagTests {
|
||||||
flag := IntFlag{Name: test.name}
|
flag := IntFlag{Name: test.name, Value: 9}
|
||||||
output := flag.String()
|
output := flag.String()
|
||||||
|
|
||||||
if output != test.expected {
|
if output != test.expected {
|
||||||
@ -151,18 +151,17 @@ var durationFlagTests = []struct {
|
|||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", "--help \"0\"\t"},
|
{"hooting", "--hooting value\t(default: 1s)"},
|
||||||
{"h", "-h \"0\"\t"},
|
{"H", "-H value\t(default: 1s)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDurationFlagHelpOutput(t *testing.T) {
|
func TestDurationFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range durationFlagTests {
|
for _, test := range durationFlagTests {
|
||||||
flag := DurationFlag{Name: test.name}
|
flag := DurationFlag{Name: test.name, Value: 1 * time.Second}
|
||||||
output := flag.String()
|
output := flag.String()
|
||||||
|
|
||||||
if output != test.expected {
|
if output != test.expected {
|
||||||
t.Errorf("%s does not match %s", output, test.expected)
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,14 +188,12 @@ var intSliceFlagTests = []struct {
|
|||||||
value *IntSlice
|
value *IntSlice
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", NewIntSlice(), "--help [--help option --help option]\t"},
|
{"heads", NewIntSlice(), "--heads value\t"},
|
||||||
{"h", NewIntSlice(), "-h [-h option -h option]\t"},
|
{"H", NewIntSlice(), "-H value\t"},
|
||||||
{"h", NewIntSlice(), "-h [-h option -h option]\t"},
|
{"H, heads", NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"},
|
||||||
{"test", NewIntSlice(9), "--test [--test option --test option]\t"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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, Value: test.value}
|
||||||
output := flag.String()
|
output := flag.String()
|
||||||
@ -228,18 +225,17 @@ var float64FlagTests = []struct {
|
|||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", "--help \"0\"\t"},
|
{"hooting", "--hooting value\t(default: 0.1)"},
|
||||||
{"h", "-h \"0\"\t"},
|
{"H", "-H value\t(default: 0.1)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFloat64FlagHelpOutput(t *testing.T) {
|
func TestFloat64FlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range float64FlagTests {
|
for _, test := range float64FlagTests {
|
||||||
flag := Float64Flag{Name: test.name}
|
flag := Float64Flag{Name: test.name, Value: float64(0.1)}
|
||||||
output := flag.String()
|
output := flag.String()
|
||||||
|
|
||||||
if output != test.expected {
|
if output != test.expected {
|
||||||
t.Errorf("%s does not match %s", output, test.expected)
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,12 +262,11 @@ var genericFlagTests = []struct {
|
|||||||
value Generic
|
value Generic
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"},
|
{"toads", &Parser{"abc", "def"}, "--toads value\ttest flag (default: abc,def)"},
|
||||||
{"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"},
|
{"t", &Parser{"abc", "def"}, "-t value\ttest flag (default: abc,def)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
|
4
funcs.go
4
funcs.go
@ -22,3 +22,7 @@ type CommandNotFoundFunc func(*Context, string)
|
|||||||
// original error messages. If this function is not set, the "Incorrect usage"
|
// original error messages. If this function is not set, the "Incorrect usage"
|
||||||
// is displayed and the execution is interrupted.
|
// is displayed and the execution is interrupted.
|
||||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) 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
|
||||||
|
18
help.go
18
help.go
@ -15,7 +15,7 @@ var AppHelpTemplate = `NAME:
|
|||||||
{{.Name}} - {{.Usage}}
|
{{.Name}} - {{.Usage}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
||||||
{{if .Version}}{{if not .HideVersion}}
|
{{if .Version}}{{if not .HideVersion}}
|
||||||
VERSION:
|
VERSION:
|
||||||
{{.Version}}
|
{{.Version}}
|
||||||
@ -26,9 +26,9 @@ AUTHOR(S):
|
|||||||
COMMANDS:{{range .Categories}}{{if .Name}}
|
COMMANDS:{{range .Categories}}{{if .Name}}
|
||||||
{{.Name}}{{ ":" }}{{end}}{{range .Commands}}
|
{{.Name}}{{ ":" }}{{end}}{{range .Commands}}
|
||||||
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}
|
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}
|
||||||
{{end}}{{end}}{{if .Flags}}
|
{{end}}{{end}}{{if .VisibleFlags}}
|
||||||
GLOBAL OPTIONS:
|
GLOBAL OPTIONS:
|
||||||
{{range .Flags}}{{.}}
|
{{range .VisibleFlags}}{{.}}
|
||||||
{{end}}{{end}}{{if .Copyright }}
|
{{end}}{{end}}{{if .Copyright }}
|
||||||
COPYRIGHT:
|
COPYRIGHT:
|
||||||
{{.Copyright}}
|
{{.Copyright}}
|
||||||
@ -42,16 +42,16 @@ var CommandHelpTemplate = `NAME:
|
|||||||
{{.HelpName}} - {{.Usage}}
|
{{.HelpName}} - {{.Usage}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
|
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
|
||||||
|
|
||||||
CATEGORY:
|
CATEGORY:
|
||||||
{{.Category}}{{end}}{{if .Description}}
|
{{.Category}}{{end}}{{if .Description}}
|
||||||
|
|
||||||
DESCRIPTION:
|
DESCRIPTION:
|
||||||
{{.Description}}{{end}}{{if .Flags}}
|
{{.Description}}{{end}}{{if .VisibleFlags}}
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
{{range .Flags}}{{.}}
|
{{range .VisibleFlags}}{{.}}
|
||||||
{{end}}{{ end }}
|
{{end}}{{ end }}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -62,14 +62,14 @@ var SubcommandHelpTemplate = `NAME:
|
|||||||
{{.HelpName}} - {{.Usage}}
|
{{.HelpName}} - {{.Usage}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||||
|
|
||||||
COMMANDS:{{range .Categories}}{{if .Name}}
|
COMMANDS:{{range .Categories}}{{if .Name}}
|
||||||
{{.Name}}{{ ":" }}{{end}}{{range .Commands}}
|
{{.Name}}{{ ":" }}{{end}}{{range .Commands}}
|
||||||
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}
|
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}}
|
||||||
{{end}}{{if .Flags}}
|
{{end}}{{if .VisibleFlags}}
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
{{range .Flags}}{{.}}
|
{{range .VisibleFlags}}{{.}}
|
||||||
{{end}}{{end}}
|
{{end}}{{end}}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user