├── 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 | --------------------------------------------------------------------------------