├── MAINTAINERS.md
├── .flake8
├── .gitignore
├── altsrc
├── altsrc.go
├── helpers_test.go
├── input_source_context.go
├── yaml_file_loader.go
├── toml_file_loader.go
├── map_input_source.go
├── flag.go
├── toml_command_test.go
├── yaml_command_test.go
├── flag_generated.go
└── flag_test.go
├── autocomplete
├── zsh_autocomplete
└── bash_autocomplete
├── helpers_unix_test.go
├── helpers_windows_test.go
├── .travis.yml
├── appveyor.yml
├── sort.go
├── helpers_test.go
├── sort_test.go
├── cli.go
├── CONTRIBUTING.md
├── LICENSE
├── category.go
├── funcs.go
├── flag-types.json
├── errors_test.go
├── errors.go
├── CODE_OF_CONDUCT.md
├── runtests
├── context.go
├── generate-flag-types
├── command_test.go
├── command.go
├── help.go
├── help_test.go
├── context_test.go
├── app.go
├── flag_generated.go
└── CHANGELOG.md
/MAINTAINERS.md:
--------------------------------------------------------------------------------
1 | - @meatballhat
2 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 120
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.coverprofile
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/altsrc/altsrc.go:
--------------------------------------------------------------------------------
1 | package altsrc
2 |
3 | //go:generate python ../generate-flag-types altsrc -i ../flag-types.json -o flag_generated.go
4 |
--------------------------------------------------------------------------------
/autocomplete/zsh_autocomplete:
--------------------------------------------------------------------------------
1 | autoload -U compinit && compinit
2 | autoload -U bashcompinit && bashcompinit
3 |
4 | script_dir=$(dirname $0)
5 | source ${script_dir}/bash_autocomplete
6 |
--------------------------------------------------------------------------------
/helpers_unix_test.go:
--------------------------------------------------------------------------------
1 | // +build darwin dragonfly freebsd linux netbsd openbsd solaris
2 |
3 | package cli
4 |
5 | import "os"
6 |
7 | func clearenv() {
8 | os.Clearenv()
9 | }
10 |
--------------------------------------------------------------------------------
/autocomplete/bash_autocomplete:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | : ${PROG:=$(basename ${BASH_SOURCE})}
4 |
5 | _cli_bash_autocomplete() {
6 | local cur opts base
7 | COMPREPLY=()
8 | cur="${COMP_WORDS[COMP_CWORD]}"
9 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
10 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
11 | return 0
12 | }
13 |
14 | complete -F _cli_bash_autocomplete $PROG
15 |
16 | unset PROG
17 |
--------------------------------------------------------------------------------
/helpers_windows_test.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "os"
5 | "syscall"
6 | )
7 |
8 | // os.Clearenv() doesn't actually unset variables on Windows
9 | // See: https://github.com/golang/go/issues/17902
10 | func clearenv() {
11 | for _, s := range os.Environ() {
12 | for j := 1; j < len(s); j++ {
13 | if s[j] == '=' {
14 | keyp, _ := syscall.UTF16PtrFromString(s[0:j])
15 | syscall.SetEnvironmentVariable(keyp, nil)
16 | break
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/altsrc/helpers_test.go:
--------------------------------------------------------------------------------
1 | package altsrc
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | )
7 |
8 | func expect(t *testing.T, a interface{}, b interface{}) {
9 | if !reflect.DeepEqual(b, a) {
10 | t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
11 | }
12 | }
13 |
14 | func refute(t *testing.T, a interface{}, b interface{}) {
15 | if a == b {
16 | t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | sudo: false
3 | dist: trusty
4 | osx_image: xcode8.3
5 | go: 1.8.x
6 |
7 | os:
8 | - linux
9 | - osx
10 |
11 | cache:
12 | directories:
13 | - node_modules
14 |
15 | before_script:
16 | - go get github.com/urfave/gfmrun/... || true
17 | - go get golang.org/x/tools/cmd/goimports
18 | - if [ ! -f node_modules/.bin/markdown-toc ] ; then
19 | npm install markdown-toc ;
20 | fi
21 |
22 | script:
23 | - ./runtests gen
24 | - ./runtests vet
25 | - ./runtests test
26 | - ./runtests gfmrun
27 | - ./runtests toc
28 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: "{build}"
2 |
3 | os: Windows Server 2016
4 |
5 | image: Visual Studio 2017
6 |
7 | clone_folder: c:\gopath\src\github.com\urfave\cli
8 |
9 | environment:
10 | GOPATH: C:\gopath
11 | GOVERSION: 1.8.x
12 | PYTHON: C:\Python36-x64
13 | PYTHON_VERSION: 3.6.x
14 | PYTHON_ARCH: 64
15 |
16 | install:
17 | - set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
18 | - go version
19 | - go env
20 | - go get github.com/urfave/gfmrun/...
21 | - go get -v -t ./...
22 |
23 | build_script:
24 | - python runtests vet
25 | - python runtests test
26 | - python runtests gfmrun
27 |
--------------------------------------------------------------------------------
/sort.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import "unicode"
4 |
5 | // lexicographicLess compares strings alphabetically considering case.
6 | func lexicographicLess(i, j string) bool {
7 | iRunes := []rune(i)
8 | jRunes := []rune(j)
9 |
10 | lenShared := len(iRunes)
11 | if lenShared > len(jRunes) {
12 | lenShared = len(jRunes)
13 | }
14 |
15 | for index := 0; index < lenShared; index++ {
16 | ir := iRunes[index]
17 | jr := jRunes[index]
18 |
19 | if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr {
20 | return lir < ljr
21 | }
22 |
23 | if ir != jr {
24 | return ir < jr
25 | }
26 | }
27 |
28 | return i < j
29 | }
30 |
--------------------------------------------------------------------------------
/altsrc/input_source_context.go:
--------------------------------------------------------------------------------
1 | package altsrc
2 |
3 | import (
4 | "time"
5 |
6 | "gopkg.in/urfave/cli.v1"
7 | )
8 |
9 | // InputSourceContext is an interface used to allow
10 | // other input sources to be implemented as needed.
11 | type InputSourceContext interface {
12 | Int(name string) (int, error)
13 | Duration(name string) (time.Duration, error)
14 | Float64(name string) (float64, error)
15 | String(name string) (string, error)
16 | StringSlice(name string) ([]string, error)
17 | IntSlice(name string) ([]int, error)
18 | Generic(name string) (cli.Generic, error)
19 | Bool(name string) (bool, error)
20 | BoolT(name string) (bool, error)
21 | }
22 |
--------------------------------------------------------------------------------
/helpers_test.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "os"
5 | "reflect"
6 | "runtime"
7 | "strings"
8 | "testing"
9 | )
10 |
11 | var (
12 | wd, _ = os.Getwd()
13 | )
14 |
15 | func expect(t *testing.T, a interface{}, b interface{}) {
16 | _, fn, line, _ := runtime.Caller(1)
17 | fn = strings.Replace(fn, wd+"/", "", -1)
18 |
19 | if !reflect.DeepEqual(a, b) {
20 | t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a))
21 | }
22 | }
23 |
24 | func refute(t *testing.T, a interface{}, b interface{}) {
25 | if reflect.DeepEqual(a, b) {
26 | t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/sort_test.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import "testing"
4 |
5 | var lexicographicLessTests = []struct {
6 | i string
7 | j string
8 | expected bool
9 | }{
10 | {"", "a", true},
11 | {"a", "", false},
12 | {"a", "a", false},
13 | {"a", "A", false},
14 | {"A", "a", true},
15 | {"aa", "a", false},
16 | {"a", "aa", true},
17 | {"a", "b", true},
18 | {"a", "B", true},
19 | {"A", "b", true},
20 | {"A", "B", true},
21 | }
22 |
23 | func TestLexicographicLess(t *testing.T) {
24 | for _, test := range lexicographicLessTests {
25 | actual := lexicographicLess(test.i, test.j)
26 | if test.expected != actual {
27 | t.Errorf(`expected string "%s" to come before "%s"`, test.i, test.j)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/cli.go:
--------------------------------------------------------------------------------
1 | // Package cli provides a minimal framework for creating and organizing command line
2 | // Go applications. cli is designed to be easy to understand and write, the most simple
3 | // cli application can be written as follows:
4 | // func main() {
5 | // cli.NewApp().Run(os.Args)
6 | // }
7 | //
8 | // Of course this application does not do much, so let's make this an actual application:
9 | // func main() {
10 | // app := cli.NewApp()
11 | // app.Name = "greet"
12 | // app.Usage = "say a greeting"
13 | // app.Action = func(c *cli.Context) error {
14 | // println("Greetings")
15 | // return nil
16 | // }
17 | //
18 | // app.Run(os.Args)
19 | // }
20 | package cli
21 |
22 | //go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go
23 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | **NOTE**: the primary maintainer(s) may be found in
4 | [./MAINTAINERS.md](./MAINTAINERS.md).
5 |
6 | Feel free to put up a pull request to fix a bug or maybe add a feature. I will
7 | give it a code review and make sure that it does not break backwards
8 | compatibility. If I or any other collaborators agree that it is in line with
9 | the vision of the project, we will work with you to get the code into
10 | a mergeable state and merge it into the master branch.
11 |
12 | If you have contributed something significant to the project, we will most
13 | likely add you as a collaborator. As a collaborator you are given the ability
14 | to merge others pull requests. It is very important that new code does not
15 | break existing code, so be careful about what code you do choose to merge.
16 |
17 | If you feel like you have contributed to the project but have not yet been added
18 | as a collaborator, we probably forgot to add you :sweat_smile:. Please open an
19 | issue!
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Jeremy Saenz & Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/category.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | // CommandCategories is a slice of *CommandCategory.
4 | type CommandCategories []*CommandCategory
5 |
6 | // CommandCategory is a category containing commands.
7 | type CommandCategory struct {
8 | Name string
9 | Commands Commands
10 | }
11 |
12 | func (c CommandCategories) Less(i, j int) bool {
13 | return lexicographicLess(c[i].Name, c[j].Name)
14 | }
15 |
16 | func (c CommandCategories) Len() int {
17 | return len(c)
18 | }
19 |
20 | func (c CommandCategories) Swap(i, j int) {
21 | c[i], c[j] = c[j], c[i]
22 | }
23 |
24 | // AddCommand adds a command to a category.
25 | func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
26 | for _, commandCategory := range c {
27 | if commandCategory.Name == category {
28 | commandCategory.Commands = append(commandCategory.Commands, command)
29 | return c
30 | }
31 | }
32 | return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
33 | }
34 |
35 | // VisibleCommands returns a slice of the Commands with Hidden=false
36 | func (c *CommandCategory) VisibleCommands() []Command {
37 | ret := []Command{}
38 | for _, command := range c.Commands {
39 | if !command.Hidden {
40 | ret = append(ret, command)
41 | }
42 | }
43 | return ret
44 | }
45 |
--------------------------------------------------------------------------------
/funcs.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | // BashCompleteFunc is an action to execute when the bash-completion flag is set
4 | type BashCompleteFunc func(*Context)
5 |
6 | // BeforeFunc is an action to execute before any subcommands are run, but after
7 | // the context is ready if a non-nil error is returned, no subcommands are run
8 | type BeforeFunc func(*Context) error
9 |
10 | // AfterFunc is an action to execute after any subcommands are run, but after the
11 | // subcommand has finished it is run even if Action() panics
12 | type AfterFunc func(*Context) error
13 |
14 | // ActionFunc is the action to execute when no subcommands are specified
15 | type ActionFunc func(*Context) error
16 |
17 | // CommandNotFoundFunc is executed if the proper command cannot be found
18 | type CommandNotFoundFunc func(*Context, string)
19 |
20 | // OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
21 | // customized usage error messages. This function is able to replace the
22 | // original error messages. If this function is not set, the "Incorrect usage"
23 | // is displayed and the execution is interrupted.
24 | type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
25 |
26 | // ExitErrHandlerFunc is executed if provided in order to handle ExitError values
27 | // returned by Actions and Before/After functions.
28 | type ExitErrHandlerFunc func(context *Context, err error)
29 |
30 | // FlagStringFunc is used by the help generation to display a flag, which is
31 | // expected to be a single line.
32 | type FlagStringFunc func(Flag) string
33 |
34 | // FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
35 | // text for a flag's full name.
36 | type FlagNamePrefixFunc func(fullName, placeholder string) string
37 |
38 | // FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
39 | // with the environment variable details.
40 | type FlagEnvHintFunc func(envVar, str string) string
41 |
42 | // FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
43 | // with the file path details.
44 | type FlagFileHintFunc func(filePath, str string) string
45 |
--------------------------------------------------------------------------------
/flag-types.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Bool",
4 | "type": "bool",
5 | "value": false,
6 | "context_default": "false",
7 | "parser": "strconv.ParseBool(f.Value.String())"
8 | },
9 | {
10 | "name": "BoolT",
11 | "type": "bool",
12 | "value": false,
13 | "doctail": " that is true by default",
14 | "context_default": "false",
15 | "parser": "strconv.ParseBool(f.Value.String())"
16 | },
17 | {
18 | "name": "Duration",
19 | "type": "time.Duration",
20 | "doctail": " (see https://golang.org/pkg/time/#ParseDuration)",
21 | "context_default": "0",
22 | "parser": "time.ParseDuration(f.Value.String())"
23 | },
24 | {
25 | "name": "Float64",
26 | "type": "float64",
27 | "context_default": "0",
28 | "parser": "strconv.ParseFloat(f.Value.String(), 64)"
29 | },
30 | {
31 | "name": "Generic",
32 | "type": "Generic",
33 | "dest": false,
34 | "context_default": "nil",
35 | "context_type": "interface{}"
36 | },
37 | {
38 | "name": "Int64",
39 | "type": "int64",
40 | "context_default": "0",
41 | "parser": "strconv.ParseInt(f.Value.String(), 0, 64)"
42 | },
43 | {
44 | "name": "Int",
45 | "type": "int",
46 | "context_default": "0",
47 | "parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
48 | "parser_cast": "int(parsed)"
49 | },
50 | {
51 | "name": "IntSlice",
52 | "type": "*IntSlice",
53 | "dest": false,
54 | "context_default": "nil",
55 | "context_type": "[]int",
56 | "parser": "(f.Value.(*IntSlice)).Value(), error(nil)"
57 | },
58 | {
59 | "name": "Int64Slice",
60 | "type": "*Int64Slice",
61 | "dest": false,
62 | "context_default": "nil",
63 | "context_type": "[]int64",
64 | "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)"
65 | },
66 | {
67 | "name": "String",
68 | "type": "string",
69 | "context_default": "\"\"",
70 | "parser": "f.Value.String(), error(nil)"
71 | },
72 | {
73 | "name": "StringSlice",
74 | "type": "*StringSlice",
75 | "dest": false,
76 | "context_default": "nil",
77 | "context_type": "[]string",
78 | "parser": "(f.Value.(*StringSlice)).Value(), error(nil)"
79 | },
80 | {
81 | "name": "Uint64",
82 | "type": "uint64",
83 | "context_default": "0",
84 | "parser": "strconv.ParseUint(f.Value.String(), 0, 64)"
85 | },
86 | {
87 | "name": "Uint",
88 | "type": "uint",
89 | "context_default": "0",
90 | "parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
91 | "parser_cast": "uint(parsed)"
92 | }
93 | ]
94 |
--------------------------------------------------------------------------------
/altsrc/yaml_file_loader.go:
--------------------------------------------------------------------------------
1 | // Disabling building of yaml support in cases where golang is 1.0 or 1.1
2 | // as the encoding library is not implemented or supported.
3 |
4 | // +build go1.2
5 |
6 | package altsrc
7 |
8 | import (
9 | "fmt"
10 | "io/ioutil"
11 | "net/http"
12 | "net/url"
13 | "os"
14 | "runtime"
15 | "strings"
16 |
17 | "gopkg.in/urfave/cli.v1"
18 |
19 | "gopkg.in/yaml.v2"
20 | )
21 |
22 | type yamlSourceContext struct {
23 | FilePath string
24 | }
25 |
26 | // NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath.
27 | func NewYamlSourceFromFile(file string) (InputSourceContext, error) {
28 | ysc := &yamlSourceContext{FilePath: file}
29 | var results map[interface{}]interface{}
30 | err := readCommandYaml(ysc.FilePath, &results)
31 | if err != nil {
32 | return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error())
33 | }
34 |
35 | return &MapInputSource{valueMap: results}, nil
36 | }
37 |
38 | // NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context.
39 | func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
40 | return func(context *cli.Context) (InputSourceContext, error) {
41 | filePath := context.String(flagFileName)
42 | return NewYamlSourceFromFile(filePath)
43 | }
44 | }
45 |
46 | func readCommandYaml(filePath string, container interface{}) (err error) {
47 | b, err := loadDataFrom(filePath)
48 | if err != nil {
49 | return err
50 | }
51 |
52 | err = yaml.Unmarshal(b, container)
53 | if err != nil {
54 | return err
55 | }
56 |
57 | err = nil
58 | return
59 | }
60 |
61 | func loadDataFrom(filePath string) ([]byte, error) {
62 | u, err := url.Parse(filePath)
63 | if err != nil {
64 | return nil, err
65 | }
66 |
67 | if u.Host != "" { // i have a host, now do i support the scheme?
68 | switch u.Scheme {
69 | case "http", "https":
70 | res, err := http.Get(filePath)
71 | if err != nil {
72 | return nil, err
73 | }
74 | return ioutil.ReadAll(res.Body)
75 | default:
76 | return nil, fmt.Errorf("scheme of %s is unsupported", filePath)
77 | }
78 | } else if u.Path != "" { // i dont have a host, but I have a path. I am a local file.
79 | if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil {
80 | return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
81 | }
82 | return ioutil.ReadFile(filePath)
83 | } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") {
84 | // on Windows systems u.Path is always empty, so we need to check the string directly.
85 | if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil {
86 | return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
87 | }
88 | return ioutil.ReadFile(filePath)
89 | }
90 |
91 | return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
92 | }
93 |
--------------------------------------------------------------------------------
/errors_test.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "testing"
8 | )
9 |
10 | func TestHandleExitCoder_nil(t *testing.T) {
11 | exitCode := 0
12 | called := false
13 |
14 | OsExiter = func(rc int) {
15 | if !called {
16 | exitCode = rc
17 | called = true
18 | }
19 | }
20 |
21 | defer func() { OsExiter = fakeOsExiter }()
22 |
23 | HandleExitCoder(nil)
24 |
25 | expect(t, exitCode, 0)
26 | expect(t, called, false)
27 | }
28 |
29 | func TestHandleExitCoder_ExitCoder(t *testing.T) {
30 | exitCode := 0
31 | called := false
32 |
33 | OsExiter = func(rc int) {
34 | if !called {
35 | exitCode = rc
36 | called = true
37 | }
38 | }
39 |
40 | defer func() { OsExiter = fakeOsExiter }()
41 |
42 | HandleExitCoder(NewExitError("galactic perimeter breach", 9))
43 |
44 | expect(t, exitCode, 9)
45 | expect(t, called, true)
46 | }
47 |
48 | func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
49 | exitCode := 0
50 | called := false
51 |
52 | OsExiter = func(rc int) {
53 | if !called {
54 | exitCode = rc
55 | called = true
56 | }
57 | }
58 |
59 | defer func() { OsExiter = fakeOsExiter }()
60 |
61 | exitErr := NewExitError("galactic perimeter breach", 9)
62 | exitErr2 := NewExitError("last ExitCoder", 11)
63 | err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2)
64 | HandleExitCoder(err)
65 |
66 | expect(t, exitCode, 11)
67 | expect(t, called, true)
68 | }
69 |
70 | // make a stub to not import pkg/errors
71 | type ErrorWithFormat struct {
72 | error
73 | }
74 |
75 | func NewErrorWithFormat(m string) *ErrorWithFormat {
76 | return &ErrorWithFormat{error: errors.New(m)}
77 | }
78 |
79 | func (f *ErrorWithFormat) Format(s fmt.State, verb rune) {
80 | fmt.Fprintf(s, "This the format: %v", f.error)
81 | }
82 |
83 | func TestHandleExitCoder_ErrorWithFormat(t *testing.T) {
84 | called := false
85 |
86 | OsExiter = func(rc int) {
87 | if !called {
88 | called = true
89 | }
90 | }
91 | ErrWriter = &bytes.Buffer{}
92 |
93 | defer func() {
94 | OsExiter = fakeOsExiter
95 | ErrWriter = fakeErrWriter
96 | }()
97 |
98 | err := NewExitError(NewErrorWithFormat("I am formatted"), 1)
99 | HandleExitCoder(err)
100 |
101 | expect(t, called, true)
102 | expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: I am formatted\n")
103 | }
104 |
105 | func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) {
106 | called := false
107 |
108 | OsExiter = func(rc int) {
109 | if !called {
110 | called = true
111 | }
112 | }
113 | ErrWriter = &bytes.Buffer{}
114 |
115 | defer func() { OsExiter = fakeOsExiter }()
116 |
117 | err := NewMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2"))
118 | HandleExitCoder(err)
119 |
120 | expect(t, called, true)
121 | expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: err1\nThis the format: err2\n")
122 | }
123 |
--------------------------------------------------------------------------------
/errors.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "strings"
8 | )
9 |
10 | // OsExiter is the function used when the app exits. If not set defaults to os.Exit.
11 | var OsExiter = os.Exit
12 |
13 | // ErrWriter is used to write errors to the user. This can be anything
14 | // implementing the io.Writer interface and defaults to os.Stderr.
15 | var ErrWriter io.Writer = os.Stderr
16 |
17 | // MultiError is an error that wraps multiple errors.
18 | type MultiError struct {
19 | Errors []error
20 | }
21 |
22 | // NewMultiError creates a new MultiError. Pass in one or more errors.
23 | func NewMultiError(err ...error) MultiError {
24 | return MultiError{Errors: err}
25 | }
26 |
27 | // Error implements the error interface.
28 | func (m MultiError) Error() string {
29 | errs := make([]string, len(m.Errors))
30 | for i, err := range m.Errors {
31 | errs[i] = err.Error()
32 | }
33 |
34 | return strings.Join(errs, "\n")
35 | }
36 |
37 | type ErrorFormatter interface {
38 | Format(s fmt.State, verb rune)
39 | }
40 |
41 | // ExitCoder is the interface checked by `App` and `Command` for a custom exit
42 | // code
43 | type ExitCoder interface {
44 | error
45 | ExitCode() int
46 | }
47 |
48 | // ExitError fulfills both the builtin `error` interface and `ExitCoder`
49 | type ExitError struct {
50 | exitCode int
51 | message interface{}
52 | }
53 |
54 | // NewExitError makes a new *ExitError
55 | func NewExitError(message interface{}, exitCode int) *ExitError {
56 | return &ExitError{
57 | exitCode: exitCode,
58 | message: message,
59 | }
60 | }
61 |
62 | // Error returns the string message, fulfilling the interface required by
63 | // `error`
64 | func (ee *ExitError) Error() string {
65 | return fmt.Sprintf("%v", ee.message)
66 | }
67 |
68 | // ExitCode returns the exit code, fulfilling the interface required by
69 | // `ExitCoder`
70 | func (ee *ExitError) ExitCode() int {
71 | return ee.exitCode
72 | }
73 |
74 | // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
75 | // so prints the error to stderr (if it is non-empty) and calls OsExiter with the
76 | // given exit code. If the given error is a MultiError, then this func is
77 | // called on all members of the Errors slice and calls OsExiter with the last exit code.
78 | func HandleExitCoder(err error) {
79 | if err == nil {
80 | return
81 | }
82 |
83 | if exitErr, ok := err.(ExitCoder); ok {
84 | if err.Error() != "" {
85 | if _, ok := exitErr.(ErrorFormatter); ok {
86 | fmt.Fprintf(ErrWriter, "%+v\n", err)
87 | } else {
88 | fmt.Fprintln(ErrWriter, err)
89 | }
90 | }
91 | OsExiter(exitErr.ExitCode())
92 | return
93 | }
94 |
95 | if multiErr, ok := err.(MultiError); ok {
96 | code := handleMultiError(multiErr)
97 | OsExiter(code)
98 | return
99 | }
100 | }
101 |
102 | func handleMultiError(multiErr MultiError) int {
103 | code := 1
104 | for _, merr := range multiErr.Errors {
105 | if multiErr2, ok := merr.(MultiError); ok {
106 | code = handleMultiError(multiErr2)
107 | } else {
108 | fmt.Fprintln(ErrWriter, merr)
109 | if exitErr, ok := merr.(ExitCoder); ok {
110 | code = exitErr.ExitCode()
111 | }
112 | }
113 | }
114 | return code
115 | }
116 |
--------------------------------------------------------------------------------
/altsrc/toml_file_loader.go:
--------------------------------------------------------------------------------
1 | // Disabling building of toml support in cases where golang is 1.0 or 1.1
2 | // as the encoding library is not implemented or supported.
3 |
4 | // +build go1.2
5 |
6 | package altsrc
7 |
8 | import (
9 | "fmt"
10 | "reflect"
11 |
12 | "github.com/BurntSushi/toml"
13 | "gopkg.in/urfave/cli.v1"
14 | )
15 |
16 | type tomlMap struct {
17 | Map map[interface{}]interface{}
18 | }
19 |
20 | func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
21 | ret = make(map[interface{}]interface{})
22 | m := i.(map[string]interface{})
23 | for key, val := range m {
24 | v := reflect.ValueOf(val)
25 | switch v.Kind() {
26 | case reflect.Bool:
27 | ret[key] = val.(bool)
28 | case reflect.String:
29 | ret[key] = val.(string)
30 | case reflect.Int:
31 | ret[key] = int(val.(int))
32 | case reflect.Int8:
33 | ret[key] = int(val.(int8))
34 | case reflect.Int16:
35 | ret[key] = int(val.(int16))
36 | case reflect.Int32:
37 | ret[key] = int(val.(int32))
38 | case reflect.Int64:
39 | ret[key] = int(val.(int64))
40 | case reflect.Uint:
41 | ret[key] = int(val.(uint))
42 | case reflect.Uint8:
43 | ret[key] = int(val.(uint8))
44 | case reflect.Uint16:
45 | ret[key] = int(val.(uint16))
46 | case reflect.Uint32:
47 | ret[key] = int(val.(uint32))
48 | case reflect.Uint64:
49 | ret[key] = int(val.(uint64))
50 | case reflect.Float32:
51 | ret[key] = float64(val.(float32))
52 | case reflect.Float64:
53 | ret[key] = float64(val.(float64))
54 | case reflect.Map:
55 | if tmp, err := unmarshalMap(val); err == nil {
56 | ret[key] = tmp
57 | } else {
58 | return nil, err
59 | }
60 | case reflect.Array, reflect.Slice:
61 | ret[key] = val.([]interface{})
62 | default:
63 | return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind())
64 | }
65 | }
66 | return ret, nil
67 | }
68 |
69 | func (tm *tomlMap) UnmarshalTOML(i interface{}) error {
70 | if tmp, err := unmarshalMap(i); err == nil {
71 | tm.Map = tmp
72 | } else {
73 | return err
74 | }
75 | return nil
76 | }
77 |
78 | type tomlSourceContext struct {
79 | FilePath string
80 | }
81 |
82 | // NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath.
83 | func NewTomlSourceFromFile(file string) (InputSourceContext, error) {
84 | tsc := &tomlSourceContext{FilePath: file}
85 | var results tomlMap = tomlMap{}
86 | if err := readCommandToml(tsc.FilePath, &results); err != nil {
87 | return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error())
88 | }
89 | return &MapInputSource{valueMap: results.Map}, nil
90 | }
91 |
92 | // NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context.
93 | func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
94 | return func(context *cli.Context) (InputSourceContext, error) {
95 | filePath := context.String(flagFileName)
96 | return NewTomlSourceFromFile(filePath)
97 | }
98 | }
99 |
100 | func readCommandToml(filePath string, container interface{}) (err error) {
101 | b, err := loadDataFrom(filePath)
102 | if err != nil {
103 | return err
104 | }
105 |
106 | err = toml.Unmarshal(b, container)
107 | if err != nil {
108 | return err
109 | }
110 |
111 | err = nil
112 | return
113 | }
114 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | education, socio-economic status, nationality, personal appearance, race,
10 | religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
59 | reviewed and investigated and will result in a response that is deemed necessary
60 | and appropriate to the circumstances. The project team is obligated to maintain
61 | confidentiality with regard to the reporter of an incident. Further details of
62 | specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 |
--------------------------------------------------------------------------------
/runtests:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import print_function
3 |
4 | import argparse
5 | import os
6 | import sys
7 | import tempfile
8 |
9 | from subprocess import check_call, check_output
10 |
11 |
12 | PACKAGE_NAME = os.environ.get(
13 | 'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
14 | )
15 |
16 |
17 | def main(sysargs=sys.argv[:]):
18 | targets = {
19 | 'vet': _vet,
20 | 'test': _test,
21 | 'gfmrun': _gfmrun,
22 | 'toc': _toc,
23 | 'gen': _gen,
24 | }
25 |
26 | parser = argparse.ArgumentParser()
27 | parser.add_argument(
28 | 'target', nargs='?', choices=tuple(targets.keys()), default='test'
29 | )
30 | args = parser.parse_args(sysargs[1:])
31 |
32 | targets[args.target]()
33 | return 0
34 |
35 |
36 | def _test():
37 | if check_output('go version'.split()).split()[2] < 'go1.2':
38 | _run('go test -v .')
39 | return
40 |
41 | coverprofiles = []
42 | for subpackage in ['', 'altsrc']:
43 | coverprofile = 'cli.coverprofile'
44 | if subpackage != '':
45 | coverprofile = '{}.coverprofile'.format(subpackage)
46 |
47 | coverprofiles.append(coverprofile)
48 |
49 | _run('go test -v'.split() + [
50 | '-coverprofile={}'.format(coverprofile),
51 | ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
52 | ])
53 |
54 | combined_name = _combine_coverprofiles(coverprofiles)
55 | _run('go tool cover -func={}'.format(combined_name))
56 | os.remove(combined_name)
57 |
58 |
59 | def _gfmrun():
60 | go_version = check_output('go version'.split()).split()[2]
61 | if go_version < 'go1.3':
62 | print('runtests: skip on {}'.format(go_version), file=sys.stderr)
63 | return
64 | _run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
65 |
66 |
67 | def _vet():
68 | _run('go vet ./...')
69 |
70 |
71 | def _toc():
72 | _run('node_modules/.bin/markdown-toc -i README.md')
73 | _run('git diff --exit-code')
74 |
75 |
76 | def _gen():
77 | go_version = check_output('go version'.split()).split()[2]
78 | if go_version < 'go1.5':
79 | print('runtests: skip on {}'.format(go_version), file=sys.stderr)
80 | return
81 |
82 | _run('go generate ./...')
83 | _run('git diff --exit-code')
84 |
85 |
86 | def _run(command):
87 | if hasattr(command, 'split'):
88 | command = command.split()
89 | print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
90 | check_call(command)
91 |
92 |
93 | def _gfmrun_count():
94 | with open('README.md') as infile:
95 | lines = infile.read().splitlines()
96 | return len(filter(_is_go_runnable, lines))
97 |
98 |
99 | def _is_go_runnable(line):
100 | return line.startswith('package main')
101 |
102 |
103 | def _combine_coverprofiles(coverprofiles):
104 | combined = tempfile.NamedTemporaryFile(
105 | suffix='.coverprofile', delete=False
106 | )
107 | combined.write('mode: set\n')
108 |
109 | for coverprofile in coverprofiles:
110 | with open(coverprofile, 'r') as infile:
111 | for line in infile.readlines():
112 | if not line.startswith('mode: '):
113 | combined.write(line)
114 |
115 | combined.flush()
116 | name = combined.name
117 | combined.close()
118 | return name
119 |
120 |
121 | if __name__ == '__main__':
122 | sys.exit(main())
123 |
--------------------------------------------------------------------------------
/context.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "os"
7 | "reflect"
8 | "strings"
9 | "syscall"
10 | )
11 |
12 | // Context is a type that is passed through to
13 | // each Handler action in a cli application. Context
14 | // can be used to retrieve context-specific Args and
15 | // parsed command-line options.
16 | type Context struct {
17 | App *App
18 | Command Command
19 | shellComplete bool
20 | flagSet *flag.FlagSet
21 | setFlags map[string]bool
22 | parentContext *Context
23 | }
24 |
25 | // NewContext creates a new context. For use in when invoking an App or Command action.
26 | func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
27 | c := &Context{App: app, flagSet: set, parentContext: parentCtx}
28 |
29 | if parentCtx != nil {
30 | c.shellComplete = parentCtx.shellComplete
31 | }
32 |
33 | return c
34 | }
35 |
36 | // NumFlags returns the number of flags set
37 | func (c *Context) NumFlags() int {
38 | return c.flagSet.NFlag()
39 | }
40 |
41 | // Set sets a context flag to a value.
42 | func (c *Context) Set(name, value string) error {
43 | c.setFlags = nil
44 | return c.flagSet.Set(name, value)
45 | }
46 |
47 | // GlobalSet sets a context flag to a value on the global flagset
48 | func (c *Context) GlobalSet(name, value string) error {
49 | globalContext(c).setFlags = nil
50 | return globalContext(c).flagSet.Set(name, value)
51 | }
52 |
53 | // IsSet determines if the flag was actually set
54 | func (c *Context) IsSet(name string) bool {
55 | if c.setFlags == nil {
56 | c.setFlags = make(map[string]bool)
57 |
58 | c.flagSet.Visit(func(f *flag.Flag) {
59 | c.setFlags[f.Name] = true
60 | })
61 |
62 | c.flagSet.VisitAll(func(f *flag.Flag) {
63 | if _, ok := c.setFlags[f.Name]; ok {
64 | return
65 | }
66 | c.setFlags[f.Name] = false
67 | })
68 |
69 | // XXX hack to support IsSet for flags with EnvVar
70 | //
71 | // There isn't an easy way to do this with the current implementation since
72 | // whether a flag was set via an environment variable is very difficult to
73 | // determine here. Instead, we intend to introduce a backwards incompatible
74 | // change in version 2 to add `IsSet` to the Flag interface to push the
75 | // responsibility closer to where the information required to determine
76 | // whether a flag is set by non-standard means such as environment
77 | // variables is available.
78 | //
79 | // See https://github.com/urfave/cli/issues/294 for additional discussion
80 | flags := c.Command.Flags
81 | if c.Command.Name == "" { // cannot == Command{} since it contains slice types
82 | if c.App != nil {
83 | flags = c.App.Flags
84 | }
85 | }
86 | for _, f := range flags {
87 | eachName(f.GetName(), func(name string) {
88 | if isSet, ok := c.setFlags[name]; isSet || !ok {
89 | return
90 | }
91 |
92 | val := reflect.ValueOf(f)
93 | if val.Kind() == reflect.Ptr {
94 | val = val.Elem()
95 | }
96 |
97 | filePathValue := val.FieldByName("FilePath")
98 | if filePathValue.IsValid() {
99 | eachName(filePathValue.String(), func(filePath string) {
100 | if _, err := os.Stat(filePath); err == nil {
101 | c.setFlags[name] = true
102 | return
103 | }
104 | })
105 | }
106 |
107 | envVarValue := val.FieldByName("EnvVar")
108 | if envVarValue.IsValid() {
109 | eachName(envVarValue.String(), func(envVar string) {
110 | envVar = strings.TrimSpace(envVar)
111 | if _, ok := syscall.Getenv(envVar); ok {
112 | c.setFlags[name] = true
113 | return
114 | }
115 | })
116 | }
117 | })
118 | }
119 | }
120 |
121 | return c.setFlags[name]
122 | }
123 |
124 | // GlobalIsSet determines if the global flag was actually set
125 | func (c *Context) GlobalIsSet(name string) bool {
126 | ctx := c
127 | if ctx.parentContext != nil {
128 | ctx = ctx.parentContext
129 | }
130 |
131 | for ; ctx != nil; ctx = ctx.parentContext {
132 | if ctx.IsSet(name) {
133 | return true
134 | }
135 | }
136 | return false
137 | }
138 |
139 | // FlagNames returns a slice of flag names used in this context.
140 | func (c *Context) FlagNames() (names []string) {
141 | for _, flag := range c.Command.Flags {
142 | name := strings.Split(flag.GetName(), ",")[0]
143 | if name == "help" {
144 | continue
145 | }
146 | names = append(names, name)
147 | }
148 | return
149 | }
150 |
151 | // GlobalFlagNames returns a slice of global flag names used by the app.
152 | func (c *Context) GlobalFlagNames() (names []string) {
153 | for _, flag := range c.App.Flags {
154 | name := strings.Split(flag.GetName(), ",")[0]
155 | if name == "help" || name == "version" {
156 | continue
157 | }
158 | names = append(names, name)
159 | }
160 | return
161 | }
162 |
163 | // Parent returns the parent context, if any
164 | func (c *Context) Parent() *Context {
165 | return c.parentContext
166 | }
167 |
168 | // value returns the value of the flag coressponding to `name`
169 | func (c *Context) value(name string) interface{} {
170 | return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
171 | }
172 |
173 | // Args contains apps console arguments
174 | type Args []string
175 |
176 | // Args returns the command line arguments associated with the context.
177 | func (c *Context) Args() Args {
178 | args := Args(c.flagSet.Args())
179 | return args
180 | }
181 |
182 | // NArg returns the number of the command line arguments.
183 | func (c *Context) NArg() int {
184 | return len(c.Args())
185 | }
186 |
187 | // Get returns the nth argument, or else a blank string
188 | func (a Args) Get(n int) string {
189 | if len(a) > n {
190 | return a[n]
191 | }
192 | return ""
193 | }
194 |
195 | // First returns the first argument, or else a blank string
196 | func (a Args) First() string {
197 | return a.Get(0)
198 | }
199 |
200 | // Tail returns the rest of the arguments (not the first one)
201 | // or else an empty string slice
202 | func (a Args) Tail() []string {
203 | if len(a) >= 2 {
204 | return []string(a)[1:]
205 | }
206 | return []string{}
207 | }
208 |
209 | // Present checks if there are any arguments present
210 | func (a Args) Present() bool {
211 | return len(a) != 0
212 | }
213 |
214 | // Swap swaps arguments at the given indexes
215 | func (a Args) Swap(from, to int) error {
216 | if from >= len(a) || to >= len(a) {
217 | return errors.New("index out of range")
218 | }
219 | a[from], a[to] = a[to], a[from]
220 | return nil
221 | }
222 |
223 | func globalContext(ctx *Context) *Context {
224 | if ctx == nil {
225 | return nil
226 | }
227 |
228 | for {
229 | if ctx.parentContext == nil {
230 | return ctx
231 | }
232 | ctx = ctx.parentContext
233 | }
234 | }
235 |
236 | func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
237 | if ctx.parentContext != nil {
238 | ctx = ctx.parentContext
239 | }
240 | for ; ctx != nil; ctx = ctx.parentContext {
241 | if f := ctx.flagSet.Lookup(name); f != nil {
242 | return ctx.flagSet
243 | }
244 | }
245 | return nil
246 | }
247 |
248 | func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
249 | switch ff.Value.(type) {
250 | case *StringSlice:
251 | default:
252 | set.Set(name, ff.Value.String())
253 | }
254 | }
255 |
256 | func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
257 | visited := make(map[string]bool)
258 | set.Visit(func(f *flag.Flag) {
259 | visited[f.Name] = true
260 | })
261 | for _, f := range flags {
262 | parts := strings.Split(f.GetName(), ",")
263 | if len(parts) == 1 {
264 | continue
265 | }
266 | var ff *flag.Flag
267 | for _, name := range parts {
268 | name = strings.Trim(name, " ")
269 | if visited[name] {
270 | if ff != nil {
271 | return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
272 | }
273 | ff = set.Lookup(name)
274 | }
275 | }
276 | if ff == nil {
277 | continue
278 | }
279 | for _, name := range parts {
280 | name = strings.Trim(name, " ")
281 | if !visited[name] {
282 | copyFlag(name, ff, set)
283 | }
284 | }
285 | }
286 | return nil
287 | }
288 |
--------------------------------------------------------------------------------
/altsrc/map_input_source.go:
--------------------------------------------------------------------------------
1 | package altsrc
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "strings"
7 | "time"
8 |
9 | "gopkg.in/urfave/cli.v1"
10 | )
11 |
12 | // MapInputSource implements InputSourceContext to return
13 | // data from the map that is loaded.
14 | type MapInputSource struct {
15 | valueMap map[interface{}]interface{}
16 | }
17 |
18 | // nestedVal checks if the name has '.' delimiters.
19 | // If so, it tries to traverse the tree by the '.' delimited sections to find
20 | // a nested value for the key.
21 | func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool) {
22 | if sections := strings.Split(name, "."); len(sections) > 1 {
23 | node := tree
24 | for _, section := range sections[:len(sections)-1] {
25 | child, ok := node[section]
26 | if !ok {
27 | return nil, false
28 | }
29 | ctype, ok := child.(map[interface{}]interface{})
30 | if !ok {
31 | return nil, false
32 | }
33 | node = ctype
34 | }
35 | if val, ok := node[sections[len(sections)-1]]; ok {
36 | return val, true
37 | }
38 | }
39 | return nil, false
40 | }
41 |
42 | // Int returns an int from the map if it exists otherwise returns 0
43 | func (fsm *MapInputSource) Int(name string) (int, error) {
44 | otherGenericValue, exists := fsm.valueMap[name]
45 | if exists {
46 | otherValue, isType := otherGenericValue.(int)
47 | if !isType {
48 | return 0, incorrectTypeForFlagError(name, "int", otherGenericValue)
49 | }
50 | return otherValue, nil
51 | }
52 | nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
53 | if exists {
54 | otherValue, isType := nestedGenericValue.(int)
55 | if !isType {
56 | return 0, incorrectTypeForFlagError(name, "int", nestedGenericValue)
57 | }
58 | return otherValue, nil
59 | }
60 |
61 | return 0, nil
62 | }
63 |
64 | // Duration returns a duration from the map if it exists otherwise returns 0
65 | func (fsm *MapInputSource) Duration(name string) (time.Duration, error) {
66 | otherGenericValue, exists := fsm.valueMap[name]
67 | if exists {
68 | otherValue, isType := otherGenericValue.(time.Duration)
69 | if !isType {
70 | return 0, incorrectTypeForFlagError(name, "duration", otherGenericValue)
71 | }
72 | return otherValue, nil
73 | }
74 | nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
75 | if exists {
76 | otherValue, isType := nestedGenericValue.(time.Duration)
77 | if !isType {
78 | return 0, incorrectTypeForFlagError(name, "duration", nestedGenericValue)
79 | }
80 | return otherValue, nil
81 | }
82 |
83 | return 0, nil
84 | }
85 |
86 | // Float64 returns an float64 from the map if it exists otherwise returns 0
87 | func (fsm *MapInputSource) Float64(name string) (float64, error) {
88 | otherGenericValue, exists := fsm.valueMap[name]
89 | if exists {
90 | otherValue, isType := otherGenericValue.(float64)
91 | if !isType {
92 | return 0, incorrectTypeForFlagError(name, "float64", otherGenericValue)
93 | }
94 | return otherValue, nil
95 | }
96 | nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
97 | if exists {
98 | otherValue, isType := nestedGenericValue.(float64)
99 | if !isType {
100 | return 0, incorrectTypeForFlagError(name, "float64", nestedGenericValue)
101 | }
102 | return otherValue, nil
103 | }
104 |
105 | return 0, nil
106 | }
107 |
108 | // String returns a string from the map if it exists otherwise returns an empty string
109 | func (fsm *MapInputSource) String(name string) (string, error) {
110 | otherGenericValue, exists := fsm.valueMap[name]
111 | if exists {
112 | otherValue, isType := otherGenericValue.(string)
113 | if !isType {
114 | return "", incorrectTypeForFlagError(name, "string", otherGenericValue)
115 | }
116 | return otherValue, nil
117 | }
118 | nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
119 | if exists {
120 | otherValue, isType := nestedGenericValue.(string)
121 | if !isType {
122 | return "", incorrectTypeForFlagError(name, "string", nestedGenericValue)
123 | }
124 | return otherValue, nil
125 | }
126 |
127 | return "", nil
128 | }
129 |
130 | // StringSlice returns an []string from the map if it exists otherwise returns nil
131 | func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
132 | otherGenericValue, exists := fsm.valueMap[name]
133 | if !exists {
134 | otherGenericValue, exists = nestedVal(name, fsm.valueMap)
135 | if !exists {
136 | return nil, nil
137 | }
138 | }
139 |
140 | otherValue, isType := otherGenericValue.([]interface{})
141 | if !isType {
142 | return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue)
143 | }
144 |
145 | var stringSlice = make([]string, 0, len(otherValue))
146 | for i, v := range otherValue {
147 | stringValue, isType := v.(string)
148 |
149 | if !isType {
150 | return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "string", v)
151 | }
152 |
153 | stringSlice = append(stringSlice, stringValue)
154 | }
155 |
156 | return stringSlice, nil
157 | }
158 |
159 | // IntSlice returns an []int from the map if it exists otherwise returns nil
160 | func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
161 | otherGenericValue, exists := fsm.valueMap[name]
162 | if !exists {
163 | otherGenericValue, exists = nestedVal(name, fsm.valueMap)
164 | if !exists {
165 | return nil, nil
166 | }
167 | }
168 |
169 | otherValue, isType := otherGenericValue.([]interface{})
170 | if !isType {
171 | return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue)
172 | }
173 |
174 | var intSlice = make([]int, 0, len(otherValue))
175 | for i, v := range otherValue {
176 | intValue, isType := v.(int)
177 |
178 | if !isType {
179 | return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "int", v)
180 | }
181 |
182 | intSlice = append(intSlice, intValue)
183 | }
184 |
185 | return intSlice, nil
186 | }
187 |
188 | // Generic returns an cli.Generic from the map if it exists otherwise returns nil
189 | func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) {
190 | otherGenericValue, exists := fsm.valueMap[name]
191 | if exists {
192 | otherValue, isType := otherGenericValue.(cli.Generic)
193 | if !isType {
194 | return nil, incorrectTypeForFlagError(name, "cli.Generic", otherGenericValue)
195 | }
196 | return otherValue, nil
197 | }
198 | nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
199 | if exists {
200 | otherValue, isType := nestedGenericValue.(cli.Generic)
201 | if !isType {
202 | return nil, incorrectTypeForFlagError(name, "cli.Generic", nestedGenericValue)
203 | }
204 | return otherValue, nil
205 | }
206 |
207 | return nil, nil
208 | }
209 |
210 | // Bool returns an bool from the map otherwise returns false
211 | func (fsm *MapInputSource) Bool(name string) (bool, error) {
212 | otherGenericValue, exists := fsm.valueMap[name]
213 | if exists {
214 | otherValue, isType := otherGenericValue.(bool)
215 | if !isType {
216 | return false, incorrectTypeForFlagError(name, "bool", otherGenericValue)
217 | }
218 | return otherValue, nil
219 | }
220 | nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
221 | if exists {
222 | otherValue, isType := nestedGenericValue.(bool)
223 | if !isType {
224 | return false, incorrectTypeForFlagError(name, "bool", nestedGenericValue)
225 | }
226 | return otherValue, nil
227 | }
228 |
229 | return false, nil
230 | }
231 |
232 | // BoolT returns an bool from the map otherwise returns true
233 | func (fsm *MapInputSource) BoolT(name string) (bool, error) {
234 | otherGenericValue, exists := fsm.valueMap[name]
235 | if exists {
236 | otherValue, isType := otherGenericValue.(bool)
237 | if !isType {
238 | return true, incorrectTypeForFlagError(name, "bool", otherGenericValue)
239 | }
240 | return otherValue, nil
241 | }
242 | nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
243 | if exists {
244 | otherValue, isType := nestedGenericValue.(bool)
245 | if !isType {
246 | return true, incorrectTypeForFlagError(name, "bool", nestedGenericValue)
247 | }
248 | return otherValue, nil
249 | }
250 |
251 | return true, nil
252 | }
253 |
254 | func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error {
255 | valueType := reflect.TypeOf(value)
256 | valueTypeName := ""
257 | if valueType != nil {
258 | valueTypeName = valueType.Name()
259 | }
260 |
261 | return fmt.Errorf("Mismatched type for flag '%s'. Expected '%s' but actual is '%s'", name, expectedTypeName, valueTypeName)
262 | }
263 |
--------------------------------------------------------------------------------
/altsrc/flag.go:
--------------------------------------------------------------------------------
1 | package altsrc
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | "syscall"
8 |
9 | "gopkg.in/urfave/cli.v1"
10 | )
11 |
12 | // FlagInputSourceExtension is an extension interface of cli.Flag that
13 | // allows a value to be set on the existing parsed flags.
14 | type FlagInputSourceExtension interface {
15 | cli.Flag
16 | ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error
17 | }
18 |
19 | // ApplyInputSourceValues iterates over all provided flags and
20 | // executes ApplyInputSourceValue on flags implementing the
21 | // FlagInputSourceExtension interface to initialize these flags
22 | // to an alternate input source.
23 | func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error {
24 | for _, f := range flags {
25 | inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension)
26 | if isType {
27 | err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext)
28 | if err != nil {
29 | return err
30 | }
31 | }
32 | }
33 |
34 | return nil
35 | }
36 |
37 | // InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
38 | // input source based on the func provided. If there is no error it will then apply the new input source to any flags
39 | // that are supported by the input source
40 | func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc {
41 | return func(context *cli.Context) error {
42 | inputSource, err := createInputSource()
43 | if err != nil {
44 | return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error())
45 | }
46 |
47 | return ApplyInputSourceValues(context, inputSource, flags)
48 | }
49 | }
50 |
51 | // InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
52 | // input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is
53 | // no error it will then apply the new input source to any flags that are supported by the input source
54 | func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
55 | return func(context *cli.Context) error {
56 | inputSource, err := createInputSource(context)
57 | if err != nil {
58 | return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error())
59 | }
60 |
61 | return ApplyInputSourceValues(context, inputSource, flags)
62 | }
63 | }
64 |
65 | // ApplyInputSourceValue applies a generic value to the flagSet if required
66 | func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
67 | if f.set != nil {
68 | if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
69 | value, err := isc.Generic(f.GenericFlag.Name)
70 | if err != nil {
71 | return err
72 | }
73 | if value != nil {
74 | eachName(f.Name, func(name string) {
75 | f.set.Set(f.Name, value.String())
76 | })
77 | }
78 | }
79 | }
80 |
81 | return nil
82 | }
83 |
84 | // ApplyInputSourceValue applies a StringSlice value to the flagSet if required
85 | func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
86 | if f.set != nil {
87 | if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
88 | value, err := isc.StringSlice(f.StringSliceFlag.Name)
89 | if err != nil {
90 | return err
91 | }
92 | if value != nil {
93 | var sliceValue cli.StringSlice = value
94 | eachName(f.Name, func(name string) {
95 | underlyingFlag := f.set.Lookup(f.Name)
96 | if underlyingFlag != nil {
97 | underlyingFlag.Value = &sliceValue
98 | }
99 | })
100 | }
101 | }
102 | }
103 | return nil
104 | }
105 |
106 | // ApplyInputSourceValue applies a IntSlice value if required
107 | func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
108 | if f.set != nil {
109 | if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
110 | value, err := isc.IntSlice(f.IntSliceFlag.Name)
111 | if err != nil {
112 | return err
113 | }
114 | if value != nil {
115 | var sliceValue cli.IntSlice = value
116 | eachName(f.Name, func(name string) {
117 | underlyingFlag := f.set.Lookup(f.Name)
118 | if underlyingFlag != nil {
119 | underlyingFlag.Value = &sliceValue
120 | }
121 | })
122 | }
123 | }
124 | }
125 | return nil
126 | }
127 |
128 | // ApplyInputSourceValue applies a Bool value to the flagSet if required
129 | func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
130 | if f.set != nil {
131 | if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
132 | value, err := isc.Bool(f.BoolFlag.Name)
133 | if err != nil {
134 | return err
135 | }
136 | if value {
137 | eachName(f.Name, func(name string) {
138 | f.set.Set(f.Name, strconv.FormatBool(value))
139 | })
140 | }
141 | }
142 | }
143 | return nil
144 | }
145 |
146 | // ApplyInputSourceValue applies a BoolT value to the flagSet if required
147 | func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
148 | if f.set != nil {
149 | if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
150 | value, err := isc.BoolT(f.BoolTFlag.Name)
151 | if err != nil {
152 | return err
153 | }
154 | if !value {
155 | eachName(f.Name, func(name string) {
156 | f.set.Set(f.Name, strconv.FormatBool(value))
157 | })
158 | }
159 | }
160 | }
161 | return nil
162 | }
163 |
164 | // ApplyInputSourceValue applies a String value to the flagSet if required
165 | func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
166 | if f.set != nil {
167 | if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
168 | value, err := isc.String(f.StringFlag.Name)
169 | if err != nil {
170 | return err
171 | }
172 | if value != "" {
173 | eachName(f.Name, func(name string) {
174 | f.set.Set(f.Name, value)
175 | })
176 | }
177 | }
178 | }
179 | return nil
180 | }
181 |
182 | // ApplyInputSourceValue applies a int value to the flagSet if required
183 | func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
184 | if f.set != nil {
185 | if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
186 | value, err := isc.Int(f.IntFlag.Name)
187 | if err != nil {
188 | return err
189 | }
190 | if value > 0 {
191 | eachName(f.Name, func(name string) {
192 | f.set.Set(f.Name, strconv.FormatInt(int64(value), 10))
193 | })
194 | }
195 | }
196 | }
197 | return nil
198 | }
199 |
200 | // ApplyInputSourceValue applies a Duration value to the flagSet if required
201 | func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
202 | if f.set != nil {
203 | if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
204 | value, err := isc.Duration(f.DurationFlag.Name)
205 | if err != nil {
206 | return err
207 | }
208 | if value > 0 {
209 | eachName(f.Name, func(name string) {
210 | f.set.Set(f.Name, value.String())
211 | })
212 | }
213 | }
214 | }
215 | return nil
216 | }
217 |
218 | // ApplyInputSourceValue applies a Float64 value to the flagSet if required
219 | func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
220 | if f.set != nil {
221 | if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
222 | value, err := isc.Float64(f.Float64Flag.Name)
223 | if err != nil {
224 | return err
225 | }
226 | if value > 0 {
227 | floatStr := float64ToString(value)
228 | eachName(f.Name, func(name string) {
229 | f.set.Set(f.Name, floatStr)
230 | })
231 | }
232 | }
233 | }
234 | return nil
235 | }
236 |
237 | func isEnvVarSet(envVars string) bool {
238 | for _, envVar := range strings.Split(envVars, ",") {
239 | envVar = strings.TrimSpace(envVar)
240 | if _, ok := syscall.Getenv(envVar); ok {
241 | // TODO: Can't use this for bools as
242 | // set means that it was true or false based on
243 | // Bool flag type, should work for other types
244 | return true
245 | }
246 | }
247 |
248 | return false
249 | }
250 |
251 | func float64ToString(f float64) string {
252 | return fmt.Sprintf("%v", f)
253 | }
254 |
255 | func eachName(longName string, fn func(string)) {
256 | parts := strings.Split(longName, ",")
257 | for _, name := range parts {
258 | name = strings.Trim(name, " ")
259 | fn(name)
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/generate-flag-types:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | The flag types that ship with the cli library have many things in common, and
4 | so we can take advantage of the `go generate` command to create much of the
5 | source code from a list of definitions. These definitions attempt to cover
6 | the parts that vary between flag types, and should evolve as needed.
7 |
8 | An example of the minimum definition needed is:
9 |
10 | {
11 | "name": "SomeType",
12 | "type": "sometype",
13 | "context_default": "nil"
14 | }
15 |
16 | In this example, the code generated for the `cli` package will include a type
17 | named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
18 | Fetching values by name via `*cli.Context` will default to a value of `nil`.
19 |
20 | A more complete, albeit somewhat redundant, example showing all available
21 | definition keys is:
22 |
23 | {
24 | "name": "VeryMuchType",
25 | "type": "*VeryMuchType",
26 | "value": true,
27 | "dest": false,
28 | "doctail": " which really only wraps a []float64, oh well!",
29 | "context_type": "[]float64",
30 | "context_default": "nil",
31 | "parser": "parseVeryMuchType(f.Value.String())",
32 | "parser_cast": "[]float64(parsed)"
33 | }
34 |
35 | The meaning of each field is as follows:
36 |
37 | name (string) - The type "name", which will be suffixed with
38 | `Flag` when generating the type definition
39 | for `cli` and the wrapper type for `altsrc`
40 | type (string) - The type that the generated `Flag` type for `cli`
41 | is expected to "contain" as its `.Value` member
42 | value (bool) - Should the generated `cli` type have a `Value`
43 | member?
44 | dest (bool) - Should the generated `cli` type support a
45 | destination pointer?
46 | doctail (string) - Additional docs for the `cli` flag type comment
47 | context_type (string) - The literal type used in the `*cli.Context`
48 | reader func signature
49 | context_default (string) - The literal value used as the default by the
50 | `*cli.Context` reader funcs when no value is
51 | present
52 | parser (string) - Literal code used to parse the flag `f`,
53 | expected to have a return signature of
54 | (value, error)
55 | parser_cast (string) - Literal code used to cast the `parsed` value
56 | returned from the `parser` code
57 | """
58 |
59 | from __future__ import print_function, unicode_literals
60 |
61 | import argparse
62 | import json
63 | import os
64 | import subprocess
65 | import sys
66 | import tempfile
67 | import textwrap
68 |
69 |
70 | class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
71 | argparse.RawDescriptionHelpFormatter):
72 | pass
73 |
74 |
75 | def main(sysargs=sys.argv[:]):
76 | parser = argparse.ArgumentParser(
77 | description='Generate flag type code!',
78 | formatter_class=_FancyFormatter)
79 | parser.add_argument(
80 | 'package',
81 | type=str, default='cli', choices=_WRITEFUNCS.keys(),
82 | help='Package for which flag types will be generated'
83 | )
84 | parser.add_argument(
85 | '-i', '--in-json',
86 | type=argparse.FileType('r'),
87 | default=sys.stdin,
88 | help='Input JSON file which defines each type to be generated'
89 | )
90 | parser.add_argument(
91 | '-o', '--out-go',
92 | type=argparse.FileType('w'),
93 | default=sys.stdout,
94 | help='Output file/stream to which generated source will be written'
95 | )
96 | parser.epilog = __doc__
97 |
98 | args = parser.parse_args(sysargs[1:])
99 | _generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
100 | return 0
101 |
102 |
103 | def _generate_flag_types(writefunc, output_go, input_json):
104 | types = json.load(input_json)
105 |
106 | tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False)
107 | writefunc(tmp, types)
108 | tmp.close()
109 |
110 | new_content = subprocess.check_output(
111 | ['goimports', tmp.name]
112 | ).decode('utf-8')
113 |
114 | print(new_content, file=output_go, end='')
115 | output_go.flush()
116 | os.remove(tmp.name)
117 |
118 |
119 | def _set_typedef_defaults(typedef):
120 | typedef.setdefault('doctail', '')
121 | typedef.setdefault('context_type', typedef['type'])
122 | typedef.setdefault('dest', True)
123 | typedef.setdefault('value', True)
124 | typedef.setdefault('parser', 'f.Value, error(nil)')
125 | typedef.setdefault('parser_cast', 'parsed')
126 |
127 |
128 | def _write_cli_flag_types(outfile, types):
129 | _fwrite(outfile, """\
130 | package cli
131 |
132 | // WARNING: This file is generated!
133 |
134 | """)
135 |
136 | for typedef in types:
137 | _set_typedef_defaults(typedef)
138 |
139 | _fwrite(outfile, """\
140 | // {name}Flag is a flag with type {type}{doctail}
141 | type {name}Flag struct {{
142 | Name string
143 | Usage string
144 | EnvVar string
145 | FilePath string
146 | Hidden bool
147 | """.format(**typedef))
148 |
149 | if typedef['value']:
150 | _fwrite(outfile, """\
151 | Value {type}
152 | """.format(**typedef))
153 |
154 | if typedef['dest']:
155 | _fwrite(outfile, """\
156 | Destination *{type}
157 | """.format(**typedef))
158 |
159 | _fwrite(outfile, "\n}\n\n")
160 |
161 | _fwrite(outfile, """\
162 | // String returns a readable representation of this value
163 | // (for usage defaults)
164 | func (f {name}Flag) String() string {{
165 | return FlagStringer(f)
166 | }}
167 |
168 | // GetName returns the name of the flag
169 | func (f {name}Flag) GetName() string {{
170 | return f.Name
171 | }}
172 |
173 | // {name} looks up the value of a local {name}Flag, returns
174 | // {context_default} if not found
175 | func (c *Context) {name}(name string) {context_type} {{
176 | return lookup{name}(name, c.flagSet)
177 | }}
178 |
179 | // Global{name} looks up the value of a global {name}Flag, returns
180 | // {context_default} if not found
181 | func (c *Context) Global{name}(name string) {context_type} {{
182 | if fs := lookupGlobalFlagSet(name, c); fs != nil {{
183 | return lookup{name}(name, fs)
184 | }}
185 | return {context_default}
186 | }}
187 |
188 | func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
189 | f := set.Lookup(name)
190 | if f != nil {{
191 | parsed, err := {parser}
192 | if err != nil {{
193 | return {context_default}
194 | }}
195 | return {parser_cast}
196 | }}
197 | return {context_default}
198 | }}
199 | """.format(**typedef))
200 |
201 |
202 | def _write_altsrc_flag_types(outfile, types):
203 | _fwrite(outfile, """\
204 | package altsrc
205 |
206 | import (
207 | "gopkg.in/urfave/cli.v1"
208 | )
209 |
210 | // WARNING: This file is generated!
211 |
212 | """)
213 |
214 | for typedef in types:
215 | _set_typedef_defaults(typedef)
216 |
217 | _fwrite(outfile, """\
218 | // {name}Flag is the flag type that wraps cli.{name}Flag to allow
219 | // for other values to be specified
220 | type {name}Flag struct {{
221 | cli.{name}Flag
222 | set *flag.FlagSet
223 | }}
224 |
225 | // New{name}Flag creates a new {name}Flag
226 | func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{
227 | return &{name}Flag{{{name}Flag: fl, set: nil}}
228 | }}
229 |
230 | // Apply saves the flagSet for later usage calls, then calls the
231 | // wrapped {name}Flag.Apply
232 | func (f *{name}Flag) Apply(set *flag.FlagSet) {{
233 | f.set = set
234 | f.{name}Flag.Apply(set)
235 | }}
236 |
237 | // ApplyWithError saves the flagSet for later usage calls, then calls the
238 | // wrapped {name}Flag.ApplyWithError
239 | func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
240 | f.set = set
241 | return f.{name}Flag.ApplyWithError(set)
242 | }}
243 | """.format(**typedef))
244 |
245 |
246 | def _fwrite(outfile, text):
247 | print(textwrap.dedent(text), end='', file=outfile)
248 |
249 |
250 | _WRITEFUNCS = {
251 | 'cli': _write_cli_flag_types,
252 | 'altsrc': _write_altsrc_flag_types
253 | }
254 |
255 | if __name__ == '__main__':
256 | sys.exit(main())
257 |
--------------------------------------------------------------------------------
/altsrc/toml_command_test.go:
--------------------------------------------------------------------------------
1 | // Disabling building of toml support in cases where golang is 1.0 or 1.1
2 | // as the encoding library is not implemented or supported.
3 |
4 | // +build go1.2
5 |
6 | package altsrc
7 |
8 | import (
9 | "flag"
10 | "io/ioutil"
11 | "os"
12 | "testing"
13 |
14 | "gopkg.in/urfave/cli.v1"
15 | )
16 |
17 | func TestCommandTomFileTest(t *testing.T) {
18 | app := cli.NewApp()
19 | set := flag.NewFlagSet("test", 0)
20 | ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
21 | defer os.Remove("current.toml")
22 | test := []string{"test-cmd", "--load", "current.toml"}
23 | set.Parse(test)
24 |
25 | c := cli.NewContext(app, set, nil)
26 |
27 | command := &cli.Command{
28 | Name: "test-cmd",
29 | Aliases: []string{"tc"},
30 | Usage: "this is for testing",
31 | Description: "testing",
32 | Action: func(c *cli.Context) error {
33 | val := c.Int("test")
34 | expect(t, val, 15)
35 | return nil
36 | },
37 | Flags: []cli.Flag{
38 | NewIntFlag(cli.IntFlag{Name: "test"}),
39 | cli.StringFlag{Name: "load"}},
40 | }
41 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
42 | err := command.Run(c)
43 |
44 | expect(t, err, nil)
45 | }
46 |
47 | func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) {
48 | app := cli.NewApp()
49 | set := flag.NewFlagSet("test", 0)
50 | ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
51 | defer os.Remove("current.toml")
52 |
53 | os.Setenv("THE_TEST", "10")
54 | defer os.Setenv("THE_TEST", "")
55 | test := []string{"test-cmd", "--load", "current.toml"}
56 | set.Parse(test)
57 |
58 | c := cli.NewContext(app, set, nil)
59 |
60 | command := &cli.Command{
61 | Name: "test-cmd",
62 | Aliases: []string{"tc"},
63 | Usage: "this is for testing",
64 | Description: "testing",
65 | Action: func(c *cli.Context) error {
66 | val := c.Int("test")
67 | expect(t, val, 10)
68 | return nil
69 | },
70 | Flags: []cli.Flag{
71 | NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
72 | cli.StringFlag{Name: "load"}},
73 | }
74 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
75 |
76 | err := command.Run(c)
77 |
78 | expect(t, err, nil)
79 | }
80 |
81 | func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) {
82 | app := cli.NewApp()
83 | set := flag.NewFlagSet("test", 0)
84 | ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
85 | defer os.Remove("current.toml")
86 |
87 | os.Setenv("THE_TEST", "10")
88 | defer os.Setenv("THE_TEST", "")
89 | test := []string{"test-cmd", "--load", "current.toml"}
90 | set.Parse(test)
91 |
92 | c := cli.NewContext(app, set, nil)
93 |
94 | command := &cli.Command{
95 | Name: "test-cmd",
96 | Aliases: []string{"tc"},
97 | Usage: "this is for testing",
98 | Description: "testing",
99 | Action: func(c *cli.Context) error {
100 | val := c.Int("top.test")
101 | expect(t, val, 10)
102 | return nil
103 | },
104 | Flags: []cli.Flag{
105 | NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}),
106 | cli.StringFlag{Name: "load"}},
107 | }
108 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
109 |
110 | err := command.Run(c)
111 |
112 | expect(t, err, nil)
113 | }
114 |
115 | func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) {
116 | app := cli.NewApp()
117 | set := flag.NewFlagSet("test", 0)
118 | ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
119 | defer os.Remove("current.toml")
120 |
121 | test := []string{"test-cmd", "--load", "current.toml", "--test", "7"}
122 | set.Parse(test)
123 |
124 | c := cli.NewContext(app, set, nil)
125 |
126 | command := &cli.Command{
127 | Name: "test-cmd",
128 | Aliases: []string{"tc"},
129 | Usage: "this is for testing",
130 | Description: "testing",
131 | Action: func(c *cli.Context) error {
132 | val := c.Int("test")
133 | expect(t, val, 7)
134 | return nil
135 | },
136 | Flags: []cli.Flag{
137 | NewIntFlag(cli.IntFlag{Name: "test"}),
138 | cli.StringFlag{Name: "load"}},
139 | }
140 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
141 |
142 | err := command.Run(c)
143 |
144 | expect(t, err, nil)
145 | }
146 |
147 | func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) {
148 | app := cli.NewApp()
149 | set := flag.NewFlagSet("test", 0)
150 | ioutil.WriteFile("current.toml", []byte(`[top]
151 | test = 15`), 0666)
152 | defer os.Remove("current.toml")
153 |
154 | test := []string{"test-cmd", "--load", "current.toml", "--top.test", "7"}
155 | set.Parse(test)
156 |
157 | c := cli.NewContext(app, set, nil)
158 |
159 | command := &cli.Command{
160 | Name: "test-cmd",
161 | Aliases: []string{"tc"},
162 | Usage: "this is for testing",
163 | Description: "testing",
164 | Action: func(c *cli.Context) error {
165 | val := c.Int("top.test")
166 | expect(t, val, 7)
167 | return nil
168 | },
169 | Flags: []cli.Flag{
170 | NewIntFlag(cli.IntFlag{Name: "top.test"}),
171 | cli.StringFlag{Name: "load"}},
172 | }
173 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
174 |
175 | err := command.Run(c)
176 |
177 | expect(t, err, nil)
178 | }
179 |
180 | func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) {
181 | app := cli.NewApp()
182 | set := flag.NewFlagSet("test", 0)
183 | ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
184 | defer os.Remove("current.toml")
185 |
186 | test := []string{"test-cmd", "--load", "current.toml"}
187 | set.Parse(test)
188 |
189 | c := cli.NewContext(app, set, nil)
190 |
191 | command := &cli.Command{
192 | Name: "test-cmd",
193 | Aliases: []string{"tc"},
194 | Usage: "this is for testing",
195 | Description: "testing",
196 | Action: func(c *cli.Context) error {
197 | val := c.Int("test")
198 | expect(t, val, 15)
199 | return nil
200 | },
201 | Flags: []cli.Flag{
202 | NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
203 | cli.StringFlag{Name: "load"}},
204 | }
205 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
206 |
207 | err := command.Run(c)
208 |
209 | expect(t, err, nil)
210 | }
211 |
212 | func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) {
213 | app := cli.NewApp()
214 | set := flag.NewFlagSet("test", 0)
215 | ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
216 | defer os.Remove("current.toml")
217 |
218 | test := []string{"test-cmd", "--load", "current.toml"}
219 | set.Parse(test)
220 |
221 | c := cli.NewContext(app, set, nil)
222 |
223 | command := &cli.Command{
224 | Name: "test-cmd",
225 | Aliases: []string{"tc"},
226 | Usage: "this is for testing",
227 | Description: "testing",
228 | Action: func(c *cli.Context) error {
229 | val := c.Int("top.test")
230 | expect(t, val, 15)
231 | return nil
232 | },
233 | Flags: []cli.Flag{
234 | NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}),
235 | cli.StringFlag{Name: "load"}},
236 | }
237 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
238 |
239 | err := command.Run(c)
240 |
241 | expect(t, err, nil)
242 | }
243 |
244 | func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) {
245 | app := cli.NewApp()
246 | set := flag.NewFlagSet("test", 0)
247 | ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
248 | defer os.Remove("current.toml")
249 |
250 | os.Setenv("THE_TEST", "11")
251 | defer os.Setenv("THE_TEST", "")
252 |
253 | test := []string{"test-cmd", "--load", "current.toml"}
254 | set.Parse(test)
255 |
256 | c := cli.NewContext(app, set, nil)
257 |
258 | command := &cli.Command{
259 | Name: "test-cmd",
260 | Aliases: []string{"tc"},
261 | Usage: "this is for testing",
262 | Description: "testing",
263 | Action: func(c *cli.Context) error {
264 | val := c.Int("test")
265 | expect(t, val, 11)
266 | return nil
267 | },
268 | Flags: []cli.Flag{
269 | NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
270 | cli.StringFlag{Name: "load"}},
271 | }
272 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
273 | err := command.Run(c)
274 |
275 | expect(t, err, nil)
276 | }
277 |
278 | func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *testing.T) {
279 | app := cli.NewApp()
280 | set := flag.NewFlagSet("test", 0)
281 | ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
282 | defer os.Remove("current.toml")
283 |
284 | os.Setenv("THE_TEST", "11")
285 | defer os.Setenv("THE_TEST", "")
286 |
287 | test := []string{"test-cmd", "--load", "current.toml"}
288 | set.Parse(test)
289 |
290 | c := cli.NewContext(app, set, nil)
291 |
292 | command := &cli.Command{
293 | Name: "test-cmd",
294 | Aliases: []string{"tc"},
295 | Usage: "this is for testing",
296 | Description: "testing",
297 | Action: func(c *cli.Context) error {
298 | val := c.Int("top.test")
299 | expect(t, val, 11)
300 | return nil
301 | },
302 | Flags: []cli.Flag{
303 | NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}),
304 | cli.StringFlag{Name: "load"}},
305 | }
306 | command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
307 | err := command.Run(c)
308 |
309 | expect(t, err, nil)
310 | }
311 |
--------------------------------------------------------------------------------
/altsrc/yaml_command_test.go:
--------------------------------------------------------------------------------
1 | // Disabling building of yaml support in cases where golang is 1.0 or 1.1
2 | // as the encoding library is not implemented or supported.
3 |
4 | // +build go1.2
5 |
6 | package altsrc
7 |
8 | import (
9 | "flag"
10 | "io/ioutil"
11 | "os"
12 | "testing"
13 |
14 | "gopkg.in/urfave/cli.v1"
15 | )
16 |
17 | func TestCommandYamlFileTest(t *testing.T) {
18 | app := cli.NewApp()
19 | set := flag.NewFlagSet("test", 0)
20 | ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
21 | defer os.Remove("current.yaml")
22 | test := []string{"test-cmd", "--load", "current.yaml"}
23 | set.Parse(test)
24 |
25 | c := cli.NewContext(app, set, nil)
26 |
27 | command := &cli.Command{
28 | Name: "test-cmd",
29 | Aliases: []string{"tc"},
30 | Usage: "this is for testing",
31 | Description: "testing",
32 | Action: func(c *cli.Context) error {
33 | val := c.Int("test")
34 | expect(t, val, 15)
35 | return nil
36 | },
37 | Flags: []cli.Flag{
38 | NewIntFlag(cli.IntFlag{Name: "test"}),
39 | cli.StringFlag{Name: "load"}},
40 | }
41 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
42 | err := command.Run(c)
43 |
44 | expect(t, err, nil)
45 | }
46 |
47 | func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) {
48 | app := cli.NewApp()
49 | set := flag.NewFlagSet("test", 0)
50 | ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
51 | defer os.Remove("current.yaml")
52 |
53 | os.Setenv("THE_TEST", "10")
54 | defer os.Setenv("THE_TEST", "")
55 | test := []string{"test-cmd", "--load", "current.yaml"}
56 | set.Parse(test)
57 |
58 | c := cli.NewContext(app, set, nil)
59 |
60 | command := &cli.Command{
61 | Name: "test-cmd",
62 | Aliases: []string{"tc"},
63 | Usage: "this is for testing",
64 | Description: "testing",
65 | Action: func(c *cli.Context) error {
66 | val := c.Int("test")
67 | expect(t, val, 10)
68 | return nil
69 | },
70 | Flags: []cli.Flag{
71 | NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
72 | cli.StringFlag{Name: "load"}},
73 | }
74 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
75 |
76 | err := command.Run(c)
77 |
78 | expect(t, err, nil)
79 | }
80 |
81 | func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) {
82 | app := cli.NewApp()
83 | set := flag.NewFlagSet("test", 0)
84 | ioutil.WriteFile("current.yaml", []byte(`top:
85 | test: 15`), 0666)
86 | defer os.Remove("current.yaml")
87 |
88 | os.Setenv("THE_TEST", "10")
89 | defer os.Setenv("THE_TEST", "")
90 | test := []string{"test-cmd", "--load", "current.yaml"}
91 | set.Parse(test)
92 |
93 | c := cli.NewContext(app, set, nil)
94 |
95 | command := &cli.Command{
96 | Name: "test-cmd",
97 | Aliases: []string{"tc"},
98 | Usage: "this is for testing",
99 | Description: "testing",
100 | Action: func(c *cli.Context) error {
101 | val := c.Int("top.test")
102 | expect(t, val, 10)
103 | return nil
104 | },
105 | Flags: []cli.Flag{
106 | NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}),
107 | cli.StringFlag{Name: "load"}},
108 | }
109 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
110 |
111 | err := command.Run(c)
112 |
113 | expect(t, err, nil)
114 | }
115 |
116 | func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
117 | app := cli.NewApp()
118 | set := flag.NewFlagSet("test", 0)
119 | ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
120 | defer os.Remove("current.yaml")
121 |
122 | test := []string{"test-cmd", "--load", "current.yaml", "--test", "7"}
123 | set.Parse(test)
124 |
125 | c := cli.NewContext(app, set, nil)
126 |
127 | command := &cli.Command{
128 | Name: "test-cmd",
129 | Aliases: []string{"tc"},
130 | Usage: "this is for testing",
131 | Description: "testing",
132 | Action: func(c *cli.Context) error {
133 | val := c.Int("test")
134 | expect(t, val, 7)
135 | return nil
136 | },
137 | Flags: []cli.Flag{
138 | NewIntFlag(cli.IntFlag{Name: "test"}),
139 | cli.StringFlag{Name: "load"}},
140 | }
141 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
142 |
143 | err := command.Run(c)
144 |
145 | expect(t, err, nil)
146 | }
147 |
148 | func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) {
149 | app := cli.NewApp()
150 | set := flag.NewFlagSet("test", 0)
151 | ioutil.WriteFile("current.yaml", []byte(`top:
152 | test: 15`), 0666)
153 | defer os.Remove("current.yaml")
154 |
155 | test := []string{"test-cmd", "--load", "current.yaml", "--top.test", "7"}
156 | set.Parse(test)
157 |
158 | c := cli.NewContext(app, set, nil)
159 |
160 | command := &cli.Command{
161 | Name: "test-cmd",
162 | Aliases: []string{"tc"},
163 | Usage: "this is for testing",
164 | Description: "testing",
165 | Action: func(c *cli.Context) error {
166 | val := c.Int("top.test")
167 | expect(t, val, 7)
168 | return nil
169 | },
170 | Flags: []cli.Flag{
171 | NewIntFlag(cli.IntFlag{Name: "top.test"}),
172 | cli.StringFlag{Name: "load"}},
173 | }
174 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
175 |
176 | err := command.Run(c)
177 |
178 | expect(t, err, nil)
179 | }
180 |
181 | func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
182 | app := cli.NewApp()
183 | set := flag.NewFlagSet("test", 0)
184 | ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
185 | defer os.Remove("current.yaml")
186 |
187 | test := []string{"test-cmd", "--load", "current.yaml"}
188 | set.Parse(test)
189 |
190 | c := cli.NewContext(app, set, nil)
191 |
192 | command := &cli.Command{
193 | Name: "test-cmd",
194 | Aliases: []string{"tc"},
195 | Usage: "this is for testing",
196 | Description: "testing",
197 | Action: func(c *cli.Context) error {
198 | val := c.Int("test")
199 | expect(t, val, 15)
200 | return nil
201 | },
202 | Flags: []cli.Flag{
203 | NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
204 | cli.StringFlag{Name: "load"}},
205 | }
206 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
207 |
208 | err := command.Run(c)
209 |
210 | expect(t, err, nil)
211 | }
212 |
213 | func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) {
214 | app := cli.NewApp()
215 | set := flag.NewFlagSet("test", 0)
216 | ioutil.WriteFile("current.yaml", []byte(`top:
217 | test: 15`), 0666)
218 | defer os.Remove("current.yaml")
219 |
220 | test := []string{"test-cmd", "--load", "current.yaml"}
221 | set.Parse(test)
222 |
223 | c := cli.NewContext(app, set, nil)
224 |
225 | command := &cli.Command{
226 | Name: "test-cmd",
227 | Aliases: []string{"tc"},
228 | Usage: "this is for testing",
229 | Description: "testing",
230 | Action: func(c *cli.Context) error {
231 | val := c.Int("top.test")
232 | expect(t, val, 15)
233 | return nil
234 | },
235 | Flags: []cli.Flag{
236 | NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}),
237 | cli.StringFlag{Name: "load"}},
238 | }
239 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
240 |
241 | err := command.Run(c)
242 |
243 | expect(t, err, nil)
244 | }
245 |
246 | func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) {
247 | app := cli.NewApp()
248 | set := flag.NewFlagSet("test", 0)
249 | ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
250 | defer os.Remove("current.yaml")
251 |
252 | os.Setenv("THE_TEST", "11")
253 | defer os.Setenv("THE_TEST", "")
254 |
255 | test := []string{"test-cmd", "--load", "current.yaml"}
256 | set.Parse(test)
257 |
258 | c := cli.NewContext(app, set, nil)
259 |
260 | command := &cli.Command{
261 | Name: "test-cmd",
262 | Aliases: []string{"tc"},
263 | Usage: "this is for testing",
264 | Description: "testing",
265 | Action: func(c *cli.Context) error {
266 | val := c.Int("test")
267 | expect(t, val, 11)
268 | return nil
269 | },
270 | Flags: []cli.Flag{
271 | NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
272 | cli.StringFlag{Name: "load"}},
273 | }
274 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
275 | err := command.Run(c)
276 |
277 | expect(t, err, nil)
278 | }
279 |
280 | func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) {
281 | app := cli.NewApp()
282 | set := flag.NewFlagSet("test", 0)
283 | ioutil.WriteFile("current.yaml", []byte(`top:
284 | test: 15`), 0666)
285 | defer os.Remove("current.yaml")
286 |
287 | os.Setenv("THE_TEST", "11")
288 | defer os.Setenv("THE_TEST", "")
289 |
290 | test := []string{"test-cmd", "--load", "current.yaml"}
291 | set.Parse(test)
292 |
293 | c := cli.NewContext(app, set, nil)
294 |
295 | command := &cli.Command{
296 | Name: "test-cmd",
297 | Aliases: []string{"tc"},
298 | Usage: "this is for testing",
299 | Description: "testing",
300 | Action: func(c *cli.Context) error {
301 | val := c.Int("top.test")
302 | expect(t, val, 11)
303 | return nil
304 | },
305 | Flags: []cli.Flag{
306 | NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}),
307 | cli.StringFlag{Name: "load"}},
308 | }
309 | command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
310 | err := command.Run(c)
311 |
312 | expect(t, err, nil)
313 | }
314 |
--------------------------------------------------------------------------------
/command_test.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "fmt"
7 | "io/ioutil"
8 | "strings"
9 | "testing"
10 | )
11 |
12 | func TestCommandFlagParsing(t *testing.T) {
13 | cases := []struct {
14 | testArgs []string
15 | skipFlagParsing bool
16 | skipArgReorder bool
17 | expectedErr error
18 | UseShortOptionHandling bool
19 | }{
20 | // Test normal "not ignoring flags" flow
21 | {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break"), false},
22 |
23 | // Test no arg reorder
24 | {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false},
25 | {[]string{"test-cmd", "blah", "blah", "-break", "ls", "-l"}, false, true, nil, true},
26 |
27 | {[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags
28 | {[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg
29 | {[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg
30 | {[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling
31 |
32 | }
33 |
34 | for _, c := range cases {
35 | app := NewApp()
36 | app.Writer = ioutil.Discard
37 | set := flag.NewFlagSet("test", 0)
38 | set.Parse(c.testArgs)
39 |
40 | context := NewContext(app, set, nil)
41 |
42 | command := Command{
43 | Name: "test-cmd",
44 | Aliases: []string{"tc"},
45 | Usage: "this is for testing",
46 | Description: "testing",
47 | Action: func(_ *Context) error { return nil },
48 | SkipFlagParsing: c.skipFlagParsing,
49 | SkipArgReorder: c.skipArgReorder,
50 | UseShortOptionHandling: c.UseShortOptionHandling,
51 | }
52 |
53 | err := command.Run(context)
54 |
55 | expect(t, err, c.expectedErr)
56 | expect(t, []string(context.Args()), c.testArgs)
57 | }
58 | }
59 |
60 | func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
61 | app := NewApp()
62 | app.Commands = []Command{
63 | {
64 | Name: "bar",
65 | Before: func(c *Context) error {
66 | return fmt.Errorf("before error")
67 | },
68 | After: func(c *Context) error {
69 | return fmt.Errorf("after error")
70 | },
71 | },
72 | }
73 |
74 | err := app.Run([]string{"foo", "bar"})
75 | if err == nil {
76 | t.Fatalf("expected to receive error from Run, got none")
77 | }
78 |
79 | if !strings.Contains(err.Error(), "before error") {
80 | t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
81 | }
82 | if !strings.Contains(err.Error(), "after error") {
83 | t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
84 | }
85 | }
86 |
87 | func TestCommand_Run_BeforeSavesMetadata(t *testing.T) {
88 | var receivedMsgFromAction string
89 | var receivedMsgFromAfter string
90 |
91 | app := NewApp()
92 | app.Commands = []Command{
93 | {
94 | Name: "bar",
95 | Before: func(c *Context) error {
96 | c.App.Metadata["msg"] = "hello world"
97 | return nil
98 | },
99 | Action: func(c *Context) error {
100 | msg, ok := c.App.Metadata["msg"]
101 | if !ok {
102 | return errors.New("msg not found")
103 | }
104 | receivedMsgFromAction = msg.(string)
105 | return nil
106 | },
107 | After: func(c *Context) error {
108 | msg, ok := c.App.Metadata["msg"]
109 | if !ok {
110 | return errors.New("msg not found")
111 | }
112 | receivedMsgFromAfter = msg.(string)
113 | return nil
114 | },
115 | },
116 | }
117 |
118 | err := app.Run([]string{"foo", "bar"})
119 | if err != nil {
120 | t.Fatalf("expected no error from Run, got %s", err)
121 | }
122 |
123 | expectedMsg := "hello world"
124 |
125 | if receivedMsgFromAction != expectedMsg {
126 | t.Fatalf("expected msg from Action to match. Given: %q\nExpected: %q",
127 | receivedMsgFromAction, expectedMsg)
128 | }
129 | if receivedMsgFromAfter != expectedMsg {
130 | t.Fatalf("expected msg from After to match. Given: %q\nExpected: %q",
131 | receivedMsgFromAction, expectedMsg)
132 | }
133 | }
134 |
135 | func TestCommand_OnUsageError_hasCommandContext(t *testing.T) {
136 | app := NewApp()
137 | app.Commands = []Command{
138 | {
139 | Name: "bar",
140 | Flags: []Flag{
141 | IntFlag{Name: "flag"},
142 | },
143 | OnUsageError: func(c *Context, err error, _ bool) error {
144 | return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error())
145 | },
146 | },
147 | }
148 |
149 | err := app.Run([]string{"foo", "bar", "--flag=wrong"})
150 | if err == nil {
151 | t.Fatalf("expected to receive error from Run, got none")
152 | }
153 |
154 | if !strings.HasPrefix(err.Error(), "intercepted in bar") {
155 | t.Errorf("Expect an intercepted error, but got \"%v\"", err)
156 | }
157 | }
158 |
159 | func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
160 | app := NewApp()
161 | app.Commands = []Command{
162 | {
163 | Name: "bar",
164 | Flags: []Flag{
165 | IntFlag{Name: "flag"},
166 | },
167 | OnUsageError: func(c *Context, err error, _ bool) error {
168 | if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
169 | t.Errorf("Expect an invalid value error, but got \"%v\"", err)
170 | }
171 | return errors.New("intercepted: " + err.Error())
172 | },
173 | },
174 | }
175 |
176 | err := app.Run([]string{"foo", "bar", "--flag=wrong"})
177 | if err == nil {
178 | t.Fatalf("expected to receive error from Run, got none")
179 | }
180 |
181 | if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
182 | t.Errorf("Expect an intercepted error, but got \"%v\"", err)
183 | }
184 | }
185 |
186 | func TestCommand_OnUsageError_WithSubcommand(t *testing.T) {
187 | app := NewApp()
188 | app.Commands = []Command{
189 | {
190 | Name: "bar",
191 | Subcommands: []Command{
192 | {
193 | Name: "baz",
194 | },
195 | },
196 | Flags: []Flag{
197 | IntFlag{Name: "flag"},
198 | },
199 | OnUsageError: func(c *Context, err error, _ bool) error {
200 | if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
201 | t.Errorf("Expect an invalid value error, but got \"%v\"", err)
202 | }
203 | return errors.New("intercepted: " + err.Error())
204 | },
205 | },
206 | }
207 |
208 | err := app.Run([]string{"foo", "bar", "--flag=wrong"})
209 | if err == nil {
210 | t.Fatalf("expected to receive error from Run, got none")
211 | }
212 |
213 | if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
214 | t.Errorf("Expect an intercepted error, but got \"%v\"", err)
215 | }
216 | }
217 |
218 | func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
219 | app := NewApp()
220 | app.ErrWriter = ioutil.Discard
221 | app.Commands = []Command{
222 | {
223 | Name: "bar",
224 | Usage: "this is for testing",
225 | Subcommands: []Command{
226 | {
227 | Name: "baz",
228 | Usage: "this is for testing",
229 | Action: func(c *Context) error {
230 | if c.App.ErrWriter != ioutil.Discard {
231 | return fmt.Errorf("ErrWriter not passed")
232 | }
233 |
234 | return nil
235 | },
236 | },
237 | },
238 | },
239 | }
240 |
241 | err := app.Run([]string{"foo", "bar", "baz"})
242 | if err != nil {
243 | t.Fatal(err)
244 | }
245 | }
246 |
247 | func TestCommandFlagReordering(t *testing.T) {
248 | cases := []struct {
249 | testArgs []string
250 | expectedValue string
251 | expectedArgs []string
252 | expectedErr error
253 | }{
254 | {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, "foo", []string{"some-arg"}, nil},
255 | {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, "foo", []string{"some-arg"}, nil},
256 | {[]string{"some-exec", "some-command", "--flag=foo", "some-arg"}, "foo", []string{"some-arg"}, nil},
257 | }
258 |
259 | for _, c := range cases {
260 | value := ""
261 | args := []string{}
262 | app := &App{
263 | Commands: []Command{
264 | {
265 | Name: "some-command",
266 | Flags: []Flag{
267 | StringFlag{Name: "flag"},
268 | },
269 | Action: func(c *Context) {
270 | fmt.Printf("%+v\n", c.String("flag"))
271 | value = c.String("flag")
272 | args = c.Args()
273 | },
274 | },
275 | },
276 | }
277 |
278 | err := app.Run(c.testArgs)
279 | expect(t, err, c.expectedErr)
280 | expect(t, value, c.expectedValue)
281 | expect(t, args, c.expectedArgs)
282 | }
283 | }
284 |
285 | func TestCommandSkipFlagParsing(t *testing.T) {
286 | cases := []struct {
287 | testArgs []string
288 | expectedArgs []string
289 | expectedErr error
290 | }{
291 | {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, []string{"some-arg", "--flag", "foo"}, nil},
292 | {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, []string{"some-arg", "--flag=foo"}, nil},
293 | }
294 |
295 | for _, c := range cases {
296 | value := ""
297 | args := []string{}
298 | app := &App{
299 | Commands: []Command{
300 | {
301 | SkipFlagParsing: true,
302 | Name: "some-command",
303 | Flags: []Flag{
304 | StringFlag{Name: "flag"},
305 | },
306 | Action: func(c *Context) {
307 | fmt.Printf("%+v\n", c.String("flag"))
308 | value = c.String("flag")
309 | args = c.Args()
310 | },
311 | },
312 | },
313 | }
314 |
315 | err := app.Run(c.testArgs)
316 | expect(t, err, c.expectedErr)
317 | expect(t, args, c.expectedArgs)
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/command.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "io/ioutil"
7 | "sort"
8 | "strings"
9 | )
10 |
11 | // Command is a subcommand for a cli.App.
12 | type Command struct {
13 | // The name of the command
14 | Name string
15 | // short name of the command. Typically one character (deprecated, use `Aliases`)
16 | ShortName string
17 | // A list of aliases for the command
18 | Aliases []string
19 | // A short description of the usage of this command
20 | Usage string
21 | // Custom text to show on USAGE section of help
22 | UsageText string
23 | // A longer explanation of how the command works
24 | Description string
25 | // A short description of the arguments of this command
26 | ArgsUsage string
27 | // The category the command is part of
28 | Category string
29 | // The function to call when checking for bash command completions
30 | BashComplete BashCompleteFunc
31 | // An action to execute before any sub-subcommands are run, but after the context is ready
32 | // If a non-nil error is returned, no sub-subcommands are run
33 | Before BeforeFunc
34 | // An action to execute after any subcommands are run, but after the subcommand has finished
35 | // It is run even if Action() panics
36 | After AfterFunc
37 | // The function to call when this command is invoked
38 | Action interface{}
39 | // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
40 | // of deprecation period has passed, maybe?
41 |
42 | // Execute this function if a usage error occurs.
43 | OnUsageError OnUsageErrorFunc
44 | // List of child commands
45 | Subcommands Commands
46 | // List of flags to parse
47 | Flags []Flag
48 | // Treat all flags as normal arguments if true
49 | SkipFlagParsing bool
50 | // Skip argument reordering which attempts to move flags before arguments,
51 | // but only works if all flags appear after all arguments. This behavior was
52 | // removed n version 2 since it only works under specific conditions so we
53 | // backport here by exposing it as an option for compatibility.
54 | SkipArgReorder bool
55 | // Boolean to hide built-in help command
56 | HideHelp bool
57 | // Boolean to hide this command from help or completion
58 | Hidden bool
59 | // Boolean to enable short-option handling so user can combine several
60 | // single-character bool arguements into one
61 | // i.e. foobar -o -v -> foobar -ov
62 | UseShortOptionHandling bool
63 |
64 | // Full name of command for help, defaults to full command name, including parent commands.
65 | HelpName string
66 | commandNamePath []string
67 |
68 | // CustomHelpTemplate the text template for the command help topic.
69 | // cli.go uses text/template to render templates. You can
70 | // render custom help text by setting this variable.
71 | CustomHelpTemplate string
72 | }
73 |
74 | type CommandsByName []Command
75 |
76 | func (c CommandsByName) Len() int {
77 | return len(c)
78 | }
79 |
80 | func (c CommandsByName) Less(i, j int) bool {
81 | return lexicographicLess(c[i].Name, c[j].Name)
82 | }
83 |
84 | func (c CommandsByName) Swap(i, j int) {
85 | c[i], c[j] = c[j], c[i]
86 | }
87 |
88 | // FullName returns the full name of the command.
89 | // For subcommands this ensures that parent commands are part of the command path
90 | func (c Command) FullName() string {
91 | if c.commandNamePath == nil {
92 | return c.Name
93 | }
94 | return strings.Join(c.commandNamePath, " ")
95 | }
96 |
97 | // Commands is a slice of Command
98 | type Commands []Command
99 |
100 | // Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
101 | func (c Command) Run(ctx *Context) (err error) {
102 | if len(c.Subcommands) > 0 {
103 | return c.startApp(ctx)
104 | }
105 |
106 | if !c.HideHelp && (HelpFlag != BoolFlag{}) {
107 | // append help to flags
108 | c.Flags = append(
109 | c.Flags,
110 | HelpFlag,
111 | )
112 | }
113 |
114 | set, err := c.parseFlags(ctx.Args().Tail())
115 |
116 | context := NewContext(ctx.App, set, ctx)
117 | context.Command = c
118 | if checkCommandCompletions(context, c.Name) {
119 | return nil
120 | }
121 |
122 | if err != nil {
123 | if c.OnUsageError != nil {
124 | err := c.OnUsageError(context, err, false)
125 | context.App.handleExitCoder(context, err)
126 | return err
127 | }
128 | fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
129 | fmt.Fprintln(context.App.Writer)
130 | ShowCommandHelp(context, c.Name)
131 | return err
132 | }
133 |
134 | if checkCommandHelp(context, c.Name) {
135 | return nil
136 | }
137 |
138 | if c.After != nil {
139 | defer func() {
140 | afterErr := c.After(context)
141 | if afterErr != nil {
142 | context.App.handleExitCoder(context, err)
143 | if err != nil {
144 | err = NewMultiError(err, afterErr)
145 | } else {
146 | err = afterErr
147 | }
148 | }
149 | }()
150 | }
151 |
152 | if c.Before != nil {
153 | err = c.Before(context)
154 | if err != nil {
155 | ShowCommandHelp(context, c.Name)
156 | context.App.handleExitCoder(context, err)
157 | return err
158 | }
159 | }
160 |
161 | if c.Action == nil {
162 | c.Action = helpSubcommand.Action
163 | }
164 |
165 | err = HandleAction(c.Action, context)
166 |
167 | if err != nil {
168 | context.App.handleExitCoder(context, err)
169 | }
170 | return err
171 | }
172 |
173 | func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
174 | set, err := flagSet(c.Name, c.Flags)
175 | if err != nil {
176 | return nil, err
177 | }
178 | set.SetOutput(ioutil.Discard)
179 |
180 | if c.SkipFlagParsing {
181 | return set, set.Parse(append([]string{"--"}, args...))
182 | }
183 |
184 | if c.UseShortOptionHandling {
185 | args = translateShortOptions(args)
186 | }
187 |
188 | if !c.SkipArgReorder {
189 | args = reorderArgs(args)
190 | }
191 |
192 | err = set.Parse(args)
193 | if err != nil {
194 | return nil, err
195 | }
196 |
197 | err = normalizeFlags(c.Flags, set)
198 | if err != nil {
199 | return nil, err
200 | }
201 |
202 | return set, nil
203 | }
204 |
205 | // reorderArgs moves all flags before arguments as this is what flag expects
206 | func reorderArgs(args []string) []string {
207 | var nonflags, flags []string
208 |
209 | readFlagValue := false
210 | for i, arg := range args {
211 | if arg == "--" {
212 | nonflags = append(nonflags, args[i:]...)
213 | break
214 | }
215 |
216 | if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") {
217 | readFlagValue = false
218 | flags = append(flags, arg)
219 | continue
220 | }
221 | readFlagValue = false
222 |
223 | if arg != "-" && strings.HasPrefix(arg, "-") {
224 | flags = append(flags, arg)
225 |
226 | readFlagValue = !strings.Contains(arg, "=")
227 | } else {
228 | nonflags = append(nonflags, arg)
229 | }
230 | }
231 |
232 | return append(flags, nonflags...)
233 | }
234 |
235 | func translateShortOptions(flagArgs Args) []string {
236 | // separate combined flags
237 | var flagArgsSeparated []string
238 | for _, flagArg := range flagArgs {
239 | if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 {
240 | for _, flagChar := range flagArg[1:] {
241 | flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar))
242 | }
243 | } else {
244 | flagArgsSeparated = append(flagArgsSeparated, flagArg)
245 | }
246 | }
247 | return flagArgsSeparated
248 | }
249 |
250 | // Names returns the names including short names and aliases.
251 | func (c Command) Names() []string {
252 | names := []string{c.Name}
253 |
254 | if c.ShortName != "" {
255 | names = append(names, c.ShortName)
256 | }
257 |
258 | return append(names, c.Aliases...)
259 | }
260 |
261 | // HasName returns true if Command.Name or Command.ShortName matches given name
262 | func (c Command) HasName(name string) bool {
263 | for _, n := range c.Names() {
264 | if n == name {
265 | return true
266 | }
267 | }
268 | return false
269 | }
270 |
271 | func (c Command) startApp(ctx *Context) error {
272 | app := NewApp()
273 | app.Metadata = ctx.App.Metadata
274 | // set the name and usage
275 | app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
276 | if c.HelpName == "" {
277 | app.HelpName = c.HelpName
278 | } else {
279 | app.HelpName = app.Name
280 | }
281 |
282 | app.Usage = c.Usage
283 | app.Description = c.Description
284 | app.ArgsUsage = c.ArgsUsage
285 |
286 | // set CommandNotFound
287 | app.CommandNotFound = ctx.App.CommandNotFound
288 | app.CustomAppHelpTemplate = c.CustomHelpTemplate
289 |
290 | // set the flags and commands
291 | app.Commands = c.Subcommands
292 | app.Flags = c.Flags
293 | app.HideHelp = c.HideHelp
294 |
295 | app.Version = ctx.App.Version
296 | app.HideVersion = ctx.App.HideVersion
297 | app.Compiled = ctx.App.Compiled
298 | app.Author = ctx.App.Author
299 | app.Email = ctx.App.Email
300 | app.Writer = ctx.App.Writer
301 | app.ErrWriter = ctx.App.ErrWriter
302 |
303 | app.categories = CommandCategories{}
304 | for _, command := range c.Subcommands {
305 | app.categories = app.categories.AddCommand(command.Category, command)
306 | }
307 |
308 | sort.Sort(app.categories)
309 |
310 | // bash completion
311 | app.EnableBashCompletion = ctx.App.EnableBashCompletion
312 | if c.BashComplete != nil {
313 | app.BashComplete = c.BashComplete
314 | }
315 |
316 | // set the actions
317 | app.Before = c.Before
318 | app.After = c.After
319 | if c.Action != nil {
320 | app.Action = c.Action
321 | } else {
322 | app.Action = helpSubcommand.Action
323 | }
324 | app.OnUsageError = c.OnUsageError
325 |
326 | for index, cc := range app.Commands {
327 | app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
328 | }
329 |
330 | return app.RunAsSubcommand(ctx)
331 | }
332 |
333 | // VisibleFlags returns a slice of the Flags with Hidden=false
334 | func (c Command) VisibleFlags() []Flag {
335 | return visibleFlags(c.Flags)
336 | }
337 |
--------------------------------------------------------------------------------
/help.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "strings"
8 | "text/tabwriter"
9 | "text/template"
10 | )
11 |
12 | // AppHelpTemplate is the text template for the Default help topic.
13 | // cli.go uses text/template to render templates. You can
14 | // render custom help text by setting this variable.
15 | var AppHelpTemplate = `NAME:
16 | {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
17 |
18 | USAGE:
19 | {{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}}
20 |
21 | VERSION:
22 | {{.Version}}{{end}}{{end}}{{if .Description}}
23 |
24 | DESCRIPTION:
25 | {{.Description}}{{end}}{{if len .Authors}}
26 |
27 | AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
28 | {{range $index, $author := .Authors}}{{if $index}}
29 | {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
30 |
31 | COMMANDS:{{range .VisibleCategories}}{{if .Name}}
32 |
33 | {{.Name}}:{{end}}{{range .VisibleCommands}}
34 | {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
35 |
36 | GLOBAL OPTIONS:
37 | {{range $index, $option := .VisibleFlags}}{{if $index}}
38 | {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
39 |
40 | COPYRIGHT:
41 | {{.Copyright}}{{end}}
42 | `
43 |
44 | // CommandHelpTemplate is the text template for the command help topic.
45 | // cli.go uses text/template to render templates. You can
46 | // render custom help text by setting this variable.
47 | var CommandHelpTemplate = `NAME:
48 | {{.HelpName}} - {{.Usage}}
49 |
50 | USAGE:
51 | {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
52 |
53 | CATEGORY:
54 | {{.Category}}{{end}}{{if .Description}}
55 |
56 | DESCRIPTION:
57 | {{.Description}}{{end}}{{if .VisibleFlags}}
58 |
59 | OPTIONS:
60 | {{range .VisibleFlags}}{{.}}
61 | {{end}}{{end}}
62 | `
63 |
64 | // SubcommandHelpTemplate is the text template for the subcommand help topic.
65 | // cli.go uses text/template to render templates. You can
66 | // render custom help text by setting this variable.
67 | var SubcommandHelpTemplate = `NAME:
68 | {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
69 |
70 | USAGE:
71 | {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
72 |
73 | COMMANDS:{{range .VisibleCategories}}{{if .Name}}
74 | {{.Name}}:{{end}}{{range .VisibleCommands}}
75 | {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}
76 | {{end}}{{if .VisibleFlags}}
77 | OPTIONS:
78 | {{range .VisibleFlags}}{{.}}
79 | {{end}}{{end}}
80 | `
81 |
82 | var helpCommand = Command{
83 | Name: "help",
84 | Aliases: []string{"h"},
85 | Usage: "Shows a list of commands or help for one command",
86 | ArgsUsage: "[command]",
87 | Action: func(c *Context) error {
88 | args := c.Args()
89 | if args.Present() {
90 | return ShowCommandHelp(c, args.First())
91 | }
92 |
93 | ShowAppHelp(c)
94 | return nil
95 | },
96 | }
97 |
98 | var helpSubcommand = Command{
99 | Name: "help",
100 | Aliases: []string{"h"},
101 | Usage: "Shows a list of commands or help for one command",
102 | ArgsUsage: "[command]",
103 | Action: func(c *Context) error {
104 | args := c.Args()
105 | if args.Present() {
106 | return ShowCommandHelp(c, args.First())
107 | }
108 |
109 | return ShowSubcommandHelp(c)
110 | },
111 | }
112 |
113 | // Prints help for the App or Command
114 | type helpPrinter func(w io.Writer, templ string, data interface{})
115 |
116 | // Prints help for the App or Command with custom template function.
117 | type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{})
118 |
119 | // HelpPrinter is a function that writes the help output. If not set a default
120 | // is used. The function signature is:
121 | // func(w io.Writer, templ string, data interface{})
122 | var HelpPrinter helpPrinter = printHelp
123 |
124 | // HelpPrinterCustom is same as HelpPrinter but
125 | // takes a custom function for template function map.
126 | var HelpPrinterCustom helpPrinterCustom = printHelpCustom
127 |
128 | // VersionPrinter prints the version for the App
129 | var VersionPrinter = printVersion
130 |
131 | // ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
132 | func ShowAppHelpAndExit(c *Context, exitCode int) {
133 | ShowAppHelp(c)
134 | os.Exit(exitCode)
135 | }
136 |
137 | // ShowAppHelp is an action that displays the help.
138 | func ShowAppHelp(c *Context) (err error) {
139 | if c.App.CustomAppHelpTemplate == "" {
140 | HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
141 | return
142 | }
143 | customAppData := func() map[string]interface{} {
144 | if c.App.ExtraInfo == nil {
145 | return nil
146 | }
147 | return map[string]interface{}{
148 | "ExtraInfo": c.App.ExtraInfo,
149 | }
150 | }
151 | HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData())
152 | return nil
153 | }
154 |
155 | // DefaultAppComplete prints the list of subcommands as the default app completion method
156 | func DefaultAppComplete(c *Context) {
157 | for _, command := range c.App.Commands {
158 | if command.Hidden {
159 | continue
160 | }
161 | for _, name := range command.Names() {
162 | fmt.Fprintln(c.App.Writer, name)
163 | }
164 | }
165 | }
166 |
167 | // ShowCommandHelpAndExit - exits with code after showing help
168 | func ShowCommandHelpAndExit(c *Context, command string, code int) {
169 | ShowCommandHelp(c, command)
170 | os.Exit(code)
171 | }
172 |
173 | // ShowCommandHelp prints help for the given command
174 | func ShowCommandHelp(ctx *Context, command string) error {
175 | // show the subcommand help for a command with subcommands
176 | if command == "" {
177 | HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
178 | return nil
179 | }
180 |
181 | for _, c := range ctx.App.Commands {
182 | if c.HasName(command) {
183 | if c.CustomHelpTemplate != "" {
184 | HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil)
185 | } else {
186 | HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
187 | }
188 | return nil
189 | }
190 | }
191 |
192 | if ctx.App.CommandNotFound == nil {
193 | return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
194 | }
195 |
196 | ctx.App.CommandNotFound(ctx, command)
197 | return nil
198 | }
199 |
200 | // ShowSubcommandHelp prints help for the given subcommand
201 | func ShowSubcommandHelp(c *Context) error {
202 | return ShowCommandHelp(c, c.Command.Name)
203 | }
204 |
205 | // ShowVersion prints the version number of the App
206 | func ShowVersion(c *Context) {
207 | VersionPrinter(c)
208 | }
209 |
210 | func printVersion(c *Context) {
211 | fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
212 | }
213 |
214 | // ShowCompletions prints the lists of commands within a given context
215 | func ShowCompletions(c *Context) {
216 | a := c.App
217 | if a != nil && a.BashComplete != nil {
218 | a.BashComplete(c)
219 | }
220 | }
221 |
222 | // ShowCommandCompletions prints the custom completions for a given command
223 | func ShowCommandCompletions(ctx *Context, command string) {
224 | c := ctx.App.Command(command)
225 | if c != nil && c.BashComplete != nil {
226 | c.BashComplete(ctx)
227 | }
228 | }
229 |
230 | func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
231 | funcMap := template.FuncMap{
232 | "join": strings.Join,
233 | }
234 | if customFunc != nil {
235 | for key, value := range customFunc {
236 | funcMap[key] = value
237 | }
238 | }
239 |
240 | w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
241 | t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
242 | err := t.Execute(w, data)
243 | if err != nil {
244 | // If the writer is closed, t.Execute will fail, and there's nothing
245 | // we can do to recover.
246 | if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
247 | fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
248 | }
249 | return
250 | }
251 | w.Flush()
252 | }
253 |
254 | func printHelp(out io.Writer, templ string, data interface{}) {
255 | printHelpCustom(out, templ, data, nil)
256 | }
257 |
258 | func checkVersion(c *Context) bool {
259 | found := false
260 | if VersionFlag.GetName() != "" {
261 | eachName(VersionFlag.GetName(), func(name string) {
262 | if c.GlobalBool(name) || c.Bool(name) {
263 | found = true
264 | }
265 | })
266 | }
267 | return found
268 | }
269 |
270 | func checkHelp(c *Context) bool {
271 | found := false
272 | if HelpFlag.GetName() != "" {
273 | eachName(HelpFlag.GetName(), func(name string) {
274 | if c.GlobalBool(name) || c.Bool(name) {
275 | found = true
276 | }
277 | })
278 | }
279 | return found
280 | }
281 |
282 | func checkCommandHelp(c *Context, name string) bool {
283 | if c.Bool("h") || c.Bool("help") {
284 | ShowCommandHelp(c, name)
285 | return true
286 | }
287 |
288 | return false
289 | }
290 |
291 | func checkSubcommandHelp(c *Context) bool {
292 | if c.Bool("h") || c.Bool("help") {
293 | ShowSubcommandHelp(c)
294 | return true
295 | }
296 |
297 | return false
298 | }
299 |
300 | func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
301 | if !a.EnableBashCompletion {
302 | return false, arguments
303 | }
304 |
305 | pos := len(arguments) - 1
306 | lastArg := arguments[pos]
307 |
308 | if lastArg != "--"+BashCompletionFlag.GetName() {
309 | return false, arguments
310 | }
311 |
312 | return true, arguments[:pos]
313 | }
314 |
315 | func checkCompletions(c *Context) bool {
316 | if !c.shellComplete {
317 | return false
318 | }
319 |
320 | if args := c.Args(); args.Present() {
321 | name := args.First()
322 | if cmd := c.App.Command(name); cmd != nil {
323 | // let the command handle the completion
324 | return false
325 | }
326 | }
327 |
328 | ShowCompletions(c)
329 | return true
330 | }
331 |
332 | func checkCommandCompletions(c *Context, name string) bool {
333 | if !c.shellComplete {
334 | return false
335 | }
336 |
337 | ShowCommandCompletions(c, name)
338 | return true
339 | }
340 |
--------------------------------------------------------------------------------
/altsrc/flag_generated.go:
--------------------------------------------------------------------------------
1 | package altsrc
2 |
3 | import (
4 | "flag"
5 |
6 | "gopkg.in/urfave/cli.v1"
7 | )
8 |
9 | // WARNING: This file is generated!
10 |
11 | // BoolFlag is the flag type that wraps cli.BoolFlag to allow
12 | // for other values to be specified
13 | type BoolFlag struct {
14 | cli.BoolFlag
15 | set *flag.FlagSet
16 | }
17 |
18 | // NewBoolFlag creates a new BoolFlag
19 | func NewBoolFlag(fl cli.BoolFlag) *BoolFlag {
20 | return &BoolFlag{BoolFlag: fl, set: nil}
21 | }
22 |
23 | // Apply saves the flagSet for later usage calls, then calls the
24 | // wrapped BoolFlag.Apply
25 | func (f *BoolFlag) Apply(set *flag.FlagSet) {
26 | f.set = set
27 | f.BoolFlag.Apply(set)
28 | }
29 |
30 | // ApplyWithError saves the flagSet for later usage calls, then calls the
31 | // wrapped BoolFlag.ApplyWithError
32 | func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error {
33 | f.set = set
34 | return f.BoolFlag.ApplyWithError(set)
35 | }
36 |
37 | // BoolTFlag is the flag type that wraps cli.BoolTFlag to allow
38 | // for other values to be specified
39 | type BoolTFlag struct {
40 | cli.BoolTFlag
41 | set *flag.FlagSet
42 | }
43 |
44 | // NewBoolTFlag creates a new BoolTFlag
45 | func NewBoolTFlag(fl cli.BoolTFlag) *BoolTFlag {
46 | return &BoolTFlag{BoolTFlag: fl, set: nil}
47 | }
48 |
49 | // Apply saves the flagSet for later usage calls, then calls the
50 | // wrapped BoolTFlag.Apply
51 | func (f *BoolTFlag) Apply(set *flag.FlagSet) {
52 | f.set = set
53 | f.BoolTFlag.Apply(set)
54 | }
55 |
56 | // ApplyWithError saves the flagSet for later usage calls, then calls the
57 | // wrapped BoolTFlag.ApplyWithError
58 | func (f *BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
59 | f.set = set
60 | return f.BoolTFlag.ApplyWithError(set)
61 | }
62 |
63 | // DurationFlag is the flag type that wraps cli.DurationFlag to allow
64 | // for other values to be specified
65 | type DurationFlag struct {
66 | cli.DurationFlag
67 | set *flag.FlagSet
68 | }
69 |
70 | // NewDurationFlag creates a new DurationFlag
71 | func NewDurationFlag(fl cli.DurationFlag) *DurationFlag {
72 | return &DurationFlag{DurationFlag: fl, set: nil}
73 | }
74 |
75 | // Apply saves the flagSet for later usage calls, then calls the
76 | // wrapped DurationFlag.Apply
77 | func (f *DurationFlag) Apply(set *flag.FlagSet) {
78 | f.set = set
79 | f.DurationFlag.Apply(set)
80 | }
81 |
82 | // ApplyWithError saves the flagSet for later usage calls, then calls the
83 | // wrapped DurationFlag.ApplyWithError
84 | func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error {
85 | f.set = set
86 | return f.DurationFlag.ApplyWithError(set)
87 | }
88 |
89 | // Float64Flag is the flag type that wraps cli.Float64Flag to allow
90 | // for other values to be specified
91 | type Float64Flag struct {
92 | cli.Float64Flag
93 | set *flag.FlagSet
94 | }
95 |
96 | // NewFloat64Flag creates a new Float64Flag
97 | func NewFloat64Flag(fl cli.Float64Flag) *Float64Flag {
98 | return &Float64Flag{Float64Flag: fl, set: nil}
99 | }
100 |
101 | // Apply saves the flagSet for later usage calls, then calls the
102 | // wrapped Float64Flag.Apply
103 | func (f *Float64Flag) Apply(set *flag.FlagSet) {
104 | f.set = set
105 | f.Float64Flag.Apply(set)
106 | }
107 |
108 | // ApplyWithError saves the flagSet for later usage calls, then calls the
109 | // wrapped Float64Flag.ApplyWithError
110 | func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error {
111 | f.set = set
112 | return f.Float64Flag.ApplyWithError(set)
113 | }
114 |
115 | // GenericFlag is the flag type that wraps cli.GenericFlag to allow
116 | // for other values to be specified
117 | type GenericFlag struct {
118 | cli.GenericFlag
119 | set *flag.FlagSet
120 | }
121 |
122 | // NewGenericFlag creates a new GenericFlag
123 | func NewGenericFlag(fl cli.GenericFlag) *GenericFlag {
124 | return &GenericFlag{GenericFlag: fl, set: nil}
125 | }
126 |
127 | // Apply saves the flagSet for later usage calls, then calls the
128 | // wrapped GenericFlag.Apply
129 | func (f *GenericFlag) Apply(set *flag.FlagSet) {
130 | f.set = set
131 | f.GenericFlag.Apply(set)
132 | }
133 |
134 | // ApplyWithError saves the flagSet for later usage calls, then calls the
135 | // wrapped GenericFlag.ApplyWithError
136 | func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error {
137 | f.set = set
138 | return f.GenericFlag.ApplyWithError(set)
139 | }
140 |
141 | // Int64Flag is the flag type that wraps cli.Int64Flag to allow
142 | // for other values to be specified
143 | type Int64Flag struct {
144 | cli.Int64Flag
145 | set *flag.FlagSet
146 | }
147 |
148 | // NewInt64Flag creates a new Int64Flag
149 | func NewInt64Flag(fl cli.Int64Flag) *Int64Flag {
150 | return &Int64Flag{Int64Flag: fl, set: nil}
151 | }
152 |
153 | // Apply saves the flagSet for later usage calls, then calls the
154 | // wrapped Int64Flag.Apply
155 | func (f *Int64Flag) Apply(set *flag.FlagSet) {
156 | f.set = set
157 | f.Int64Flag.Apply(set)
158 | }
159 |
160 | // ApplyWithError saves the flagSet for later usage calls, then calls the
161 | // wrapped Int64Flag.ApplyWithError
162 | func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error {
163 | f.set = set
164 | return f.Int64Flag.ApplyWithError(set)
165 | }
166 |
167 | // IntFlag is the flag type that wraps cli.IntFlag to allow
168 | // for other values to be specified
169 | type IntFlag struct {
170 | cli.IntFlag
171 | set *flag.FlagSet
172 | }
173 |
174 | // NewIntFlag creates a new IntFlag
175 | func NewIntFlag(fl cli.IntFlag) *IntFlag {
176 | return &IntFlag{IntFlag: fl, set: nil}
177 | }
178 |
179 | // Apply saves the flagSet for later usage calls, then calls the
180 | // wrapped IntFlag.Apply
181 | func (f *IntFlag) Apply(set *flag.FlagSet) {
182 | f.set = set
183 | f.IntFlag.Apply(set)
184 | }
185 |
186 | // ApplyWithError saves the flagSet for later usage calls, then calls the
187 | // wrapped IntFlag.ApplyWithError
188 | func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error {
189 | f.set = set
190 | return f.IntFlag.ApplyWithError(set)
191 | }
192 |
193 | // IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow
194 | // for other values to be specified
195 | type IntSliceFlag struct {
196 | cli.IntSliceFlag
197 | set *flag.FlagSet
198 | }
199 |
200 | // NewIntSliceFlag creates a new IntSliceFlag
201 | func NewIntSliceFlag(fl cli.IntSliceFlag) *IntSliceFlag {
202 | return &IntSliceFlag{IntSliceFlag: fl, set: nil}
203 | }
204 |
205 | // Apply saves the flagSet for later usage calls, then calls the
206 | // wrapped IntSliceFlag.Apply
207 | func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
208 | f.set = set
209 | f.IntSliceFlag.Apply(set)
210 | }
211 |
212 | // ApplyWithError saves the flagSet for later usage calls, then calls the
213 | // wrapped IntSliceFlag.ApplyWithError
214 | func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
215 | f.set = set
216 | return f.IntSliceFlag.ApplyWithError(set)
217 | }
218 |
219 | // Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow
220 | // for other values to be specified
221 | type Int64SliceFlag struct {
222 | cli.Int64SliceFlag
223 | set *flag.FlagSet
224 | }
225 |
226 | // NewInt64SliceFlag creates a new Int64SliceFlag
227 | func NewInt64SliceFlag(fl cli.Int64SliceFlag) *Int64SliceFlag {
228 | return &Int64SliceFlag{Int64SliceFlag: fl, set: nil}
229 | }
230 |
231 | // Apply saves the flagSet for later usage calls, then calls the
232 | // wrapped Int64SliceFlag.Apply
233 | func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
234 | f.set = set
235 | f.Int64SliceFlag.Apply(set)
236 | }
237 |
238 | // ApplyWithError saves the flagSet for later usage calls, then calls the
239 | // wrapped Int64SliceFlag.ApplyWithError
240 | func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
241 | f.set = set
242 | return f.Int64SliceFlag.ApplyWithError(set)
243 | }
244 |
245 | // StringFlag is the flag type that wraps cli.StringFlag to allow
246 | // for other values to be specified
247 | type StringFlag struct {
248 | cli.StringFlag
249 | set *flag.FlagSet
250 | }
251 |
252 | // NewStringFlag creates a new StringFlag
253 | func NewStringFlag(fl cli.StringFlag) *StringFlag {
254 | return &StringFlag{StringFlag: fl, set: nil}
255 | }
256 |
257 | // Apply saves the flagSet for later usage calls, then calls the
258 | // wrapped StringFlag.Apply
259 | func (f *StringFlag) Apply(set *flag.FlagSet) {
260 | f.set = set
261 | f.StringFlag.Apply(set)
262 | }
263 |
264 | // ApplyWithError saves the flagSet for later usage calls, then calls the
265 | // wrapped StringFlag.ApplyWithError
266 | func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error {
267 | f.set = set
268 | return f.StringFlag.ApplyWithError(set)
269 | }
270 |
271 | // StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow
272 | // for other values to be specified
273 | type StringSliceFlag struct {
274 | cli.StringSliceFlag
275 | set *flag.FlagSet
276 | }
277 |
278 | // NewStringSliceFlag creates a new StringSliceFlag
279 | func NewStringSliceFlag(fl cli.StringSliceFlag) *StringSliceFlag {
280 | return &StringSliceFlag{StringSliceFlag: fl, set: nil}
281 | }
282 |
283 | // Apply saves the flagSet for later usage calls, then calls the
284 | // wrapped StringSliceFlag.Apply
285 | func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
286 | f.set = set
287 | f.StringSliceFlag.Apply(set)
288 | }
289 |
290 | // ApplyWithError saves the flagSet for later usage calls, then calls the
291 | // wrapped StringSliceFlag.ApplyWithError
292 | func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
293 | f.set = set
294 | return f.StringSliceFlag.ApplyWithError(set)
295 | }
296 |
297 | // Uint64Flag is the flag type that wraps cli.Uint64Flag to allow
298 | // for other values to be specified
299 | type Uint64Flag struct {
300 | cli.Uint64Flag
301 | set *flag.FlagSet
302 | }
303 |
304 | // NewUint64Flag creates a new Uint64Flag
305 | func NewUint64Flag(fl cli.Uint64Flag) *Uint64Flag {
306 | return &Uint64Flag{Uint64Flag: fl, set: nil}
307 | }
308 |
309 | // Apply saves the flagSet for later usage calls, then calls the
310 | // wrapped Uint64Flag.Apply
311 | func (f *Uint64Flag) Apply(set *flag.FlagSet) {
312 | f.set = set
313 | f.Uint64Flag.Apply(set)
314 | }
315 |
316 | // ApplyWithError saves the flagSet for later usage calls, then calls the
317 | // wrapped Uint64Flag.ApplyWithError
318 | func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
319 | f.set = set
320 | return f.Uint64Flag.ApplyWithError(set)
321 | }
322 |
323 | // UintFlag is the flag type that wraps cli.UintFlag to allow
324 | // for other values to be specified
325 | type UintFlag struct {
326 | cli.UintFlag
327 | set *flag.FlagSet
328 | }
329 |
330 | // NewUintFlag creates a new UintFlag
331 | func NewUintFlag(fl cli.UintFlag) *UintFlag {
332 | return &UintFlag{UintFlag: fl, set: nil}
333 | }
334 |
335 | // Apply saves the flagSet for later usage calls, then calls the
336 | // wrapped UintFlag.Apply
337 | func (f *UintFlag) Apply(set *flag.FlagSet) {
338 | f.set = set
339 | f.UintFlag.Apply(set)
340 | }
341 |
342 | // ApplyWithError saves the flagSet for later usage calls, then calls the
343 | // wrapped UintFlag.ApplyWithError
344 | func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error {
345 | f.set = set
346 | return f.UintFlag.ApplyWithError(set)
347 | }
348 |
--------------------------------------------------------------------------------
/altsrc/flag_test.go:
--------------------------------------------------------------------------------
1 | package altsrc
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 | "strings"
8 | "testing"
9 | "time"
10 |
11 | "gopkg.in/urfave/cli.v1"
12 | )
13 |
14 | type testApplyInputSource struct {
15 | Flag FlagInputSourceExtension
16 | FlagName string
17 | FlagSetName string
18 | Expected string
19 | ContextValueString string
20 | ContextValue flag.Value
21 | EnvVarValue string
22 | EnvVarName string
23 | MapValue interface{}
24 | }
25 |
26 | func TestGenericApplyInputSourceValue(t *testing.T) {
27 | v := &Parser{"abc", "def"}
28 | c := runTest(t, testApplyInputSource{
29 | Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
30 | FlagName: "test",
31 | MapValue: v,
32 | })
33 | expect(t, v, c.Generic("test"))
34 | }
35 |
36 | func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
37 | p := &Parser{"abc", "def"}
38 | c := runTest(t, testApplyInputSource{
39 | Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
40 | FlagName: "test",
41 | MapValue: &Parser{"efg", "hig"},
42 | ContextValueString: p.String(),
43 | })
44 | expect(t, p, c.Generic("test"))
45 | }
46 |
47 | func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
48 | c := runTest(t, testApplyInputSource{
49 | Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}),
50 | FlagName: "test",
51 | MapValue: &Parser{"efg", "hij"},
52 | EnvVarName: "TEST",
53 | EnvVarValue: "abc,def",
54 | })
55 | expect(t, &Parser{"abc", "def"}, c.Generic("test"))
56 | }
57 |
58 | func TestStringSliceApplyInputSourceValue(t *testing.T) {
59 | c := runTest(t, testApplyInputSource{
60 | Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
61 | FlagName: "test",
62 | MapValue: []interface{}{"hello", "world"},
63 | })
64 | expect(t, c.StringSlice("test"), []string{"hello", "world"})
65 | }
66 |
67 | func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
68 | c := runTest(t, testApplyInputSource{
69 | Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
70 | FlagName: "test",
71 | MapValue: []interface{}{"hello", "world"},
72 | ContextValueString: "ohno",
73 | })
74 | expect(t, c.StringSlice("test"), []string{"ohno"})
75 | }
76 |
77 | func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
78 | c := runTest(t, testApplyInputSource{
79 | Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}),
80 | FlagName: "test",
81 | MapValue: []interface{}{"hello", "world"},
82 | EnvVarName: "TEST",
83 | EnvVarValue: "oh,no",
84 | })
85 | expect(t, c.StringSlice("test"), []string{"oh", "no"})
86 | }
87 |
88 | func TestIntSliceApplyInputSourceValue(t *testing.T) {
89 | c := runTest(t, testApplyInputSource{
90 | Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
91 | FlagName: "test",
92 | MapValue: []interface{}{1, 2},
93 | })
94 | expect(t, c.IntSlice("test"), []int{1, 2})
95 | }
96 |
97 | func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
98 | c := runTest(t, testApplyInputSource{
99 | Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
100 | FlagName: "test",
101 | MapValue: []interface{}{1, 2},
102 | ContextValueString: "3",
103 | })
104 | expect(t, c.IntSlice("test"), []int{3})
105 | }
106 |
107 | func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
108 | c := runTest(t, testApplyInputSource{
109 | Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}),
110 | FlagName: "test",
111 | MapValue: []interface{}{1, 2},
112 | EnvVarName: "TEST",
113 | EnvVarValue: "3,4",
114 | })
115 | expect(t, c.IntSlice("test"), []int{3, 4})
116 | }
117 |
118 | func TestBoolApplyInputSourceMethodSet(t *testing.T) {
119 | c := runTest(t, testApplyInputSource{
120 | Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
121 | FlagName: "test",
122 | MapValue: true,
123 | })
124 | expect(t, true, c.Bool("test"))
125 | }
126 |
127 | func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
128 | c := runTest(t, testApplyInputSource{
129 | Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
130 | FlagName: "test",
131 | MapValue: false,
132 | ContextValueString: "true",
133 | })
134 | expect(t, true, c.Bool("test"))
135 | }
136 |
137 | func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
138 | c := runTest(t, testApplyInputSource{
139 | Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}),
140 | FlagName: "test",
141 | MapValue: false,
142 | EnvVarName: "TEST",
143 | EnvVarValue: "true",
144 | })
145 | expect(t, true, c.Bool("test"))
146 | }
147 |
148 | func TestBoolTApplyInputSourceMethodSet(t *testing.T) {
149 | c := runTest(t, testApplyInputSource{
150 | Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}),
151 | FlagName: "test",
152 | MapValue: false,
153 | })
154 | expect(t, false, c.BoolT("test"))
155 | }
156 |
157 | func TestBoolTApplyInputSourceMethodContextSet(t *testing.T) {
158 | c := runTest(t, testApplyInputSource{
159 | Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}),
160 | FlagName: "test",
161 | MapValue: true,
162 | ContextValueString: "false",
163 | })
164 | expect(t, false, c.BoolT("test"))
165 | }
166 |
167 | func TestBoolTApplyInputSourceMethodEnvVarSet(t *testing.T) {
168 | c := runTest(t, testApplyInputSource{
169 | Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test", EnvVar: "TEST"}),
170 | FlagName: "test",
171 | MapValue: true,
172 | EnvVarName: "TEST",
173 | EnvVarValue: "false",
174 | })
175 | expect(t, false, c.BoolT("test"))
176 | }
177 |
178 | func TestStringApplyInputSourceMethodSet(t *testing.T) {
179 | c := runTest(t, testApplyInputSource{
180 | Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
181 | FlagName: "test",
182 | MapValue: "hello",
183 | })
184 | expect(t, "hello", c.String("test"))
185 | }
186 |
187 | func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
188 | c := runTest(t, testApplyInputSource{
189 | Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
190 | FlagName: "test",
191 | MapValue: "hello",
192 | ContextValueString: "goodbye",
193 | })
194 | expect(t, "goodbye", c.String("test"))
195 | }
196 |
197 | func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
198 | c := runTest(t, testApplyInputSource{
199 | Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}),
200 | FlagName: "test",
201 | MapValue: "hello",
202 | EnvVarName: "TEST",
203 | EnvVarValue: "goodbye",
204 | })
205 | expect(t, "goodbye", c.String("test"))
206 | }
207 |
208 | func TestIntApplyInputSourceMethodSet(t *testing.T) {
209 | c := runTest(t, testApplyInputSource{
210 | Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
211 | FlagName: "test",
212 | MapValue: 15,
213 | })
214 | expect(t, 15, c.Int("test"))
215 | }
216 |
217 | func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
218 | c := runTest(t, testApplyInputSource{
219 | Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
220 | FlagName: "test",
221 | MapValue: 15,
222 | ContextValueString: "7",
223 | })
224 | expect(t, 7, c.Int("test"))
225 | }
226 |
227 | func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
228 | c := runTest(t, testApplyInputSource{
229 | Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}),
230 | FlagName: "test",
231 | MapValue: 15,
232 | EnvVarName: "TEST",
233 | EnvVarValue: "12",
234 | })
235 | expect(t, 12, c.Int("test"))
236 | }
237 |
238 | func TestDurationApplyInputSourceMethodSet(t *testing.T) {
239 | c := runTest(t, testApplyInputSource{
240 | Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
241 | FlagName: "test",
242 | MapValue: time.Duration(30 * time.Second),
243 | })
244 | expect(t, time.Duration(30*time.Second), c.Duration("test"))
245 | }
246 |
247 | func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
248 | c := runTest(t, testApplyInputSource{
249 | Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
250 | FlagName: "test",
251 | MapValue: time.Duration(30 * time.Second),
252 | ContextValueString: time.Duration(15 * time.Second).String(),
253 | })
254 | expect(t, time.Duration(15*time.Second), c.Duration("test"))
255 | }
256 |
257 | func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
258 | c := runTest(t, testApplyInputSource{
259 | Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}),
260 | FlagName: "test",
261 | MapValue: time.Duration(30 * time.Second),
262 | EnvVarName: "TEST",
263 | EnvVarValue: time.Duration(15 * time.Second).String(),
264 | })
265 | expect(t, time.Duration(15*time.Second), c.Duration("test"))
266 | }
267 |
268 | func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
269 | c := runTest(t, testApplyInputSource{
270 | Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
271 | FlagName: "test",
272 | MapValue: 1.3,
273 | })
274 | expect(t, 1.3, c.Float64("test"))
275 | }
276 |
277 | func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
278 | c := runTest(t, testApplyInputSource{
279 | Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
280 | FlagName: "test",
281 | MapValue: 1.3,
282 | ContextValueString: fmt.Sprintf("%v", 1.4),
283 | })
284 | expect(t, 1.4, c.Float64("test"))
285 | }
286 |
287 | func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
288 | c := runTest(t, testApplyInputSource{
289 | Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}),
290 | FlagName: "test",
291 | MapValue: 1.3,
292 | EnvVarName: "TEST",
293 | EnvVarValue: fmt.Sprintf("%v", 1.4),
294 | })
295 | expect(t, 1.4, c.Float64("test"))
296 | }
297 |
298 | func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
299 | inputSource := &MapInputSource{valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}}
300 | set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError)
301 | c := cli.NewContext(nil, set, nil)
302 | if test.EnvVarName != "" && test.EnvVarValue != "" {
303 | os.Setenv(test.EnvVarName, test.EnvVarValue)
304 | defer os.Setenv(test.EnvVarName, "")
305 | }
306 |
307 | test.Flag.Apply(set)
308 | if test.ContextValue != nil {
309 | flag := set.Lookup(test.FlagName)
310 | flag.Value = test.ContextValue
311 | }
312 | if test.ContextValueString != "" {
313 | set.Set(test.FlagName, test.ContextValueString)
314 | }
315 | test.Flag.ApplyInputSourceValue(c, inputSource)
316 |
317 | return c
318 | }
319 |
320 | type Parser [2]string
321 |
322 | func (p *Parser) Set(value string) error {
323 | parts := strings.Split(value, ",")
324 | if len(parts) != 2 {
325 | return fmt.Errorf("invalid format")
326 | }
327 |
328 | (*p)[0] = parts[0]
329 | (*p)[1] = parts[1]
330 |
331 | return nil
332 | }
333 |
334 | func (p *Parser) String() string {
335 | return fmt.Sprintf("%s,%s", p[0], p[1])
336 | }
337 |
--------------------------------------------------------------------------------
/help_test.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "bytes"
5 | "flag"
6 | "fmt"
7 | "runtime"
8 | "strings"
9 | "testing"
10 | )
11 |
12 | func Test_ShowAppHelp_NoAuthor(t *testing.T) {
13 | output := new(bytes.Buffer)
14 | app := NewApp()
15 | app.Writer = output
16 |
17 | c := NewContext(app, nil, nil)
18 |
19 | ShowAppHelp(c)
20 |
21 | if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 {
22 | t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):")
23 | }
24 | }
25 |
26 | func Test_ShowAppHelp_NoVersion(t *testing.T) {
27 | output := new(bytes.Buffer)
28 | app := NewApp()
29 | app.Writer = output
30 |
31 | app.Version = ""
32 |
33 | c := NewContext(app, nil, nil)
34 |
35 | ShowAppHelp(c)
36 |
37 | if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 {
38 | t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
39 | }
40 | }
41 |
42 | func Test_ShowAppHelp_HideVersion(t *testing.T) {
43 | output := new(bytes.Buffer)
44 | app := NewApp()
45 | app.Writer = output
46 |
47 | app.HideVersion = true
48 |
49 | c := NewContext(app, nil, nil)
50 |
51 | ShowAppHelp(c)
52 |
53 | if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 {
54 | t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
55 | }
56 | }
57 |
58 | func Test_Help_Custom_Flags(t *testing.T) {
59 | oldFlag := HelpFlag
60 | defer func() {
61 | HelpFlag = oldFlag
62 | }()
63 |
64 | HelpFlag = BoolFlag{
65 | Name: "help, x",
66 | Usage: "show help",
67 | }
68 |
69 | app := App{
70 | Flags: []Flag{
71 | BoolFlag{Name: "foo, h"},
72 | },
73 | Action: func(ctx *Context) error {
74 | if ctx.Bool("h") != true {
75 | t.Errorf("custom help flag not set")
76 | }
77 | return nil
78 | },
79 | }
80 | output := new(bytes.Buffer)
81 | app.Writer = output
82 | app.Run([]string{"test", "-h"})
83 | if output.Len() > 0 {
84 | t.Errorf("unexpected output: %s", output.String())
85 | }
86 | }
87 |
88 | func Test_Version_Custom_Flags(t *testing.T) {
89 | oldFlag := VersionFlag
90 | defer func() {
91 | VersionFlag = oldFlag
92 | }()
93 |
94 | VersionFlag = BoolFlag{
95 | Name: "version, V",
96 | Usage: "show version",
97 | }
98 |
99 | app := App{
100 | Flags: []Flag{
101 | BoolFlag{Name: "foo, v"},
102 | },
103 | Action: func(ctx *Context) error {
104 | if ctx.Bool("v") != true {
105 | t.Errorf("custom version flag not set")
106 | }
107 | return nil
108 | },
109 | }
110 | output := new(bytes.Buffer)
111 | app.Writer = output
112 | app.Run([]string{"test", "-v"})
113 | if output.Len() > 0 {
114 | t.Errorf("unexpected output: %s", output.String())
115 | }
116 | }
117 |
118 | func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) {
119 | app := NewApp()
120 |
121 | set := flag.NewFlagSet("test", 0)
122 | set.Parse([]string{"foo"})
123 |
124 | c := NewContext(app, set, nil)
125 |
126 | err := helpCommand.Action.(func(*Context) error)(c)
127 |
128 | if err == nil {
129 | t.Fatalf("expected error from helpCommand.Action(), but got nil")
130 | }
131 |
132 | exitErr, ok := err.(*ExitError)
133 | if !ok {
134 | t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error())
135 | }
136 |
137 | if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
138 | t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error())
139 | }
140 |
141 | if exitErr.exitCode != 3 {
142 | t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode)
143 | }
144 | }
145 |
146 | func Test_helpCommand_InHelpOutput(t *testing.T) {
147 | app := NewApp()
148 | output := &bytes.Buffer{}
149 | app.Writer = output
150 | app.Run([]string{"test", "--help"})
151 |
152 | s := output.String()
153 |
154 | if strings.Contains(s, "\nCOMMANDS:\nGLOBAL OPTIONS:\n") {
155 | t.Fatalf("empty COMMANDS section detected: %q", s)
156 | }
157 |
158 | if !strings.Contains(s, "help, h") {
159 | t.Fatalf("missing \"help, h\": %q", s)
160 | }
161 | }
162 |
163 | func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
164 | app := NewApp()
165 |
166 | set := flag.NewFlagSet("test", 0)
167 | set.Parse([]string{"foo"})
168 |
169 | c := NewContext(app, set, nil)
170 |
171 | err := helpSubcommand.Action.(func(*Context) error)(c)
172 |
173 | if err == nil {
174 | t.Fatalf("expected error from helpCommand.Action(), but got nil")
175 | }
176 |
177 | exitErr, ok := err.(*ExitError)
178 | if !ok {
179 | t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error())
180 | }
181 |
182 | if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
183 | t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error())
184 | }
185 |
186 | if exitErr.exitCode != 3 {
187 | t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode)
188 | }
189 | }
190 |
191 | func TestShowAppHelp_CommandAliases(t *testing.T) {
192 | app := &App{
193 | Commands: []Command{
194 | {
195 | Name: "frobbly",
196 | Aliases: []string{"fr", "frob"},
197 | Action: func(ctx *Context) error {
198 | return nil
199 | },
200 | },
201 | },
202 | }
203 |
204 | output := &bytes.Buffer{}
205 | app.Writer = output
206 | app.Run([]string{"foo", "--help"})
207 |
208 | if !strings.Contains(output.String(), "frobbly, fr, frob") {
209 | t.Errorf("expected output to include all command aliases; got: %q", output.String())
210 | }
211 | }
212 |
213 | func TestShowCommandHelp_CommandAliases(t *testing.T) {
214 | app := &App{
215 | Commands: []Command{
216 | {
217 | Name: "frobbly",
218 | Aliases: []string{"fr", "frob", "bork"},
219 | Action: func(ctx *Context) error {
220 | return nil
221 | },
222 | },
223 | },
224 | }
225 |
226 | output := &bytes.Buffer{}
227 | app.Writer = output
228 | app.Run([]string{"foo", "help", "fr"})
229 |
230 | if !strings.Contains(output.String(), "frobbly") {
231 | t.Errorf("expected output to include command name; got: %q", output.String())
232 | }
233 |
234 | if strings.Contains(output.String(), "bork") {
235 | t.Errorf("expected output to exclude command aliases; got: %q", output.String())
236 | }
237 | }
238 |
239 | func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
240 | app := &App{
241 | Commands: []Command{
242 | {
243 | Name: "frobbly",
244 | Aliases: []string{"fr", "frob", "bork"},
245 | Action: func(ctx *Context) error {
246 | return nil
247 | },
248 | },
249 | },
250 | }
251 |
252 | output := &bytes.Buffer{}
253 | app.Writer = output
254 | app.Run([]string{"foo", "help"})
255 |
256 | if !strings.Contains(output.String(), "frobbly, fr, frob, bork") {
257 | t.Errorf("expected output to include all command aliases; got: %q", output.String())
258 | }
259 | }
260 |
261 | func TestShowCommandHelp_Customtemplate(t *testing.T) {
262 | app := &App{
263 | Commands: []Command{
264 | {
265 | Name: "frobbly",
266 | Action: func(ctx *Context) error {
267 | return nil
268 | },
269 | HelpName: "foo frobbly",
270 | CustomHelpTemplate: `NAME:
271 | {{.HelpName}} - {{.Usage}}
272 |
273 | USAGE:
274 | {{.HelpName}} [FLAGS] TARGET [TARGET ...]
275 |
276 | FLAGS:
277 | {{range .VisibleFlags}}{{.}}
278 | {{end}}
279 | EXAMPLES:
280 | 1. Frobbly runs with this param locally.
281 | $ {{.HelpName}} wobbly
282 | `,
283 | },
284 | },
285 | }
286 | output := &bytes.Buffer{}
287 | app.Writer = output
288 | app.Run([]string{"foo", "help", "frobbly"})
289 |
290 | if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") {
291 | t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String())
292 | }
293 |
294 | if !strings.Contains(output.String(), "1. Frobbly runs with this param locally.") {
295 | t.Errorf("expected output to include \"1. Frobbly runs with this param locally.\"; got: %q", output.String())
296 | }
297 |
298 | if !strings.Contains(output.String(), "$ foo frobbly wobbly") {
299 | t.Errorf("expected output to include \"$ foo frobbly wobbly\"; got: %q", output.String())
300 | }
301 | }
302 |
303 | func TestShowSubcommandHelp_CommandUsageText(t *testing.T) {
304 | app := &App{
305 | Commands: []Command{
306 | {
307 | Name: "frobbly",
308 | UsageText: "this is usage text",
309 | },
310 | },
311 | }
312 |
313 | output := &bytes.Buffer{}
314 | app.Writer = output
315 |
316 | app.Run([]string{"foo", "frobbly", "--help"})
317 |
318 | if !strings.Contains(output.String(), "this is usage text") {
319 | t.Errorf("expected output to include usage text; got: %q", output.String())
320 | }
321 | }
322 |
323 | func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
324 | app := &App{
325 | Commands: []Command{
326 | {
327 | Name: "frobbly",
328 | Subcommands: []Command{
329 | {
330 | Name: "bobbly",
331 | UsageText: "this is usage text",
332 | },
333 | },
334 | },
335 | },
336 | }
337 |
338 | output := &bytes.Buffer{}
339 | app.Writer = output
340 | app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
341 |
342 | if !strings.Contains(output.String(), "this is usage text") {
343 | t.Errorf("expected output to include usage text; got: %q", output.String())
344 | }
345 | }
346 |
347 | func TestShowAppHelp_HiddenCommand(t *testing.T) {
348 | app := &App{
349 | Commands: []Command{
350 | {
351 | Name: "frobbly",
352 | Action: func(ctx *Context) error {
353 | return nil
354 | },
355 | },
356 | {
357 | Name: "secretfrob",
358 | Hidden: true,
359 | Action: func(ctx *Context) error {
360 | return nil
361 | },
362 | },
363 | },
364 | }
365 |
366 | output := &bytes.Buffer{}
367 | app.Writer = output
368 | app.Run([]string{"app", "--help"})
369 |
370 | if strings.Contains(output.String(), "secretfrob") {
371 | t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String())
372 | }
373 |
374 | if !strings.Contains(output.String(), "frobbly") {
375 | t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
376 | }
377 | }
378 |
379 | func TestShowAppHelp_CustomAppTemplate(t *testing.T) {
380 | app := &App{
381 | Commands: []Command{
382 | {
383 | Name: "frobbly",
384 | Action: func(ctx *Context) error {
385 | return nil
386 | },
387 | },
388 | {
389 | Name: "secretfrob",
390 | Hidden: true,
391 | Action: func(ctx *Context) error {
392 | return nil
393 | },
394 | },
395 | },
396 | ExtraInfo: func() map[string]string {
397 | platform := fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH)
398 | goruntime := fmt.Sprintf("Version: %s | CPUs: %d", runtime.Version(), runtime.NumCPU())
399 | return map[string]string{
400 | "PLATFORM": platform,
401 | "RUNTIME": goruntime,
402 | }
403 | },
404 | CustomAppHelpTemplate: `NAME:
405 | {{.Name}} - {{.Usage}}
406 |
407 | USAGE:
408 | {{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...]
409 |
410 | COMMANDS:
411 | {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
412 | {{end}}{{if .VisibleFlags}}
413 | GLOBAL FLAGS:
414 | {{range .VisibleFlags}}{{.}}
415 | {{end}}{{end}}
416 | VERSION:
417 | 2.0.0
418 | {{"\n"}}{{range $key, $value := ExtraInfo}}
419 | {{$key}}:
420 | {{$value}}
421 | {{end}}`,
422 | }
423 |
424 | output := &bytes.Buffer{}
425 | app.Writer = output
426 | app.Run([]string{"app", "--help"})
427 |
428 | if strings.Contains(output.String(), "secretfrob") {
429 | t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String())
430 | }
431 |
432 | if !strings.Contains(output.String(), "frobbly") {
433 | t.Errorf("expected output to include \"frobbly\"; got: %q", output.String())
434 | }
435 |
436 | if !strings.Contains(output.String(), "PLATFORM:") ||
437 | !strings.Contains(output.String(), "OS:") ||
438 | !strings.Contains(output.String(), "Arch:") {
439 | t.Errorf("expected output to include \"PLATFORM:, OS: and Arch:\"; got: %q", output.String())
440 | }
441 |
442 | if !strings.Contains(output.String(), "RUNTIME:") ||
443 | !strings.Contains(output.String(), "Version:") ||
444 | !strings.Contains(output.String(), "CPUs:") {
445 | t.Errorf("expected output to include \"RUNTIME:, Version: and CPUs:\"; got: %q", output.String())
446 | }
447 |
448 | if !strings.Contains(output.String(), "VERSION:") ||
449 | !strings.Contains(output.String(), "2.0.0") {
450 | t.Errorf("expected output to include \"VERSION:, 2.0.0\"; got: %q", output.String())
451 | }
452 | }
453 |
--------------------------------------------------------------------------------
/context_test.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "flag"
5 | "os"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestNewContext(t *testing.T) {
11 | set := flag.NewFlagSet("test", 0)
12 | set.Int("myflag", 12, "doc")
13 | set.Int64("myflagInt64", int64(12), "doc")
14 | set.Uint("myflagUint", uint(93), "doc")
15 | set.Uint64("myflagUint64", uint64(93), "doc")
16 | set.Float64("myflag64", float64(17), "doc")
17 | globalSet := flag.NewFlagSet("test", 0)
18 | globalSet.Int("myflag", 42, "doc")
19 | globalSet.Int64("myflagInt64", int64(42), "doc")
20 | globalSet.Uint("myflagUint", uint(33), "doc")
21 | globalSet.Uint64("myflagUint64", uint64(33), "doc")
22 | globalSet.Float64("myflag64", float64(47), "doc")
23 | globalCtx := NewContext(nil, globalSet, nil)
24 | command := Command{Name: "mycommand"}
25 | c := NewContext(nil, set, globalCtx)
26 | c.Command = command
27 | expect(t, c.Int("myflag"), 12)
28 | expect(t, c.Int64("myflagInt64"), int64(12))
29 | expect(t, c.Uint("myflagUint"), uint(93))
30 | expect(t, c.Uint64("myflagUint64"), uint64(93))
31 | expect(t, c.Float64("myflag64"), float64(17))
32 | expect(t, c.GlobalInt("myflag"), 42)
33 | expect(t, c.GlobalInt64("myflagInt64"), int64(42))
34 | expect(t, c.GlobalUint("myflagUint"), uint(33))
35 | expect(t, c.GlobalUint64("myflagUint64"), uint64(33))
36 | expect(t, c.GlobalFloat64("myflag64"), float64(47))
37 | expect(t, c.Command.Name, "mycommand")
38 | }
39 |
40 | func TestContext_Int(t *testing.T) {
41 | set := flag.NewFlagSet("test", 0)
42 | set.Int("myflag", 12, "doc")
43 | c := NewContext(nil, set, nil)
44 | expect(t, c.Int("myflag"), 12)
45 | }
46 |
47 | func TestContext_Int64(t *testing.T) {
48 | set := flag.NewFlagSet("test", 0)
49 | set.Int64("myflagInt64", 12, "doc")
50 | c := NewContext(nil, set, nil)
51 | expect(t, c.Int64("myflagInt64"), int64(12))
52 | }
53 |
54 | func TestContext_Uint(t *testing.T) {
55 | set := flag.NewFlagSet("test", 0)
56 | set.Uint("myflagUint", uint(13), "doc")
57 | c := NewContext(nil, set, nil)
58 | expect(t, c.Uint("myflagUint"), uint(13))
59 | }
60 |
61 | func TestContext_Uint64(t *testing.T) {
62 | set := flag.NewFlagSet("test", 0)
63 | set.Uint64("myflagUint64", uint64(9), "doc")
64 | c := NewContext(nil, set, nil)
65 | expect(t, c.Uint64("myflagUint64"), uint64(9))
66 | }
67 |
68 | func TestContext_GlobalInt(t *testing.T) {
69 | set := flag.NewFlagSet("test", 0)
70 | set.Int("myflag", 12, "doc")
71 | c := NewContext(nil, set, nil)
72 | expect(t, c.GlobalInt("myflag"), 12)
73 | expect(t, c.GlobalInt("nope"), 0)
74 | }
75 |
76 | func TestContext_GlobalInt64(t *testing.T) {
77 | set := flag.NewFlagSet("test", 0)
78 | set.Int64("myflagInt64", 12, "doc")
79 | c := NewContext(nil, set, nil)
80 | expect(t, c.GlobalInt64("myflagInt64"), int64(12))
81 | expect(t, c.GlobalInt64("nope"), int64(0))
82 | }
83 |
84 | func TestContext_Float64(t *testing.T) {
85 | set := flag.NewFlagSet("test", 0)
86 | set.Float64("myflag", float64(17), "doc")
87 | c := NewContext(nil, set, nil)
88 | expect(t, c.Float64("myflag"), float64(17))
89 | }
90 |
91 | func TestContext_GlobalFloat64(t *testing.T) {
92 | set := flag.NewFlagSet("test", 0)
93 | set.Float64("myflag", float64(17), "doc")
94 | c := NewContext(nil, set, nil)
95 | expect(t, c.GlobalFloat64("myflag"), float64(17))
96 | expect(t, c.GlobalFloat64("nope"), float64(0))
97 | }
98 |
99 | func TestContext_Duration(t *testing.T) {
100 | set := flag.NewFlagSet("test", 0)
101 | set.Duration("myflag", time.Duration(12*time.Second), "doc")
102 | c := NewContext(nil, set, nil)
103 | expect(t, c.Duration("myflag"), time.Duration(12*time.Second))
104 | }
105 |
106 | func TestContext_String(t *testing.T) {
107 | set := flag.NewFlagSet("test", 0)
108 | set.String("myflag", "hello world", "doc")
109 | c := NewContext(nil, set, nil)
110 | expect(t, c.String("myflag"), "hello world")
111 | }
112 |
113 | func TestContext_Bool(t *testing.T) {
114 | set := flag.NewFlagSet("test", 0)
115 | set.Bool("myflag", false, "doc")
116 | c := NewContext(nil, set, nil)
117 | expect(t, c.Bool("myflag"), false)
118 | }
119 |
120 | func TestContext_BoolT(t *testing.T) {
121 | set := flag.NewFlagSet("test", 0)
122 | set.Bool("myflag", true, "doc")
123 | c := NewContext(nil, set, nil)
124 | expect(t, c.BoolT("myflag"), true)
125 | }
126 |
127 | func TestContext_GlobalBool(t *testing.T) {
128 | set := flag.NewFlagSet("test", 0)
129 |
130 | globalSet := flag.NewFlagSet("test-global", 0)
131 | globalSet.Bool("myflag", false, "doc")
132 | globalCtx := NewContext(nil, globalSet, nil)
133 |
134 | c := NewContext(nil, set, globalCtx)
135 | expect(t, c.GlobalBool("myflag"), false)
136 | expect(t, c.GlobalBool("nope"), false)
137 | }
138 |
139 | func TestContext_GlobalBoolT(t *testing.T) {
140 | set := flag.NewFlagSet("test", 0)
141 |
142 | globalSet := flag.NewFlagSet("test-global", 0)
143 | globalSet.Bool("myflag", true, "doc")
144 | globalCtx := NewContext(nil, globalSet, nil)
145 |
146 | c := NewContext(nil, set, globalCtx)
147 | expect(t, c.GlobalBoolT("myflag"), true)
148 | expect(t, c.GlobalBoolT("nope"), false)
149 | }
150 |
151 | func TestContext_Args(t *testing.T) {
152 | set := flag.NewFlagSet("test", 0)
153 | set.Bool("myflag", false, "doc")
154 | c := NewContext(nil, set, nil)
155 | set.Parse([]string{"--myflag", "bat", "baz"})
156 | expect(t, len(c.Args()), 2)
157 | expect(t, c.Bool("myflag"), true)
158 | }
159 |
160 | func TestContext_NArg(t *testing.T) {
161 | set := flag.NewFlagSet("test", 0)
162 | set.Bool("myflag", false, "doc")
163 | c := NewContext(nil, set, nil)
164 | set.Parse([]string{"--myflag", "bat", "baz"})
165 | expect(t, c.NArg(), 2)
166 | }
167 |
168 | func TestContext_IsSet(t *testing.T) {
169 | set := flag.NewFlagSet("test", 0)
170 | set.Bool("myflag", false, "doc")
171 | set.String("otherflag", "hello world", "doc")
172 | globalSet := flag.NewFlagSet("test", 0)
173 | globalSet.Bool("myflagGlobal", true, "doc")
174 | globalCtx := NewContext(nil, globalSet, nil)
175 | c := NewContext(nil, set, globalCtx)
176 | set.Parse([]string{"--myflag", "bat", "baz"})
177 | globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
178 | expect(t, c.IsSet("myflag"), true)
179 | expect(t, c.IsSet("otherflag"), false)
180 | expect(t, c.IsSet("bogusflag"), false)
181 | expect(t, c.IsSet("myflagGlobal"), false)
182 | }
183 |
184 | // XXX Corresponds to hack in context.IsSet for flags with EnvVar field
185 | // Should be moved to `flag_test` in v2
186 | func TestContext_IsSet_fromEnv(t *testing.T) {
187 | var (
188 | timeoutIsSet, tIsSet bool
189 | noEnvVarIsSet, nIsSet bool
190 | passwordIsSet, pIsSet bool
191 | unparsableIsSet, uIsSet bool
192 | )
193 |
194 | clearenv()
195 | os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
196 | os.Setenv("APP_PASSWORD", "")
197 | a := App{
198 | Flags: []Flag{
199 | Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
200 | StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"},
201 | Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"},
202 | Float64Flag{Name: "no-env-var, n"},
203 | },
204 | Action: func(ctx *Context) error {
205 | timeoutIsSet = ctx.IsSet("timeout")
206 | tIsSet = ctx.IsSet("t")
207 | passwordIsSet = ctx.IsSet("password")
208 | pIsSet = ctx.IsSet("p")
209 | unparsableIsSet = ctx.IsSet("unparsable")
210 | uIsSet = ctx.IsSet("u")
211 | noEnvVarIsSet = ctx.IsSet("no-env-var")
212 | nIsSet = ctx.IsSet("n")
213 | return nil
214 | },
215 | }
216 | a.Run([]string{"run"})
217 | expect(t, timeoutIsSet, true)
218 | expect(t, tIsSet, true)
219 | expect(t, passwordIsSet, true)
220 | expect(t, pIsSet, true)
221 | expect(t, noEnvVarIsSet, false)
222 | expect(t, nIsSet, false)
223 |
224 | os.Setenv("APP_UNPARSABLE", "foobar")
225 | a.Run([]string{"run"})
226 | expect(t, unparsableIsSet, false)
227 | expect(t, uIsSet, false)
228 | }
229 |
230 | func TestContext_GlobalIsSet(t *testing.T) {
231 | set := flag.NewFlagSet("test", 0)
232 | set.Bool("myflag", false, "doc")
233 | set.String("otherflag", "hello world", "doc")
234 | globalSet := flag.NewFlagSet("test", 0)
235 | globalSet.Bool("myflagGlobal", true, "doc")
236 | globalSet.Bool("myflagGlobalUnset", true, "doc")
237 | globalCtx := NewContext(nil, globalSet, nil)
238 | c := NewContext(nil, set, globalCtx)
239 | set.Parse([]string{"--myflag", "bat", "baz"})
240 | globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
241 | expect(t, c.GlobalIsSet("myflag"), false)
242 | expect(t, c.GlobalIsSet("otherflag"), false)
243 | expect(t, c.GlobalIsSet("bogusflag"), false)
244 | expect(t, c.GlobalIsSet("myflagGlobal"), true)
245 | expect(t, c.GlobalIsSet("myflagGlobalUnset"), false)
246 | expect(t, c.GlobalIsSet("bogusGlobal"), false)
247 | }
248 |
249 | // XXX Corresponds to hack in context.IsSet for flags with EnvVar field
250 | // Should be moved to `flag_test` in v2
251 | func TestContext_GlobalIsSet_fromEnv(t *testing.T) {
252 | var (
253 | timeoutIsSet, tIsSet bool
254 | noEnvVarIsSet, nIsSet bool
255 | passwordIsSet, pIsSet bool
256 | unparsableIsSet, uIsSet bool
257 | )
258 |
259 | clearenv()
260 | os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
261 | os.Setenv("APP_PASSWORD", "")
262 | a := App{
263 | Flags: []Flag{
264 | Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
265 | StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"},
266 | Float64Flag{Name: "no-env-var, n"},
267 | Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"},
268 | },
269 | Commands: []Command{
270 | {
271 | Name: "hello",
272 | Action: func(ctx *Context) error {
273 | timeoutIsSet = ctx.GlobalIsSet("timeout")
274 | tIsSet = ctx.GlobalIsSet("t")
275 | passwordIsSet = ctx.GlobalIsSet("password")
276 | pIsSet = ctx.GlobalIsSet("p")
277 | unparsableIsSet = ctx.GlobalIsSet("unparsable")
278 | uIsSet = ctx.GlobalIsSet("u")
279 | noEnvVarIsSet = ctx.GlobalIsSet("no-env-var")
280 | nIsSet = ctx.GlobalIsSet("n")
281 | return nil
282 | },
283 | },
284 | },
285 | }
286 | if err := a.Run([]string{"run", "hello"}); err != nil {
287 | t.Logf("error running Run(): %+v", err)
288 | }
289 | expect(t, timeoutIsSet, true)
290 | expect(t, tIsSet, true)
291 | expect(t, passwordIsSet, true)
292 | expect(t, pIsSet, true)
293 | expect(t, noEnvVarIsSet, false)
294 | expect(t, nIsSet, false)
295 |
296 | os.Setenv("APP_UNPARSABLE", "foobar")
297 | if err := a.Run([]string{"run"}); err != nil {
298 | t.Logf("error running Run(): %+v", err)
299 | }
300 | expect(t, unparsableIsSet, false)
301 | expect(t, uIsSet, false)
302 | }
303 |
304 | func TestContext_NumFlags(t *testing.T) {
305 | set := flag.NewFlagSet("test", 0)
306 | set.Bool("myflag", false, "doc")
307 | set.String("otherflag", "hello world", "doc")
308 | globalSet := flag.NewFlagSet("test", 0)
309 | globalSet.Bool("myflagGlobal", true, "doc")
310 | globalCtx := NewContext(nil, globalSet, nil)
311 | c := NewContext(nil, set, globalCtx)
312 | set.Parse([]string{"--myflag", "--otherflag=foo"})
313 | globalSet.Parse([]string{"--myflagGlobal"})
314 | expect(t, c.NumFlags(), 2)
315 | }
316 |
317 | func TestContext_GlobalFlag(t *testing.T) {
318 | var globalFlag string
319 | var globalFlagSet bool
320 | app := NewApp()
321 | app.Flags = []Flag{
322 | StringFlag{Name: "global, g", Usage: "global"},
323 | }
324 | app.Action = func(c *Context) error {
325 | globalFlag = c.GlobalString("global")
326 | globalFlagSet = c.GlobalIsSet("global")
327 | return nil
328 | }
329 | app.Run([]string{"command", "-g", "foo"})
330 | expect(t, globalFlag, "foo")
331 | expect(t, globalFlagSet, true)
332 |
333 | }
334 |
335 | func TestContext_GlobalFlagsInSubcommands(t *testing.T) {
336 | subcommandRun := false
337 | parentFlag := false
338 | app := NewApp()
339 |
340 | app.Flags = []Flag{
341 | BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
342 | }
343 |
344 | app.Commands = []Command{
345 | {
346 | Name: "foo",
347 | Flags: []Flag{
348 | BoolFlag{Name: "parent, p", Usage: "Parent flag"},
349 | },
350 | Subcommands: []Command{
351 | {
352 | Name: "bar",
353 | Action: func(c *Context) error {
354 | if c.GlobalBool("debug") {
355 | subcommandRun = true
356 | }
357 | if c.GlobalBool("parent") {
358 | parentFlag = true
359 | }
360 | return nil
361 | },
362 | },
363 | },
364 | },
365 | }
366 |
367 | app.Run([]string{"command", "-d", "foo", "-p", "bar"})
368 |
369 | expect(t, subcommandRun, true)
370 | expect(t, parentFlag, true)
371 | }
372 |
373 | func TestContext_Set(t *testing.T) {
374 | set := flag.NewFlagSet("test", 0)
375 | set.Int("int", 5, "an int")
376 | c := NewContext(nil, set, nil)
377 |
378 | expect(t, c.IsSet("int"), false)
379 | c.Set("int", "1")
380 | expect(t, c.Int("int"), 1)
381 | expect(t, c.IsSet("int"), true)
382 | }
383 |
384 | func TestContext_GlobalSet(t *testing.T) {
385 | gSet := flag.NewFlagSet("test", 0)
386 | gSet.Int("int", 5, "an int")
387 |
388 | set := flag.NewFlagSet("sub", 0)
389 | set.Int("int", 3, "an int")
390 |
391 | pc := NewContext(nil, gSet, nil)
392 | c := NewContext(nil, set, pc)
393 |
394 | c.Set("int", "1")
395 | expect(t, c.Int("int"), 1)
396 | expect(t, c.GlobalInt("int"), 5)
397 |
398 | expect(t, c.GlobalIsSet("int"), false)
399 | c.GlobalSet("int", "1")
400 | expect(t, c.Int("int"), 1)
401 | expect(t, c.GlobalInt("int"), 1)
402 | expect(t, c.GlobalIsSet("int"), true)
403 | }
404 |
--------------------------------------------------------------------------------
/app.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "io/ioutil"
7 | "os"
8 | "path/filepath"
9 | "sort"
10 | "time"
11 | )
12 |
13 | var (
14 | changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
15 | appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
16 | runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
17 |
18 | contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
19 |
20 | errInvalidActionType = NewExitError("ERROR invalid Action type. "+
21 | fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
22 | fmt.Sprintf("See %s", appActionDeprecationURL), 2)
23 | )
24 |
25 | // App is the main structure of a cli application. It is recommended that
26 | // an app be created with the cli.NewApp() function
27 | type App struct {
28 | // The name of the program. Defaults to path.Base(os.Args[0])
29 | Name string
30 | // Full name of command for help, defaults to Name
31 | HelpName string
32 | // Description of the program.
33 | Usage string
34 | // Text to override the USAGE section of help
35 | UsageText string
36 | // Description of the program argument format.
37 | ArgsUsage string
38 | // Version of the program
39 | Version string
40 | // Description of the program
41 | Description string
42 | // List of commands to execute
43 | Commands []Command
44 | // List of flags to parse
45 | Flags []Flag
46 | // Boolean to enable bash completion commands
47 | EnableBashCompletion bool
48 | // Boolean to hide built-in help command
49 | HideHelp bool
50 | // Boolean to hide built-in version flag and the VERSION section of help
51 | HideVersion bool
52 | // Populate on app startup, only gettable through method Categories()
53 | categories CommandCategories
54 | // An action to execute when the bash-completion flag is set
55 | BashComplete BashCompleteFunc
56 | // An action to execute before any subcommands are run, but after the context is ready
57 | // If a non-nil error is returned, no subcommands are run
58 | Before BeforeFunc
59 | // An action to execute after any subcommands are run, but after the subcommand has finished
60 | // It is run even if Action() panics
61 | After AfterFunc
62 |
63 | // The action to execute when no subcommands are specified
64 | // Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
65 | // *Note*: support for the deprecated `Action` signature will be removed in a future version
66 | Action interface{}
67 |
68 | // Execute this function if the proper command cannot be found
69 | CommandNotFound CommandNotFoundFunc
70 | // Execute this function if an usage error occurs
71 | OnUsageError OnUsageErrorFunc
72 | // Compilation date
73 | Compiled time.Time
74 | // List of all authors who contributed
75 | Authors []Author
76 | // Copyright of the binary if any
77 | Copyright string
78 | // Name of Author (Note: Use App.Authors, this is deprecated)
79 | Author string
80 | // Email of Author (Note: Use App.Authors, this is deprecated)
81 | Email string
82 | // Writer writer to write output to
83 | Writer io.Writer
84 | // ErrWriter writes error output
85 | ErrWriter io.Writer
86 | // Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to
87 | // function as a default, so this is optional.
88 | ExitErrHandler ExitErrHandlerFunc
89 | // Other custom info
90 | Metadata map[string]interface{}
91 | // Carries a function which returns app specific info.
92 | ExtraInfo func() map[string]string
93 | // CustomAppHelpTemplate the text template for app help topic.
94 | // cli.go uses text/template to render templates. You can
95 | // render custom help text by setting this variable.
96 | CustomAppHelpTemplate string
97 |
98 | didSetup bool
99 | }
100 |
101 | // Tries to find out when this binary was compiled.
102 | // Returns the current time if it fails to find it.
103 | func compileTime() time.Time {
104 | info, err := os.Stat(os.Args[0])
105 | if err != nil {
106 | return time.Now()
107 | }
108 | return info.ModTime()
109 | }
110 |
111 | // NewApp creates a new cli Application with some reasonable defaults for Name,
112 | // Usage, Version and Action.
113 | func NewApp() *App {
114 | return &App{
115 | Name: filepath.Base(os.Args[0]),
116 | HelpName: filepath.Base(os.Args[0]),
117 | Usage: "A new cli application",
118 | UsageText: "",
119 | Version: "0.0.0",
120 | BashComplete: DefaultAppComplete,
121 | Action: helpCommand.Action,
122 | Compiled: compileTime(),
123 | Writer: os.Stdout,
124 | }
125 | }
126 |
127 | // Setup runs initialization code to ensure all data structures are ready for
128 | // `Run` or inspection prior to `Run`. It is internally called by `Run`, but
129 | // will return early if setup has already happened.
130 | func (a *App) Setup() {
131 | if a.didSetup {
132 | return
133 | }
134 |
135 | a.didSetup = true
136 |
137 | if a.Author != "" || a.Email != "" {
138 | a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
139 | }
140 |
141 | newCmds := []Command{}
142 | for _, c := range a.Commands {
143 | if c.HelpName == "" {
144 | c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
145 | }
146 | newCmds = append(newCmds, c)
147 | }
148 | a.Commands = newCmds
149 |
150 | if a.Command(helpCommand.Name) == nil && !a.HideHelp {
151 | a.Commands = append(a.Commands, helpCommand)
152 | if (HelpFlag != BoolFlag{}) {
153 | a.appendFlag(HelpFlag)
154 | }
155 | }
156 |
157 | if !a.HideVersion {
158 | a.appendFlag(VersionFlag)
159 | }
160 |
161 | a.categories = CommandCategories{}
162 | for _, command := range a.Commands {
163 | a.categories = a.categories.AddCommand(command.Category, command)
164 | }
165 | sort.Sort(a.categories)
166 |
167 | if a.Metadata == nil {
168 | a.Metadata = make(map[string]interface{})
169 | }
170 |
171 | if a.Writer == nil {
172 | a.Writer = os.Stdout
173 | }
174 | }
175 |
176 | // Run is the entry point to the cli app. Parses the arguments slice and routes
177 | // to the proper flag/args combination
178 | func (a *App) Run(arguments []string) (err error) {
179 | a.Setup()
180 |
181 | // handle the completion flag separately from the flagset since
182 | // completion could be attempted after a flag, but before its value was put
183 | // on the command line. this causes the flagset to interpret the completion
184 | // flag name as the value of the flag before it which is undesirable
185 | // note that we can only do this because the shell autocomplete function
186 | // always appends the completion flag at the end of the command
187 | shellComplete, arguments := checkShellCompleteFlag(a, arguments)
188 |
189 | // parse flags
190 | set, err := flagSet(a.Name, a.Flags)
191 | if err != nil {
192 | return err
193 | }
194 |
195 | set.SetOutput(ioutil.Discard)
196 | err = set.Parse(arguments[1:])
197 | nerr := normalizeFlags(a.Flags, set)
198 | context := NewContext(a, set, nil)
199 | if nerr != nil {
200 | fmt.Fprintln(a.Writer, nerr)
201 | ShowAppHelp(context)
202 | return nerr
203 | }
204 | context.shellComplete = shellComplete
205 |
206 | if checkCompletions(context) {
207 | return nil
208 | }
209 |
210 | if err != nil {
211 | if a.OnUsageError != nil {
212 | err := a.OnUsageError(context, err, false)
213 | a.handleExitCoder(context, err)
214 | return err
215 | }
216 | fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
217 | ShowAppHelp(context)
218 | return err
219 | }
220 |
221 | if !a.HideHelp && checkHelp(context) {
222 | ShowAppHelp(context)
223 | return nil
224 | }
225 |
226 | if !a.HideVersion && checkVersion(context) {
227 | ShowVersion(context)
228 | return nil
229 | }
230 |
231 | if a.After != nil {
232 | defer func() {
233 | if afterErr := a.After(context); afterErr != nil {
234 | if err != nil {
235 | err = NewMultiError(err, afterErr)
236 | } else {
237 | err = afterErr
238 | }
239 | }
240 | }()
241 | }
242 |
243 | if a.Before != nil {
244 | beforeErr := a.Before(context)
245 | if beforeErr != nil {
246 | fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
247 | ShowAppHelp(context)
248 | a.handleExitCoder(context, beforeErr)
249 | err = beforeErr
250 | return err
251 | }
252 | }
253 |
254 | args := context.Args()
255 | if args.Present() {
256 | name := args.First()
257 | c := a.Command(name)
258 | if c != nil {
259 | return c.Run(context)
260 | }
261 | }
262 |
263 | if a.Action == nil {
264 | a.Action = helpCommand.Action
265 | }
266 |
267 | // Run default Action
268 | err = HandleAction(a.Action, context)
269 |
270 | a.handleExitCoder(context, err)
271 | return err
272 | }
273 |
274 | // RunAndExitOnError calls .Run() and exits non-zero if an error was returned
275 | //
276 | // Deprecated: instead you should return an error that fulfills cli.ExitCoder
277 | // to cli.App.Run. This will cause the application to exit with the given eror
278 | // code in the cli.ExitCoder
279 | func (a *App) RunAndExitOnError() {
280 | if err := a.Run(os.Args); err != nil {
281 | fmt.Fprintln(a.errWriter(), err)
282 | OsExiter(1)
283 | }
284 | }
285 |
286 | // RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
287 | // generate command-specific flags
288 | func (a *App) RunAsSubcommand(ctx *Context) (err error) {
289 | // append help to commands
290 | if len(a.Commands) > 0 {
291 | if a.Command(helpCommand.Name) == nil && !a.HideHelp {
292 | a.Commands = append(a.Commands, helpCommand)
293 | if (HelpFlag != BoolFlag{}) {
294 | a.appendFlag(HelpFlag)
295 | }
296 | }
297 | }
298 |
299 | newCmds := []Command{}
300 | for _, c := range a.Commands {
301 | if c.HelpName == "" {
302 | c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
303 | }
304 | newCmds = append(newCmds, c)
305 | }
306 | a.Commands = newCmds
307 |
308 | // parse flags
309 | set, err := flagSet(a.Name, a.Flags)
310 | if err != nil {
311 | return err
312 | }
313 |
314 | set.SetOutput(ioutil.Discard)
315 | err = set.Parse(ctx.Args().Tail())
316 | nerr := normalizeFlags(a.Flags, set)
317 | context := NewContext(a, set, ctx)
318 |
319 | if nerr != nil {
320 | fmt.Fprintln(a.Writer, nerr)
321 | fmt.Fprintln(a.Writer)
322 | if len(a.Commands) > 0 {
323 | ShowSubcommandHelp(context)
324 | } else {
325 | ShowCommandHelp(ctx, context.Args().First())
326 | }
327 | return nerr
328 | }
329 |
330 | if checkCompletions(context) {
331 | return nil
332 | }
333 |
334 | if err != nil {
335 | if a.OnUsageError != nil {
336 | err = a.OnUsageError(context, err, true)
337 | a.handleExitCoder(context, err)
338 | return err
339 | }
340 | fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
341 | ShowSubcommandHelp(context)
342 | return err
343 | }
344 |
345 | if len(a.Commands) > 0 {
346 | if checkSubcommandHelp(context) {
347 | return nil
348 | }
349 | } else {
350 | if checkCommandHelp(ctx, context.Args().First()) {
351 | return nil
352 | }
353 | }
354 |
355 | if a.After != nil {
356 | defer func() {
357 | afterErr := a.After(context)
358 | if afterErr != nil {
359 | a.handleExitCoder(context, err)
360 | if err != nil {
361 | err = NewMultiError(err, afterErr)
362 | } else {
363 | err = afterErr
364 | }
365 | }
366 | }()
367 | }
368 |
369 | if a.Before != nil {
370 | beforeErr := a.Before(context)
371 | if beforeErr != nil {
372 | a.handleExitCoder(context, beforeErr)
373 | err = beforeErr
374 | return err
375 | }
376 | }
377 |
378 | args := context.Args()
379 | if args.Present() {
380 | name := args.First()
381 | c := a.Command(name)
382 | if c != nil {
383 | return c.Run(context)
384 | }
385 | }
386 |
387 | // Run default Action
388 | err = HandleAction(a.Action, context)
389 |
390 | a.handleExitCoder(context, err)
391 | return err
392 | }
393 |
394 | // Command returns the named command on App. Returns nil if the command does not exist
395 | func (a *App) Command(name string) *Command {
396 | for _, c := range a.Commands {
397 | if c.HasName(name) {
398 | return &c
399 | }
400 | }
401 |
402 | return nil
403 | }
404 |
405 | // Categories returns a slice containing all the categories with the commands they contain
406 | func (a *App) Categories() CommandCategories {
407 | return a.categories
408 | }
409 |
410 | // VisibleCategories returns a slice of categories and commands that are
411 | // Hidden=false
412 | func (a *App) VisibleCategories() []*CommandCategory {
413 | ret := []*CommandCategory{}
414 | for _, category := range a.categories {
415 | if visible := func() *CommandCategory {
416 | for _, command := range category.Commands {
417 | if !command.Hidden {
418 | return category
419 | }
420 | }
421 | return nil
422 | }(); visible != nil {
423 | ret = append(ret, visible)
424 | }
425 | }
426 | return ret
427 | }
428 |
429 | // VisibleCommands returns a slice of the Commands with Hidden=false
430 | func (a *App) VisibleCommands() []Command {
431 | ret := []Command{}
432 | for _, command := range a.Commands {
433 | if !command.Hidden {
434 | ret = append(ret, command)
435 | }
436 | }
437 | return ret
438 | }
439 |
440 | // VisibleFlags returns a slice of the Flags with Hidden=false
441 | func (a *App) VisibleFlags() []Flag {
442 | return visibleFlags(a.Flags)
443 | }
444 |
445 | func (a *App) hasFlag(flag Flag) bool {
446 | for _, f := range a.Flags {
447 | if flag == f {
448 | return true
449 | }
450 | }
451 |
452 | return false
453 | }
454 |
455 | func (a *App) errWriter() io.Writer {
456 | // When the app ErrWriter is nil use the package level one.
457 | if a.ErrWriter == nil {
458 | return ErrWriter
459 | }
460 |
461 | return a.ErrWriter
462 | }
463 |
464 | func (a *App) appendFlag(flag Flag) {
465 | if !a.hasFlag(flag) {
466 | a.Flags = append(a.Flags, flag)
467 | }
468 | }
469 |
470 | func (a *App) handleExitCoder(context *Context, err error) {
471 | if a.ExitErrHandler != nil {
472 | a.ExitErrHandler(context, err)
473 | } else {
474 | HandleExitCoder(err)
475 | }
476 | }
477 |
478 | // Author represents someone who has contributed to a cli project.
479 | type Author struct {
480 | Name string // The Authors name
481 | Email string // The Authors email
482 | }
483 |
484 | // String makes Author comply to the Stringer interface, to allow an easy print in the templating process
485 | func (a Author) String() string {
486 | e := ""
487 | if a.Email != "" {
488 | e = " <" + a.Email + ">"
489 | }
490 |
491 | return fmt.Sprintf("%v%v", a.Name, e)
492 | }
493 |
494 | // HandleAction attempts to figure out which Action signature was used. If
495 | // it's an ActionFunc or a func with the legacy signature for Action, the func
496 | // is run!
497 | func HandleAction(action interface{}, context *Context) (err error) {
498 | if a, ok := action.(ActionFunc); ok {
499 | return a(context)
500 | } else if a, ok := action.(func(*Context) error); ok {
501 | return a(context)
502 | } else if a, ok := action.(func(*Context)); ok { // deprecated function signature
503 | a(context)
504 | return nil
505 | }
506 |
507 | return errInvalidActionType
508 | }
509 |
--------------------------------------------------------------------------------
/flag_generated.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "flag"
5 | "strconv"
6 | "time"
7 | )
8 |
9 | // WARNING: This file is generated!
10 |
11 | // BoolFlag is a flag with type bool
12 | type BoolFlag struct {
13 | Name string
14 | Usage string
15 | EnvVar string
16 | FilePath string
17 | Hidden bool
18 | Destination *bool
19 | }
20 |
21 | // String returns a readable representation of this value
22 | // (for usage defaults)
23 | func (f BoolFlag) String() string {
24 | return FlagStringer(f)
25 | }
26 |
27 | // GetName returns the name of the flag
28 | func (f BoolFlag) GetName() string {
29 | return f.Name
30 | }
31 |
32 | // Bool looks up the value of a local BoolFlag, returns
33 | // false if not found
34 | func (c *Context) Bool(name string) bool {
35 | return lookupBool(name, c.flagSet)
36 | }
37 |
38 | // GlobalBool looks up the value of a global BoolFlag, returns
39 | // false if not found
40 | func (c *Context) GlobalBool(name string) bool {
41 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
42 | return lookupBool(name, fs)
43 | }
44 | return false
45 | }
46 |
47 | func lookupBool(name string, set *flag.FlagSet) bool {
48 | f := set.Lookup(name)
49 | if f != nil {
50 | parsed, err := strconv.ParseBool(f.Value.String())
51 | if err != nil {
52 | return false
53 | }
54 | return parsed
55 | }
56 | return false
57 | }
58 |
59 | // BoolTFlag is a flag with type bool that is true by default
60 | type BoolTFlag struct {
61 | Name string
62 | Usage string
63 | EnvVar string
64 | FilePath string
65 | Hidden bool
66 | Destination *bool
67 | }
68 |
69 | // String returns a readable representation of this value
70 | // (for usage defaults)
71 | func (f BoolTFlag) String() string {
72 | return FlagStringer(f)
73 | }
74 |
75 | // GetName returns the name of the flag
76 | func (f BoolTFlag) GetName() string {
77 | return f.Name
78 | }
79 |
80 | // BoolT looks up the value of a local BoolTFlag, returns
81 | // false if not found
82 | func (c *Context) BoolT(name string) bool {
83 | return lookupBoolT(name, c.flagSet)
84 | }
85 |
86 | // GlobalBoolT looks up the value of a global BoolTFlag, returns
87 | // false if not found
88 | func (c *Context) GlobalBoolT(name string) bool {
89 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
90 | return lookupBoolT(name, fs)
91 | }
92 | return false
93 | }
94 |
95 | func lookupBoolT(name string, set *flag.FlagSet) bool {
96 | f := set.Lookup(name)
97 | if f != nil {
98 | parsed, err := strconv.ParseBool(f.Value.String())
99 | if err != nil {
100 | return false
101 | }
102 | return parsed
103 | }
104 | return false
105 | }
106 |
107 | // DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
108 | type DurationFlag struct {
109 | Name string
110 | Usage string
111 | EnvVar string
112 | FilePath string
113 | Hidden bool
114 | Value time.Duration
115 | Destination *time.Duration
116 | }
117 |
118 | // String returns a readable representation of this value
119 | // (for usage defaults)
120 | func (f DurationFlag) String() string {
121 | return FlagStringer(f)
122 | }
123 |
124 | // GetName returns the name of the flag
125 | func (f DurationFlag) GetName() string {
126 | return f.Name
127 | }
128 |
129 | // Duration looks up the value of a local DurationFlag, returns
130 | // 0 if not found
131 | func (c *Context) Duration(name string) time.Duration {
132 | return lookupDuration(name, c.flagSet)
133 | }
134 |
135 | // GlobalDuration looks up the value of a global DurationFlag, returns
136 | // 0 if not found
137 | func (c *Context) GlobalDuration(name string) time.Duration {
138 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
139 | return lookupDuration(name, fs)
140 | }
141 | return 0
142 | }
143 |
144 | func lookupDuration(name string, set *flag.FlagSet) time.Duration {
145 | f := set.Lookup(name)
146 | if f != nil {
147 | parsed, err := time.ParseDuration(f.Value.String())
148 | if err != nil {
149 | return 0
150 | }
151 | return parsed
152 | }
153 | return 0
154 | }
155 |
156 | // Float64Flag is a flag with type float64
157 | type Float64Flag struct {
158 | Name string
159 | Usage string
160 | EnvVar string
161 | FilePath string
162 | Hidden bool
163 | Value float64
164 | Destination *float64
165 | }
166 |
167 | // String returns a readable representation of this value
168 | // (for usage defaults)
169 | func (f Float64Flag) String() string {
170 | return FlagStringer(f)
171 | }
172 |
173 | // GetName returns the name of the flag
174 | func (f Float64Flag) GetName() string {
175 | return f.Name
176 | }
177 |
178 | // Float64 looks up the value of a local Float64Flag, returns
179 | // 0 if not found
180 | func (c *Context) Float64(name string) float64 {
181 | return lookupFloat64(name, c.flagSet)
182 | }
183 |
184 | // GlobalFloat64 looks up the value of a global Float64Flag, returns
185 | // 0 if not found
186 | func (c *Context) GlobalFloat64(name string) float64 {
187 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
188 | return lookupFloat64(name, fs)
189 | }
190 | return 0
191 | }
192 |
193 | func lookupFloat64(name string, set *flag.FlagSet) float64 {
194 | f := set.Lookup(name)
195 | if f != nil {
196 | parsed, err := strconv.ParseFloat(f.Value.String(), 64)
197 | if err != nil {
198 | return 0
199 | }
200 | return parsed
201 | }
202 | return 0
203 | }
204 |
205 | // GenericFlag is a flag with type Generic
206 | type GenericFlag struct {
207 | Name string
208 | Usage string
209 | EnvVar string
210 | FilePath string
211 | Hidden bool
212 | Value Generic
213 | }
214 |
215 | // String returns a readable representation of this value
216 | // (for usage defaults)
217 | func (f GenericFlag) String() string {
218 | return FlagStringer(f)
219 | }
220 |
221 | // GetName returns the name of the flag
222 | func (f GenericFlag) GetName() string {
223 | return f.Name
224 | }
225 |
226 | // Generic looks up the value of a local GenericFlag, returns
227 | // nil if not found
228 | func (c *Context) Generic(name string) interface{} {
229 | return lookupGeneric(name, c.flagSet)
230 | }
231 |
232 | // GlobalGeneric looks up the value of a global GenericFlag, returns
233 | // nil if not found
234 | func (c *Context) GlobalGeneric(name string) interface{} {
235 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
236 | return lookupGeneric(name, fs)
237 | }
238 | return nil
239 | }
240 |
241 | func lookupGeneric(name string, set *flag.FlagSet) interface{} {
242 | f := set.Lookup(name)
243 | if f != nil {
244 | parsed, err := f.Value, error(nil)
245 | if err != nil {
246 | return nil
247 | }
248 | return parsed
249 | }
250 | return nil
251 | }
252 |
253 | // Int64Flag is a flag with type int64
254 | type Int64Flag struct {
255 | Name string
256 | Usage string
257 | EnvVar string
258 | FilePath string
259 | Hidden bool
260 | Value int64
261 | Destination *int64
262 | }
263 |
264 | // String returns a readable representation of this value
265 | // (for usage defaults)
266 | func (f Int64Flag) String() string {
267 | return FlagStringer(f)
268 | }
269 |
270 | // GetName returns the name of the flag
271 | func (f Int64Flag) GetName() string {
272 | return f.Name
273 | }
274 |
275 | // Int64 looks up the value of a local Int64Flag, returns
276 | // 0 if not found
277 | func (c *Context) Int64(name string) int64 {
278 | return lookupInt64(name, c.flagSet)
279 | }
280 |
281 | // GlobalInt64 looks up the value of a global Int64Flag, returns
282 | // 0 if not found
283 | func (c *Context) GlobalInt64(name string) int64 {
284 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
285 | return lookupInt64(name, fs)
286 | }
287 | return 0
288 | }
289 |
290 | func lookupInt64(name string, set *flag.FlagSet) int64 {
291 | f := set.Lookup(name)
292 | if f != nil {
293 | parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
294 | if err != nil {
295 | return 0
296 | }
297 | return parsed
298 | }
299 | return 0
300 | }
301 |
302 | // IntFlag is a flag with type int
303 | type IntFlag struct {
304 | Name string
305 | Usage string
306 | EnvVar string
307 | FilePath string
308 | Hidden bool
309 | Value int
310 | Destination *int
311 | }
312 |
313 | // String returns a readable representation of this value
314 | // (for usage defaults)
315 | func (f IntFlag) String() string {
316 | return FlagStringer(f)
317 | }
318 |
319 | // GetName returns the name of the flag
320 | func (f IntFlag) GetName() string {
321 | return f.Name
322 | }
323 |
324 | // Int looks up the value of a local IntFlag, returns
325 | // 0 if not found
326 | func (c *Context) Int(name string) int {
327 | return lookupInt(name, c.flagSet)
328 | }
329 |
330 | // GlobalInt looks up the value of a global IntFlag, returns
331 | // 0 if not found
332 | func (c *Context) GlobalInt(name string) int {
333 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
334 | return lookupInt(name, fs)
335 | }
336 | return 0
337 | }
338 |
339 | func lookupInt(name string, set *flag.FlagSet) int {
340 | f := set.Lookup(name)
341 | if f != nil {
342 | parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
343 | if err != nil {
344 | return 0
345 | }
346 | return int(parsed)
347 | }
348 | return 0
349 | }
350 |
351 | // IntSliceFlag is a flag with type *IntSlice
352 | type IntSliceFlag struct {
353 | Name string
354 | Usage string
355 | EnvVar string
356 | FilePath string
357 | Hidden bool
358 | Value *IntSlice
359 | }
360 |
361 | // String returns a readable representation of this value
362 | // (for usage defaults)
363 | func (f IntSliceFlag) String() string {
364 | return FlagStringer(f)
365 | }
366 |
367 | // GetName returns the name of the flag
368 | func (f IntSliceFlag) GetName() string {
369 | return f.Name
370 | }
371 |
372 | // IntSlice looks up the value of a local IntSliceFlag, returns
373 | // nil if not found
374 | func (c *Context) IntSlice(name string) []int {
375 | return lookupIntSlice(name, c.flagSet)
376 | }
377 |
378 | // GlobalIntSlice looks up the value of a global IntSliceFlag, returns
379 | // nil if not found
380 | func (c *Context) GlobalIntSlice(name string) []int {
381 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
382 | return lookupIntSlice(name, fs)
383 | }
384 | return nil
385 | }
386 |
387 | func lookupIntSlice(name string, set *flag.FlagSet) []int {
388 | f := set.Lookup(name)
389 | if f != nil {
390 | parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
391 | if err != nil {
392 | return nil
393 | }
394 | return parsed
395 | }
396 | return nil
397 | }
398 |
399 | // Int64SliceFlag is a flag with type *Int64Slice
400 | type Int64SliceFlag struct {
401 | Name string
402 | Usage string
403 | EnvVar string
404 | FilePath string
405 | Hidden bool
406 | Value *Int64Slice
407 | }
408 |
409 | // String returns a readable representation of this value
410 | // (for usage defaults)
411 | func (f Int64SliceFlag) String() string {
412 | return FlagStringer(f)
413 | }
414 |
415 | // GetName returns the name of the flag
416 | func (f Int64SliceFlag) GetName() string {
417 | return f.Name
418 | }
419 |
420 | // Int64Slice looks up the value of a local Int64SliceFlag, returns
421 | // nil if not found
422 | func (c *Context) Int64Slice(name string) []int64 {
423 | return lookupInt64Slice(name, c.flagSet)
424 | }
425 |
426 | // GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
427 | // nil if not found
428 | func (c *Context) GlobalInt64Slice(name string) []int64 {
429 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
430 | return lookupInt64Slice(name, fs)
431 | }
432 | return nil
433 | }
434 |
435 | func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
436 | f := set.Lookup(name)
437 | if f != nil {
438 | parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
439 | if err != nil {
440 | return nil
441 | }
442 | return parsed
443 | }
444 | return nil
445 | }
446 |
447 | // StringFlag is a flag with type string
448 | type StringFlag struct {
449 | Name string
450 | Usage string
451 | EnvVar string
452 | FilePath string
453 | Hidden bool
454 | Value string
455 | Destination *string
456 | }
457 |
458 | // String returns a readable representation of this value
459 | // (for usage defaults)
460 | func (f StringFlag) String() string {
461 | return FlagStringer(f)
462 | }
463 |
464 | // GetName returns the name of the flag
465 | func (f StringFlag) GetName() string {
466 | return f.Name
467 | }
468 |
469 | // String looks up the value of a local StringFlag, returns
470 | // "" if not found
471 | func (c *Context) String(name string) string {
472 | return lookupString(name, c.flagSet)
473 | }
474 |
475 | // GlobalString looks up the value of a global StringFlag, returns
476 | // "" if not found
477 | func (c *Context) GlobalString(name string) string {
478 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
479 | return lookupString(name, fs)
480 | }
481 | return ""
482 | }
483 |
484 | func lookupString(name string, set *flag.FlagSet) string {
485 | f := set.Lookup(name)
486 | if f != nil {
487 | parsed, err := f.Value.String(), error(nil)
488 | if err != nil {
489 | return ""
490 | }
491 | return parsed
492 | }
493 | return ""
494 | }
495 |
496 | // StringSliceFlag is a flag with type *StringSlice
497 | type StringSliceFlag struct {
498 | Name string
499 | Usage string
500 | EnvVar string
501 | FilePath string
502 | Hidden bool
503 | Value *StringSlice
504 | }
505 |
506 | // String returns a readable representation of this value
507 | // (for usage defaults)
508 | func (f StringSliceFlag) String() string {
509 | return FlagStringer(f)
510 | }
511 |
512 | // GetName returns the name of the flag
513 | func (f StringSliceFlag) GetName() string {
514 | return f.Name
515 | }
516 |
517 | // StringSlice looks up the value of a local StringSliceFlag, returns
518 | // nil if not found
519 | func (c *Context) StringSlice(name string) []string {
520 | return lookupStringSlice(name, c.flagSet)
521 | }
522 |
523 | // GlobalStringSlice looks up the value of a global StringSliceFlag, returns
524 | // nil if not found
525 | func (c *Context) GlobalStringSlice(name string) []string {
526 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
527 | return lookupStringSlice(name, fs)
528 | }
529 | return nil
530 | }
531 |
532 | func lookupStringSlice(name string, set *flag.FlagSet) []string {
533 | f := set.Lookup(name)
534 | if f != nil {
535 | parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
536 | if err != nil {
537 | return nil
538 | }
539 | return parsed
540 | }
541 | return nil
542 | }
543 |
544 | // Uint64Flag is a flag with type uint64
545 | type Uint64Flag struct {
546 | Name string
547 | Usage string
548 | EnvVar string
549 | FilePath string
550 | Hidden bool
551 | Value uint64
552 | Destination *uint64
553 | }
554 |
555 | // String returns a readable representation of this value
556 | // (for usage defaults)
557 | func (f Uint64Flag) String() string {
558 | return FlagStringer(f)
559 | }
560 |
561 | // GetName returns the name of the flag
562 | func (f Uint64Flag) GetName() string {
563 | return f.Name
564 | }
565 |
566 | // Uint64 looks up the value of a local Uint64Flag, returns
567 | // 0 if not found
568 | func (c *Context) Uint64(name string) uint64 {
569 | return lookupUint64(name, c.flagSet)
570 | }
571 |
572 | // GlobalUint64 looks up the value of a global Uint64Flag, returns
573 | // 0 if not found
574 | func (c *Context) GlobalUint64(name string) uint64 {
575 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
576 | return lookupUint64(name, fs)
577 | }
578 | return 0
579 | }
580 |
581 | func lookupUint64(name string, set *flag.FlagSet) uint64 {
582 | f := set.Lookup(name)
583 | if f != nil {
584 | parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
585 | if err != nil {
586 | return 0
587 | }
588 | return parsed
589 | }
590 | return 0
591 | }
592 |
593 | // UintFlag is a flag with type uint
594 | type UintFlag struct {
595 | Name string
596 | Usage string
597 | EnvVar string
598 | FilePath string
599 | Hidden bool
600 | Value uint
601 | Destination *uint
602 | }
603 |
604 | // String returns a readable representation of this value
605 | // (for usage defaults)
606 | func (f UintFlag) String() string {
607 | return FlagStringer(f)
608 | }
609 |
610 | // GetName returns the name of the flag
611 | func (f UintFlag) GetName() string {
612 | return f.Name
613 | }
614 |
615 | // Uint looks up the value of a local UintFlag, returns
616 | // 0 if not found
617 | func (c *Context) Uint(name string) uint {
618 | return lookupUint(name, c.flagSet)
619 | }
620 |
621 | // GlobalUint looks up the value of a global UintFlag, returns
622 | // 0 if not found
623 | func (c *Context) GlobalUint(name string) uint {
624 | if fs := lookupGlobalFlagSet(name, c); fs != nil {
625 | return lookupUint(name, fs)
626 | }
627 | return 0
628 | }
629 |
630 | func lookupUint(name string, set *flag.FlagSet) uint {
631 | f := set.Lookup(name)
632 | if f != nil {
633 | parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
634 | if err != nil {
635 | return 0
636 | }
637 | return uint(parsed)
638 | }
639 | return 0
640 | }
641 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | **ATTN**: This project uses [semantic versioning](http://semver.org/).
4 |
5 | ## [Unreleased]
6 |
7 | ## 1.20.0 - 2017-08-10
8 |
9 | ### Fixed
10 |
11 | * `HandleExitCoder` is now correctly iterates over all errors in
12 | a `MultiError`. The exit code is the exit code of the last error or `1` if
13 | there are no `ExitCoder`s in the `MultiError`.
14 | * Fixed YAML file loading on Windows (previously would fail validate the file path)
15 | * Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly
16 | propogated
17 | * `ErrWriter` is now passed downwards through command structure to avoid the
18 | need to redefine it
19 | * Pass `Command` context into `OnUsageError` rather than parent context so that
20 | all fields are avaiable
21 | * Errors occuring in `Before` funcs are no longer double printed
22 | * Use `UsageText` in the help templates for commands and subcommands if
23 | defined; otherwise build the usage as before (was previously ignoring this
24 | field)
25 | * `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if
26 | a program calls `Set` or `GlobalSet` directly after flag parsing (would
27 | previously only return `true` if the flag was set during parsing)
28 |
29 | ### Changed
30 |
31 | * No longer exit the program on command/subcommand error if the error raised is
32 | not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was
33 | determined to be a regression in functionality. See [the
34 | PR](https://github.com/urfave/cli/pull/595) for discussion.
35 |
36 | ### Added
37 |
38 | * `CommandsByName` type was added to make it easy to sort `Command`s by name,
39 | alphabetically
40 | * `altsrc` now handles loading of string and int arrays from TOML
41 | * Support for definition of custom help templates for `App` via
42 | `CustomAppHelpTemplate`
43 | * Support for arbitrary key/value fields on `App` to be used with
44 | `CustomAppHelpTemplate` via `ExtraInfo`
45 | * `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be
46 | `cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag`
47 | interface to be used.
48 |
49 |
50 | ## [1.19.1] - 2016-11-21
51 |
52 | ### Fixed
53 |
54 | - Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
55 | the `Action` for a command would cause it to error rather than calling the
56 | function. Should not have a affected declarative cases using `func(c
57 | *cli.Context) err)`.
58 | - Shell completion now handles the case where the user specifies
59 | `--generate-bash-completion` immediately after a flag that takes an argument.
60 | Previously it call the application with `--generate-bash-completion` as the
61 | flag value.
62 |
63 | ## [1.19.0] - 2016-11-19
64 | ### Added
65 | - `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
66 | - A `Description` field was added to `App` for a more detailed description of
67 | the application (similar to the existing `Description` field on `Command`)
68 | - Flag type code generation via `go generate`
69 | - Write to stderr and exit 1 if action returns non-nil error
70 | - Added support for TOML to the `altsrc` loader
71 | - `SkipArgReorder` was added to allow users to skip the argument reordering.
72 | This is useful if you want to consider all "flags" after an argument as
73 | arguments rather than flags (the default behavior of the stdlib `flag`
74 | library). This is backported functionality from the [removal of the flag
75 | reordering](https://github.com/urfave/cli/pull/398) in the unreleased version
76 | 2
77 | - For formatted errors (those implementing `ErrorFormatter`), the errors will
78 | be formatted during output. Compatible with `pkg/errors`.
79 |
80 | ### Changed
81 | - Raise minimum tested/supported Go version to 1.2+
82 |
83 | ### Fixed
84 | - Consider empty environment variables as set (previously environment variables
85 | with the equivalent of `""` would be skipped rather than their value used).
86 | - Return an error if the value in a given environment variable cannot be parsed
87 | as the flag type. Previously these errors were silently swallowed.
88 | - Print full error when an invalid flag is specified (which includes the invalid flag)
89 | - `App.Writer` defaults to `stdout` when `nil`
90 | - If no action is specified on a command or app, the help is now printed instead of `panic`ing
91 | - `App.Metadata` is initialized automatically now (previously was `nil` unless initialized)
92 | - Correctly show help message if `-h` is provided to a subcommand
93 | - `context.(Global)IsSet` now respects environment variables. Previously it
94 | would return `false` if a flag was specified in the environment rather than
95 | as an argument
96 | - Removed deprecation warnings to STDERR to avoid them leaking to the end-user
97 | - `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This
98 | fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well
99 | as `altsrc` where Go would complain that the types didn't match
100 |
101 | ## [1.18.1] - 2016-08-28
102 | ### Fixed
103 | - Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported)
104 |
105 | ## [1.18.0] - 2016-06-27
106 | ### Added
107 | - `./runtests` test runner with coverage tracking by default
108 | - testing on OS X
109 | - testing on Windows
110 | - `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code
111 |
112 | ### Changed
113 | - Use spaces for alignment in help/usage output instead of tabs, making the
114 | output alignment consistent regardless of tab width
115 |
116 | ### Fixed
117 | - Printing of command aliases in help text
118 | - Printing of visible flags for both struct and struct pointer flags
119 | - Display the `help` subcommand when using `CommandCategories`
120 | - No longer swallows `panic`s that occur within the `Action`s themselves when
121 | detecting the signature of the `Action` field
122 |
123 | ## [1.17.1] - 2016-08-28
124 | ### Fixed
125 | - Removed deprecation warnings to STDERR to avoid them leaking to the end-user
126 |
127 | ## [1.17.0] - 2016-05-09
128 | ### Added
129 | - Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
130 | - `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
131 | - Support for hiding commands by setting `Hidden: true` -- this will hide the
132 | commands in help output
133 |
134 | ### Changed
135 | - `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
136 | quoted in help text output.
137 | - All flag types now include `(default: {value})` strings following usage when a
138 | default value can be (reasonably) detected.
139 | - `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
140 | with non-slice flag types
141 | - Apps now exit with a code of 3 if an unknown subcommand is specified
142 | (previously they printed "No help topic for...", but still exited 0. This
143 | makes it easier to script around apps built using `cli` since they can trust
144 | that a 0 exit code indicated a successful execution.
145 | - cleanups based on [Go Report Card
146 | feedback](https://goreportcard.com/report/github.com/urfave/cli)
147 |
148 | ## [1.16.1] - 2016-08-28
149 | ### Fixed
150 | - Removed deprecation warnings to STDERR to avoid them leaking to the end-user
151 |
152 | ## [1.16.0] - 2016-05-02
153 | ### Added
154 | - `Hidden` field on all flag struct types to omit from generated help text
155 |
156 | ### Changed
157 | - `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
158 | generated help text via the `Hidden` field
159 |
160 | ### Fixed
161 | - handling of error values in `HandleAction` and `HandleExitCoder`
162 |
163 | ## [1.15.0] - 2016-04-30
164 | ### Added
165 | - This file!
166 | - Support for placeholders in flag usage strings
167 | - `App.Metadata` map for arbitrary data/state management
168 | - `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
169 | parsing.
170 | - Support for nested lookup of dot-delimited keys in structures loaded from
171 | YAML.
172 |
173 | ### Changed
174 | - The `App.Action` and `Command.Action` now prefer a return signature of
175 | `func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
176 | `error` is returned, there may be two outcomes:
177 | - If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
178 | automatically
179 | - Else the error is bubbled up and returned from `App.Run`
180 | - Specifying an `Action` with the legacy return signature of
181 | `func(*cli.Context)` will produce a deprecation message to stderr
182 | - Specifying an `Action` that is not a `func` type will produce a non-zero exit
183 | from `App.Run`
184 | - Specifying an `Action` func that has an invalid (input) signature will
185 | produce a non-zero exit from `App.Run`
186 |
187 | ### Deprecated
188 | -
189 | `cli.App.RunAndExitOnError`, which should now be done by returning an error
190 | that fulfills `cli.ExitCoder` to `cli.App.Run`.
191 | - the legacy signature for
192 | `cli.App.Action` of `func(*cli.Context)`, which should now have a return
193 | signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
194 |
195 | ### Fixed
196 | - Added missing `*cli.Context.GlobalFloat64` method
197 |
198 | ## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
199 | ### Added
200 | - Codebeat badge
201 | - Support for categorization via `CategorizedHelp` and `Categories` on app.
202 |
203 | ### Changed
204 | - Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
205 |
206 | ### Fixed
207 | - Ensure version is not shown in help text when `HideVersion` set.
208 |
209 | ## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
210 | ### Added
211 | - YAML file input support.
212 | - `NArg` method on context.
213 |
214 | ## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
215 | ### Added
216 | - Custom usage error handling.
217 | - Custom text support in `USAGE` section of help output.
218 | - Improved help messages for empty strings.
219 | - AppVeyor CI configuration.
220 |
221 | ### Changed
222 | - Removed `panic` from default help printer func.
223 | - De-duping and optimizations.
224 |
225 | ### Fixed
226 | - Correctly handle `Before`/`After` at command level when no subcommands.
227 | - Case of literal `-` argument causing flag reordering.
228 | - Environment variable hints on Windows.
229 | - Docs updates.
230 |
231 | ## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
232 | ### Changed
233 | - Use `path.Base` in `Name` and `HelpName`
234 | - Export `GetName` on flag types.
235 |
236 | ### Fixed
237 | - Flag parsing when skipping is enabled.
238 | - Test output cleanup.
239 | - Move completion check to account for empty input case.
240 |
241 | ## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
242 | ### Added
243 | - Destination scan support for flags.
244 | - Testing against `tip` in Travis CI config.
245 |
246 | ### Changed
247 | - Go version in Travis CI config.
248 |
249 | ### Fixed
250 | - Removed redundant tests.
251 | - Use correct example naming in tests.
252 |
253 | ## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
254 | ### Fixed
255 | - Remove unused var in bash completion.
256 |
257 | ## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
258 | ### Added
259 | - Coverage and reference logos in README.
260 |
261 | ### Fixed
262 | - Use specified values in help and version parsing.
263 | - Only display app version and help message once.
264 |
265 | ## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
266 | ### Added
267 | - More tests for existing functionality.
268 | - `ArgsUsage` at app and command level for help text flexibility.
269 |
270 | ### Fixed
271 | - Honor `HideHelp` and `HideVersion` in `App.Run`.
272 | - Remove juvenile word from README.
273 |
274 | ## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
275 | ### Added
276 | - `FullName` on command with accompanying help output update.
277 | - Set default `$PROG` in bash completion.
278 |
279 | ### Changed
280 | - Docs formatting.
281 |
282 | ### Fixed
283 | - Removed self-referential imports in tests.
284 |
285 | ## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
286 | ### Added
287 | - Support for `Copyright` at app level.
288 | - `Parent` func at context level to walk up context lineage.
289 |
290 | ### Fixed
291 | - Global flag processing at top level.
292 |
293 | ## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
294 | ### Added
295 | - Aggregate errors from `Before`/`After` funcs.
296 | - Doc comments on flag structs.
297 | - Include non-global flags when checking version and help.
298 | - Travis CI config updates.
299 |
300 | ### Fixed
301 | - Ensure slice type flags have non-nil values.
302 | - Collect global flags from the full command hierarchy.
303 | - Docs prose.
304 |
305 | ## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
306 | ### Changed
307 | - `HelpPrinter` signature includes output writer.
308 |
309 | ### Fixed
310 | - Specify go 1.1+ in docs.
311 | - Set `Writer` when running command as app.
312 |
313 | ## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
314 | ### Added
315 | - Multiple author support.
316 | - `NumFlags` at context level.
317 | - `Aliases` at command level.
318 |
319 | ### Deprecated
320 | - `ShortName` at command level.
321 |
322 | ### Fixed
323 | - Subcommand help output.
324 | - Backward compatible support for deprecated `Author` and `Email` fields.
325 | - Docs regarding `Names`/`Aliases`.
326 |
327 | ## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
328 | ### Added
329 | - `After` hook func support at app and command level.
330 |
331 | ### Fixed
332 | - Use parsed context when running command as subcommand.
333 | - Docs prose.
334 |
335 | ## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
336 | ### Added
337 | - Support for hiding `-h / --help` flags, but not `help` subcommand.
338 | - Stop flag parsing after `--`.
339 |
340 | ### Fixed
341 | - Help text for generic flags to specify single value.
342 | - Use double quotes in output for defaults.
343 | - Use `ParseInt` instead of `ParseUint` for int environment var values.
344 | - Use `0` as base when parsing int environment var values.
345 |
346 | ## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
347 | ### Added
348 | - Support for environment variable lookup "cascade".
349 | - Support for `Stdout` on app for output redirection.
350 |
351 | ### Fixed
352 | - Print command help instead of app help in `ShowCommandHelp`.
353 |
354 | ## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
355 | ### Added
356 | - Docs and example code updates.
357 |
358 | ### Changed
359 | - Default `-v / --version` flag made optional.
360 |
361 | ## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
362 | ### Added
363 | - `FlagNames` at context level.
364 | - Exposed `VersionPrinter` var for more control over version output.
365 | - Zsh completion hook.
366 | - `AUTHOR` section in default app help template.
367 | - Contribution guidelines.
368 | - `DurationFlag` type.
369 |
370 | ## [1.2.0] - 2014-08-02
371 | ### Added
372 | - Support for environment variable defaults on flags plus tests.
373 |
374 | ## [1.1.0] - 2014-07-15
375 | ### Added
376 | - Bash completion.
377 | - Optional hiding of built-in help command.
378 | - Optional skipping of flag parsing at command level.
379 | - `Author`, `Email`, and `Compiled` metadata on app.
380 | - `Before` hook func support at app and command level.
381 | - `CommandNotFound` func support at app level.
382 | - Command reference available on context.
383 | - `GenericFlag` type.
384 | - `Float64Flag` type.
385 | - `BoolTFlag` type.
386 | - `IsSet` flag helper on context.
387 | - More flag lookup funcs at context level.
388 | - More tests & docs.
389 |
390 | ### Changed
391 | - Help template updates to account for presence/absence of flags.
392 | - Separated subcommand help template.
393 | - Exposed `HelpPrinter` var for more control over help output.
394 |
395 | ## [1.0.0] - 2013-11-01
396 | ### Added
397 | - `help` flag in default app flag set and each command flag set.
398 | - Custom handling of argument parsing errors.
399 | - Command lookup by name at app level.
400 | - `StringSliceFlag` type and supporting `StringSlice` type.
401 | - `IntSliceFlag` type and supporting `IntSlice` type.
402 | - Slice type flag lookups by name at context level.
403 | - Export of app and command help functions.
404 | - More tests & docs.
405 |
406 | ## 0.1.0 - 2013-07-22
407 | ### Added
408 | - Initial implementation.
409 |
410 | [Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD
411 | [1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
412 | [1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
413 | [1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
414 | [1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
415 | [1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
416 | [1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
417 | [1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
418 | [1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
419 | [1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
420 | [1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
421 | [1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
422 | [1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
423 | [1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
424 | [1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
425 | [1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
426 | [1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
427 | [1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
428 | [1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
429 | [1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
430 | [1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
431 | [1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
432 | [1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
433 | [1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
434 | [1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
435 | [1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0
436 |
--------------------------------------------------------------------------------