diff --git a/CHANGELOG.md b/CHANGELOG.md index d1b5c61..b1924f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Changed - `Context.FlagNames` now returns all flags in the context lineage - `Context.IsSet` now considers the full context lineage +- Added `IsSet` method to the `Flag` interface which allows us to detect whether or not a flag has been set ### Removed - the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`. diff --git a/app.go b/app.go index 90b1254..f0c6ac9 100644 --- a/app.go +++ b/app.go @@ -14,8 +14,6 @@ import ( var ( changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) - // unused variable. commented for now. will remove in future if agreed upon by everyone - //runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." @@ -89,7 +87,7 @@ type App struct { // render custom help text by setting this variable. CustomAppHelpTemplate string // Boolean to enable short-option handling so user can combine several - // single-character bool arguements into one + // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool @@ -182,11 +180,6 @@ func (a *App) Setup() { } } - //if a.EnableShellCompletion { - // a.appendFlag(GenerateCompletionFlag) - // a.appendFlag(InitCompletionFlag) - //} - if !a.HideVersion { a.appendFlag(VersionFlag) } @@ -246,14 +239,6 @@ func (a *App) Run(arguments []string) (err error) { return nil } - //if done, cerr := checkInitCompletion(context); done { - // if cerr != nil { - // err = cerr - // } else { - // return nil - // } - //} - if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) @@ -361,17 +346,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } a.Commands = newCmds - //<<<<<<< HEAD - // // append flags - // if a.EnableShellCompletion { - // a.appendFlag(GenerateCompletionFlag) - // } - // - // // parse flags - // set, err := flagSet(a.Name, a.Flags) - //======= _, err = a.newFlagSet() - //>>>>>>> master if err != nil { return err } diff --git a/app_test.go b/app_test.go index 07d2bd2..2d1d656 100644 --- a/app_test.go +++ b/app_test.go @@ -1993,6 +1993,10 @@ func (c *customBoolFlag) Apply(set *flag.FlagSet) error { return nil } +func (c *customBoolFlag) IsSet() bool { + return false +} + func TestCustomFlagsUnused(t *testing.T) { app := &App{ Flags: []Flag{&customBoolFlag{"custom"}}, diff --git a/args.go b/args.go index 5618a47..bd65c17 100644 --- a/args.go +++ b/args.go @@ -1,11 +1,5 @@ package cli -import "errors" - -var ( - argsRangeErr = errors.New("index out of range") -) - type Args interface { // Get returns the nth argument, or else a blank string Get(n int) string @@ -55,6 +49,6 @@ func (a *args) Present() bool { func (a *args) Slice() []string { ret := make([]string, len(*a)) - copy(ret, []string(*a)) + copy(ret, *a) return ret } diff --git a/category.go b/category.go index 64d84ec..fa56845 100644 --- a/category.go +++ b/category.go @@ -59,13 +59,6 @@ type commandCategory struct { commands []*Command } -func newCommandCategory(name string) *commandCategory { - return &commandCategory{ - name: name, - commands: []*Command{}, - } -} - func (c *commandCategory) Name() string { return c.name } diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 deleted file mode 100755 index b815295..0000000 --- a/cli-v1-to-v2 +++ /dev/null @@ -1,479 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function, unicode_literals - -import argparse -import io -import logging -import os -import re -import sys - - -_DESCRIPTION = """\ -Migrate arbitrary `.go` sources (mostly) from the v1 to v2 API. -""" -_MIGRATORS = [] - - -def main(sysargs=sys.argv[:]): - parser = argparse.ArgumentParser( - description=_DESCRIPTION, - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('path', nargs='*', - type=os.path.abspath, default=os.getcwd()) - parser.add_argument('-w', '--write', help='write changes back to file', - action='store_true', default=False) - parser.add_argument('-q', '--quiet', help='quiet down the logging', - action='store_true', default=False) - parser.add_argument('-D', '--debug', help='debug up the logging', - action='store_true', - default=(os.environ.get('DEBUG') != '')) - parser.add_argument('--selftest', help='run internal tests', - action='store_true', default=False) - - args = parser.parse_args(sysargs[1:]) - - if args.selftest: - logging.basicConfig( - level=logging.WARN, - format='selftest: %(message)s' - ) - test_migrators() - return 0 - - level = logging.FATAL if args.quiet else logging.INFO - level = logging.DEBUG if args.debug else level - - logging.basicConfig(level=level, format='%(message)s') - - paths = args.path - if len(paths) == 0: - paths = ['.'] - - for filepath in _find_candidate_files(paths): - updated_source = _update_filepath(filepath) - if args.write: - logging.info('Updating %s', filepath) - - with io.open(filepath, 'w', encoding='utf-8') as outfile: - outfile.write(updated_source) - else: - logging.info('// Updated %s:', filepath) - print(updated_source) - - return 0 - - -def _find_candidate_files(paths): - for path in paths: - if not os.path.isdir(path): - yield path - continue - - for curdir, dirs, files in os.walk(path): - for i, dirname in enumerate(dirs[:]): - if dirname.startswith('.'): - dirs.pop(i) - - for filename in files: - if not filename.decode('utf-8').endswith('.go'): - continue - - filepath = os.path.join(curdir, filename) - if not os.access(filepath, os.R_OK | os.W_OK): - continue - - yield filepath - - -def _update_filepath(filepath): - with io.open(filepath, encoding='utf-8') as infile: - return _update_source(infile.read()) - - -def _update_source(source): - for migrator, func in _MIGRATORS: - logging.debug('Running %s migrator', migrator) - source = func(source) - return source - - -def _subfmt(pattern, replfmt, source, flags=re.UNICODE): - def repl(match): - return replfmt.format(**match.groupdict()) - return re.sub(pattern, repl, source, flags=flags) - - -def _migrator(func): - _MIGRATORS.append((func.__name__.strip('_'), func)) - return func - - -@_migrator -def _slice_pointer_types(source): - return _subfmt( - '(?P\\[\\])cli\\.(?PCommand|Author){', - '{prefix}*cli.{type}{{', source - ) - - -@_migrator -def _pointer_type_literal(source): - return _subfmt( - '(?P\\s+)cli\\.(?PCommand|Author){', - '{prefix}&cli.{type}{{', source - ) - - -@_migrator -def _slice_types(source): - return _subfmt( - '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', - 'cli.New{type}({args})', source, flags=re.DOTALL | re.UNICODE - ) - - -@_migrator -def _flag_literals(source): - return _subfmt( - '(?P\\s+)cli\\.(?P\\w+)Flag{', - '{prefix}&cli.{type}Flag{{', source - ) - - -@_migrator -def _v1_imports(source): - return re.sub( - '"(?:github\\.com|gopkg\\.in)/(?:codegangsta|urfave)/cli(?:\\.v1|)"', - '"gopkg.in/urfave/cli.v2"', source, flags=re.UNICODE - ) - - -@_migrator -def _new_exit_error(source): - return re.sub('cli\\.NewExitError', 'cli.Exit', source, flags=re.UNICODE) - - -@_migrator -def _bool_t_flag(source): - return _subfmt( - 'cli\\.BoolTFlag{(?P[^}]*)}', - 'cli.BoolFlag{{Value: true,{args}}}', - source, flags=re.DOTALL | re.UNICODE - ) - - -@_migrator -def _context_args_len(source): - return _subfmt( - 'len\\((?P\\S+)\\.Args\\(\\)\\)', - '{prefix}.Args().Len()', source - ) - - -@_migrator -def _context_args_index(source): - return _subfmt( - '\\.Args\\(\\)\\[(?P\\d+)\\]', - '.Args().Get({index})', source - ) - - -@_migrator -def _envvar_string(source): - return re.sub( - 'EnvVar:(?P\\s+)"(?P[^"]+)"', - _envvar_string_repl, source, flags=re.UNICODE - ) - - -def _envvar_string_repl(match): - return 'EnvVars:{ws}[]string{{{value}}}'.format( - value=', '.join([ - '"{}"'.format(s) for s in - re.split( - '\\s*,\\s*', match.groupdict()['string'], - flags=re.UNICODE - ) - ]), - **match.groupdict() - ) - - -@_migrator -def _flag_name_stringly(source): - return re.sub( - '(?P\\s+)Name:(?P\\s+)"(?P[^"]+)"', - _flag_name_stringly_repl, source, flags=re.UNICODE - ) - - -def _flag_name_stringly_repl(match): - revars = dict(match.groupdict()) - - string = revars['string'] - parts = list( - reversed( - sorted( - filter(lambda s: len(s.strip()) > 0, [ - part.strip() for part in string.split(',') - ]), key=len - ) - ) - ) - - if len(parts) == 1: - return '{prefix}Name:{ws}"{string}"'.format(**revars) - - return ( - '{prefix}Name:{ws}"{name}", Aliases: []string{{{aliases}}}' - ).format( - name=parts[0], - aliases=', '.join(['"{}"'.format(s) for s in parts[1:]]), - **revars - ) - - -@_migrator -def _commands_opaque_type(source): - return _subfmt( - 'cli\\.Commands(?P[^B])', - '[]*cli.Command{suffix}', - source - ) - - -@_migrator -def _flag_names(source): - return re.sub('\\.GetName\\(\\)', '.Names()[0]', source, flags=re.UNICODE) - - -@_migrator -def _app_categories(source): - source = _subfmt( - '(?Prange\\s+\\S+)\\.App\\.Categories\\(\\)', - '{prefix}.App.Categories.Categories()', source - ) - - return re.sub( - '\\.App\\.Categories\\(\\)', '.App.Categories', - source, flags=re.UNICODE - ) - - -@_migrator -def _command_category_commands(source): - # XXX: brittle - return _subfmt( - '(?P\\s+category\\.)Commands(?P[^(])', - '{prefix}VisibleCommands(){suffix}', source - ) - - -@_migrator -def _context_bool_t(source): - # XXX: probably brittle - return _subfmt( - '(?P\\S+)(?:Global|)BoolT\\(', - '!{prefix}Bool(', source - ) - - -@_migrator -def _context_global_methods(source): - return _subfmt( - '\\.Global(?P' - 'Bool|Duration|Float64|Generic|Int|IntSlice|String|StringSlice|' - 'FlagNames|IsSet|Set' - ')\\(', - '.{method}(', source - ) - - -@_migrator -def _context_parent(source): - # XXX: brittle - return re.sub('\\.Parent\\(\\)', '.Lineage()[1]', source, flags=re.UNICODE) - - -@_migrator -def _app_init(source): - return re.sub( - 'cli\\.NewApp\\(\\)', '(&cli.App{})', source, flags=re.UNICODE - ) - - -@_migrator -def _bash_complete(source): - return re.sub( - 'BashComplete:', 'ShellComplete:', - re.sub('\\.BashComplete', '.ShellComplete', source, flags=re.UNICODE)) - - -@_migrator -def _enable_bash_completion(source): - return re.sub( - '\\.EnableBashCompletion', '.EnableShellCompletion', source, flags=re.UNICODE - ) - - -@_migrator -def _bash_completion_flag(source): - return re.sub( - 'cli\\.BashCompletionFlag', 'cli.GenerateCompletionFlag', source, flags=re.UNICODE - ) - - -def test_migrators(): - import difflib - - for i, (source, expected) in enumerate(_MIGRATOR_TESTS): - actual = _update_source(source) - if expected != actual: - udiff = difflib.unified_diff( - expected.splitlines(), actual.splitlines(), - fromfile='a/source.go', tofile='b/source.go', lineterm='' - ) - for line in udiff: - print(line) - raise AssertionError('migrated source does not match expected') - logging.warn('Test case %d/%d OK', i+1, len(_MIGRATOR_TESTS)) - - -_MIGRATOR_TESTS = ( - (""" -\t\t\t&cli.StringSlice{"a", "b", "c"}, -""", """ -\t\t\tcli.NewStringSlice("a", "b", "c"), -"""), - (""" -\t\tcli.IntFlag{ -\t\t\tName: "yep", -\t\t\tValue: 3, -\t\t} -""", """ -\t\t&cli.IntFlag{ -\t\t\tName: "yep", -\t\t\tValue: 3, -\t\t} -"""), - (""" -\t\tapp.Commands = []cli.Command{ -\t\t\t{ -\t\t\t\tName: "whatebbs", -\t\t\t}, -\t\t} -""", """ -\t\tapp.Commands = []*cli.Command{ -\t\t\t{ -\t\t\t\tName: "whatebbs", -\t\t\t}, -\t\t} -"""), - (""" -\t\tapp.Commands = []cli.Command{ -\t\t\tcli.Command{ -\t\t\t\tName: "whatebbs", -\t\t\t}, -\t\t} -""", """ -\t\tapp.Commands = []*cli.Command{ -\t\t\t&cli.Command{ -\t\t\t\tName: "whatebbs", -\t\t\t}, -\t\t} -"""), - (""" -\t"github.com/codegangsta/cli" -\t"github.com/urfave/cli" -\t"gopkg.in/codegangsta/cli" -\t"gopkg.in/codegangsta/cli.v1" -\t"gopkg.in/urfave/cli" -\t"gopkg.in/urfave/cli.v1" -""", """ -\t"gopkg.in/urfave/cli.v2" -\t"gopkg.in/urfave/cli.v2" -\t"gopkg.in/urfave/cli.v2" -\t"gopkg.in/urfave/cli.v2" -\t"gopkg.in/urfave/cli.v2" -\t"gopkg.in/urfave/cli.v2" -"""), - (""" -\t\t\t\treturn cli.NewExitError("foo whatebber", 9) -""", """ -\t\t\t\treturn cli.Exit("foo whatebber", 9) -"""), - (""" -\t\t\tapp.Flags = []cli.Flag{ -\t\t\t\tcli.StringFlag{ -\t\t\t\t\tName: "aha", -\t\t\t\t}, -\t\t\t\tcli.BoolTFlag{ -\t\t\t\t\tName: "blurp", -\t\t\t\t}, -\t\t\t} -""", """ -\t\t\tapp.Flags = []cli.Flag{ -\t\t\t\t&cli.StringFlag{ -\t\t\t\t\tName: "aha", -\t\t\t\t}, -\t\t\t\t&cli.BoolFlag{Value: true, -\t\t\t\t\tName: "blurp", -\t\t\t\t}, -\t\t\t} -"""), - (""" -\t\t\tAction = func(c *cli.Context) error { -\t\t\t\tif c.Args()[4] == "meep" { -\t\t\t\t\treturn nil -\t\t\t\t} -\t\t\t\treturn errors.New("mope") -\t\t\t} -""", """ -\t\t\tAction = func(c *cli.Context) error { -\t\t\t\tif c.Args().Get(4) == "meep" { -\t\t\t\t\treturn nil -\t\t\t\t} -\t\t\t\treturn errors.New("mope") -\t\t\t} -"""), - (""" -\t\tapp.Flags = []cli.Flag{ -\t\t\tcli.StringFlag{ -\t\t\t\tName: "toots", -\t\t\t\tEnvVar: "TOOTS,TOOTERS", -\t\t\t}, -\t\t} -""", """ -\t\tapp.Flags = []cli.Flag{ -\t\t\t&cli.StringFlag{ -\t\t\t\tName: "toots", -\t\t\t\tEnvVars: []string{"TOOTS", "TOOTERS"}, -\t\t\t}, -\t\t} -"""), - (""" -\t\tapp.Flags = []cli.Flag{ -\t\t\tcli.StringFlag{ -\t\t\t\tName: "t, tootles, toots", -\t\t\t}, -\t\t} -""", """ -\t\tapp.Flags = []cli.Flag{ -\t\t\t&cli.StringFlag{ -\t\t\t\tName: "tootles", Aliases: []string{"toots", "t"}, -\t\t\t}, -\t\t} -"""), - (""" -\t\tapp := cli.NewApp() -\t\tapp.HideHelp = true -""", """ -\t\tapp := (&cli.App{}) -\t\tapp.HideHelp = true -""") -) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/context.go b/context.go index 897ca19..66dcdd6 100644 --- a/context.go +++ b/context.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "os/signal" - "reflect" "strings" "syscall" ) @@ -91,23 +90,25 @@ func (c *Context) IsSet(name string) bool { return false } - val := reflect.ValueOf(f) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - - filePathValue := val.FieldByName("FilePath") - if !filePathValue.IsValid() { - return false - } - - envVarValues := val.FieldByName("EnvVars") - if !envVarValues.IsValid() { - return false - } + //val := reflect.ValueOf(f) + //if val.Kind() == reflect.Ptr { + // val = val.Elem() + //} + // + //filePathValue := val.FieldByName("FilePath") + //if !filePathValue.IsValid() { + // return false + //} + // + //envVarValues := val.FieldByName("EnvVars") + //if !envVarValues.IsValid() { + // return false + //} + // + //_, ok := flagFromEnvOrFile(envVarValues.Interface().([]string), filePathValue.Interface().(string)) + //return ok - _, ok := flagFromEnvOrFile(envVarValues.Interface().([]string), filePathValue.Interface().(string)) - return ok + return f.IsSet() } return false diff --git a/context_test.go b/context_test.go index 1dc48e1..fef4945 100644 --- a/context_test.go +++ b/context_test.go @@ -222,75 +222,6 @@ func TestContext_IsSet_fromEnv(t *testing.T) { expect(t, uIsSet, false) } -// XXX Corresponds to hack in context.IsSet for flags with EnvVar field -// TODO: Should be moved to `flag_test` in v2 -//func TestContext_GlobalIsSet_fromEnv(t *testing.T) { -// var ( -// timeoutIsSet, tIsSet bool -// noEnvVarIsSet, nIsSet bool -// passwordIsSet, pIsSet bool -// passwordValue string -// unparsableIsSet, uIsSet bool -// overrideIsSet, oIsSet bool -// overrideValue string -// ) -// -// os.Clearenv() -// _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") -// _ = os.Setenv("APP_PASSWORD", "badpass") -// _ = os.Setenv("APP_OVERRIDE", "overridden") -// a := App{ -// Flags: []Flag{ -// Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, -// StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"}, -// Float64Flag{Name: "no-env-var, n"}, -// Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"}, -// StringFlag{Name: "overrides-default, o", Value: "default", EnvVar: "APP_OVERRIDE"}, -// }, -// Commands: []Command{ -// { -// Name: "hello", -// Action: func(ctx *Context) error { -// timeoutIsSet = ctx.GlobalIsSet("timeout") -// tIsSet = ctx.GlobalIsSet("t") -// passwordIsSet = ctx.GlobalIsSet("password") -// pIsSet = ctx.GlobalIsSet("p") -// passwordValue = ctx.GlobalString("password") -// unparsableIsSet = ctx.GlobalIsSet("unparsable") -// uIsSet = ctx.GlobalIsSet("u") -// noEnvVarIsSet = ctx.GlobalIsSet("no-env-var") -// nIsSet = ctx.GlobalIsSet("n") -// overrideIsSet = ctx.GlobalIsSet("overrides-default") -// oIsSet = ctx.GlobalIsSet("o") -// overrideValue = ctx.GlobalString("overrides-default") -// return nil -// }, -// }, -// }, -// } -// if err := a.Run([]string{"run", "hello"}); err != nil { -// t.Logf("error running Run(): %+v", err) -// } -// expect(t, timeoutIsSet, true) -// expect(t, tIsSet, true) -// expect(t, passwordIsSet, true) -// expect(t, pIsSet, true) -// expect(t, passwordValue, "badpass") -// expect(t, unparsableIsSet, false) -// expect(t, noEnvVarIsSet, false) -// expect(t, nIsSet, false) -// expect(t, overrideIsSet, true) -// expect(t, oIsSet, true) -// expect(t, overrideValue, "overridden") -// -// _ = os.Setenv("APP_UNPARSABLE", "foobar") -// if err := a.Run([]string{"run"}); err != nil { -// t.Logf("error running Run(): %+v", err) -// } -// expect(t, unparsableIsSet, false) -// expect(t, uIsSet, false) -//} - func TestContext_NumFlags(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") @@ -323,8 +254,8 @@ func TestContext_LocalFlagNames(t *testing.T) { parentSet.Bool("top-flag", true, "doc") parentCtx := NewContext(nil, parentSet, nil) ctx := NewContext(nil, set, parentCtx) - set.Parse([]string{"--one-flag", "--two-flag=foo"}) - parentSet.Parse([]string{"--top-flag"}) + _ = set.Parse([]string{"--one-flag", "--two-flag=foo"}) + _ = parentSet.Parse([]string{"--top-flag"}) actualFlags := ctx.LocalFlagNames() sort.Strings(actualFlags) @@ -340,8 +271,8 @@ func TestContext_FlagNames(t *testing.T) { parentSet.Bool("top-flag", true, "doc") parentCtx := NewContext(nil, parentSet, nil) ctx := NewContext(nil, set, parentCtx) - set.Parse([]string{"--one-flag", "--two-flag=foo"}) - parentSet.Parse([]string{"--top-flag"}) + _ = set.Parse([]string{"--one-flag", "--two-flag=foo"}) + _ = parentSet.Parse([]string{"--top-flag"}) actualFlags := ctx.FlagNames() sort.Strings(actualFlags) @@ -356,8 +287,8 @@ func TestContext_Lineage(t *testing.T) { parentSet.Bool("top-flag", true, "doc") parentCtx := NewContext(nil, parentSet, nil) ctx := NewContext(nil, set, parentCtx) - set.Parse([]string{"--local-flag"}) - parentSet.Parse([]string{"--top-flag"}) + _ = set.Parse([]string{"--local-flag"}) + _ = parentSet.Parse([]string{"--top-flag"}) lineage := ctx.Lineage() expect(t, len(lineage), 2) @@ -372,8 +303,8 @@ func TestContext_lookupFlagSet(t *testing.T) { parentSet.Bool("top-flag", true, "doc") parentCtx := NewContext(nil, parentSet, nil) ctx := NewContext(nil, set, parentCtx) - set.Parse([]string{"--local-flag"}) - parentSet.Parse([]string{"--top-flag"}) + _ = set.Parse([]string{"--local-flag"}) + _ = parentSet.Parse([]string{"--top-flag"}) fs := lookupFlagSet("top-flag", ctx) expect(t, fs, parentCtx.flagSet) @@ -534,15 +465,17 @@ func TestCheckRequiredFlags(t *testing.T) { for _, test := range tdata { t.Run(test.testCase, func(t *testing.T) { // setup - set := flag.NewFlagSet("test", 0) - for _, flags := range test.flags { - flags.Apply(set) - } - _ = set.Parse(test.parseInput) if test.envVarInput[0] != "" { os.Clearenv() _ = os.Setenv(test.envVarInput[0], test.envVarInput[1]) } + + set := flag.NewFlagSet("test", 0) + for _, flags := range test.flags { + _ = flags.Apply(set) + } + _ = set.Parse(test.parseInput) + c := &Context{} ctx := NewContext(c.App, set, c) ctx.Command.Flags = test.flags @@ -558,8 +491,10 @@ func TestCheckRequiredFlags(t *testing.T) { t.Errorf("did not expected an error, but there was one: %s", err) } for _, errString := range test.expectedErrorContents { - if !strings.Contains(err.Error(), errString) { - t.Errorf("expected error %q to contain %q, but it didn't!", err.Error(), errString) + if err != nil { + if !strings.Contains(err.Error(), errString) { + t.Errorf("expected error %q to contain %q, but it didn't!", err.Error(), errString) + } } } }) diff --git a/flag.go b/flag.go index bc7cc27..701ef31 100644 --- a/flag.go +++ b/flag.go @@ -21,26 +21,6 @@ var ( commaWhitespace = regexp.MustCompile("[, ]+.*") ) -// GenerateCompletionFlag enables completion for all commands and subcommands -//var GenerateCompletionFlag Flag = &BoolFlag{ -// Name: "generate-completion", -// Hidden: true, -//} -// -//func genCompName() string { -// names := GenerateCompletionFlag.Names() -// if len(names) == 0 { -// return "generate-completion" -// } -// return names[0] -//} -// -//// InitCompletionFlag generates completion code -//var InitCompletionFlag = &StringFlag{ -// Name: "init-completion", -// Usage: "generate completion code. Value must be 'bash' or 'zsh'", -//} - // BashCompletionFlag enables bash-completion for all commands and subcommands var BashCompletionFlag Flag = &BoolFlag{ Name: "generate-bash-completion", @@ -49,18 +29,18 @@ var BashCompletionFlag Flag = &BoolFlag{ // VersionFlag prints the version for the application var VersionFlag Flag = &BoolFlag{ - Name: "version", + Name: "version", Aliases: []string{"v"}, - Usage: "print the version", + Usage: "print the version", } // HelpFlag prints the help for all commands and subcommands. // Set to nil to disable the flag. The subcommand // will still be added unless HideHelp is set to true. var HelpFlag Flag = &BoolFlag{ - Name: "help", + Name: "help", Aliases: []string{"h"}, - Usage: "show help", + Usage: "show help", } // FlagStringer converts a flag definition to a string. This is used by help @@ -92,16 +72,12 @@ func (f FlagsByName) Len() int { } func (f FlagsByName) Less(i, j int) bool { - //<<<<<<< HEAD if len(f[j].Names()) == 0 { return false } else if len(f[i].Names()) == 0 { return true } - return f[i].Names()[0] < f[j].Names()[0] - //======= - // return lexicographicLess(f[i].GetName(), f[j].GetName()) - //>>>>>>> master + return lexicographicLess(f[i].Names()[0], f[j].Names()[0]) } func (f FlagsByName) Swap(i, j int) { @@ -116,6 +92,7 @@ type Flag interface { // Apply Flag settings to the given flag set Apply(*flag.FlagSet) error Names() []string + IsSet() bool } // RequiredFlag is an interface that allows us to mark flags as required @@ -153,14 +130,6 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { return set, nil } -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} - func visibleFlags(fl []Flag) []Flag { var visible []Flag for _, f := range fl { diff --git a/flag_bool.go b/flag_bool.go index 0efa71e..6a1da61 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -18,6 +18,12 @@ type BoolFlag struct { Value bool DefaultText string Destination *bool + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *BoolFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -57,10 +63,13 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valBool, err := strconv.ParseBool(val) + if err != nil { return fmt.Errorf("could not parse %q as bool value for flag %s: %s", val, f.Name, err) } + f.Value = valBool + f.HasBeenSet = true } } @@ -84,16 +93,6 @@ func (c *Context) Bool(name string) bool { return false } -// GlobalBool looks up the value of a global BoolFlag, returns -// false if not found -//func (c *Context) GlobalBool(name string) bool { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupBool(name, fs) -// } -// return false -//} - -// TODO: Fix Duplicate func lookupBool(name string, set *flag.FlagSet) bool { f := set.Lookup(name) if f != nil { diff --git a/flag_duration.go b/flag_duration.go index 0bf2b0e..2c34944 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -18,6 +18,12 @@ type DurationFlag struct { Value time.Duration DefaultText string Destination *time.Duration + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *DurationFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -55,12 +61,15 @@ func (f *DurationFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { - if val != ""{ + if val != "" { valDuration, err := time.ParseDuration(val) + if err != nil { return fmt.Errorf("could not parse %q as duration value for flag %s: %s", val, f.Name, err) } + f.Value = valDuration + f.HasBeenSet = true } } @@ -82,14 +91,6 @@ func (c *Context) Duration(name string) time.Duration { } return 0 } -// GlobalDuration looks up the value of a global DurationFlag, returns -// 0 if not found -//func (c *Context) GlobalDuration(name string) time.Duration { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupDuration(name, fs) -// } -// return 0 -//} func lookupDuration(name string, set *flag.FlagSet) time.Duration { f := set.Lookup(name) diff --git a/flag_float64.go b/flag_float64.go index 03e8faa..2285547 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -18,6 +18,12 @@ type Float64Flag struct { Value float64 DefaultText string Destination *float64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64Flag)IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -57,10 +63,13 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valFloat, err := strconv.ParseFloat(val, 10) + if err != nil { return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err) } + f.Value = valFloat + f.HasBeenSet = true } } @@ -84,15 +93,6 @@ func (c *Context) Float64(name string) float64 { return 0 } -// GlobalFloat64 looks up the value of a global Float64Flag, returns -// 0 if not found -//func (c *Context) GlobalFloat64(name string) float64 { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupFloat64(name, fs) -// } -// return 0 -//} - func lookupFloat64(name string, set *flag.FlagSet) float64 { f := set.Lookup(name) if f != nil { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 159bdea..91d2e9d 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -74,6 +74,12 @@ type Float64SliceFlag struct { Hidden bool Value *Float64Slice DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64SliceFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -114,12 +120,16 @@ func (f *Float64SliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { - f.Value = &Float64Slice{} + if val != "" { + f.Value = &Float64Slice{} - for _, s := range strings.Split(val, ",") { - if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) + } } + + f.HasBeenSet = true } } @@ -142,15 +152,6 @@ func (c *Context) Float64Slice(name string) []float64 { return nil } -// GlobalFloat64Slice looks up the value of a global Float64SliceFlag, returns -// nil if not found -//func (c *Context) GlobalFloat64Slice(name string) []int { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupFloat64Slice(name, fs) -// } -// return nil -//} - func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 { f := set.Lookup(name) if f != nil { diff --git a/flag_generic.go b/flag_generic.go index 4ab804b..2d9baa7 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -23,6 +23,12 @@ type GenericFlag struct { TakesFile bool Value Generic DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *GenericFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -64,8 +70,12 @@ func (f *GenericFlag) GetValue() string { // provided by the user for parsing by the flag func (f GenericFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { - if err := f.Value.Set(val); err != nil { - return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err) + if val != "" { + if err := f.Value.Set(val); err != nil { + return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err) + } + + f.HasBeenSet = true } } @@ -79,18 +89,12 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error { // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { - return lookupGeneric(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupGeneric(name, fs) + } + return nil } -// GlobalGeneric looks up the value of a global GenericFlag, returns -// nil if not found -//func (c *Context) GlobalGeneric(name string) interface{} { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupGeneric(name, fs) -// } -// return nil -//} - func lookupGeneric(name string, set *flag.FlagSet) interface{} { f := set.Lookup(name) if f != nil { diff --git a/flag_int.go b/flag_int.go index 8bffad5..be961bf 100644 --- a/flag_int.go +++ b/flag_int.go @@ -18,6 +18,12 @@ type IntFlag struct { Value int DefaultText string Destination *int + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -57,10 +63,13 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) + if err != nil { return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) } + f.Value = int(valInt) + f.HasBeenSet = true } } @@ -84,15 +93,6 @@ func (c *Context) Int(name string) int { return 0 } -// GlobalInt looks up the value of a global IntFlag, returns -// 0 if not found -//func (c *Context) GlobalInt(name string) int { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupInt(name, fs) -// } -// return 0 -//} - func lookupInt(name string, set *flag.FlagSet) int { f := set.Lookup(name) if f != nil { diff --git a/flag_int64.go b/flag_int64.go index eb67b42..1f4cefd 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -18,6 +18,12 @@ type Int64Flag struct { Value int64 DefaultText string Destination *int64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64Flag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -57,10 +63,13 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) + if err != nil { return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) } + f.Value = valInt + f.HasBeenSet = true } } @@ -84,15 +93,6 @@ func (c *Context) Int64(name string) int64 { return 0 } -// GlobalInt64 looks up the value of a global Int64Flag, returns -// 0 if not found -//func (c *Context) GlobalInt64(name string) int64 { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupInt64(name, fs) -// } -// return 0 -//} - func lookupInt64(name string, set *flag.FlagSet) int64 { f := set.Lookup(name) if f != nil { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index b3f53d4..818f858 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -74,6 +74,12 @@ type Int64SliceFlag struct { Hidden bool Value *Int64Slice DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64SliceFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -121,6 +127,8 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) } } + + f.HasBeenSet = true } for _, name := range f.Names() { @@ -142,15 +150,6 @@ func (c *Context) Int64Slice(name string) []int64 { return nil } -// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns -// nil if not found -//func (c *Context) GlobalInt64Slice(name string) []int { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupInt64Slice(name, fs) -// } -// return nil -//} - func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { f := set.Lookup(name) if f != nil { diff --git a/flag_int_slice.go b/flag_int_slice.go index 1b63c8a..d4f934e 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -85,6 +85,12 @@ type IntSliceFlag struct { Hidden bool Value *IntSlice DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntSliceFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -132,6 +138,8 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) } } + + f.HasBeenSet = true } for _, name := range f.Names() { @@ -153,15 +161,6 @@ func (c *Context) IntSlice(name string) []int { return nil } -// GlobalIntSlice looks up the value of a global IntSliceFlag, returns -// nil if not found -//func (c *Context) GlobalIntSlice(name string) []int { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupIntSlice(name, fs) -// } -// return nil -//} - func lookupIntSlice(name string, set *flag.FlagSet) []int { f := set.Lookup(name) if f != nil { diff --git a/flag_path.go b/flag_path.go index 89cd99c..d6b23c3 100644 --- a/flag_path.go +++ b/flag_path.go @@ -14,6 +14,12 @@ type PathFlag struct { Value string DefaultText string Destination *string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *PathFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -52,6 +58,7 @@ func (f *PathFlag) GetValue() string { func (f *PathFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = val + f.HasBeenSet = true } for _, name := range f.Names() { diff --git a/flag_string.go b/flag_string.go index 3e15066..bcd8253 100644 --- a/flag_string.go +++ b/flag_string.go @@ -15,52 +15,59 @@ type StringFlag struct { Value string DefaultText string Destination *string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value // (for usage defaults) -func (s *StringFlag) String() string { - return FlagStringer(s) +func (f *StringFlag) String() string { + return FlagStringer(f) } // Names returns the names of the flag -func (s *StringFlag) Names() []string { - return flagNames(s) +func (f *StringFlag) Names() []string { + return flagNames(f) } // IsRequired returns whether or not the flag is required -func (s *StringFlag) IsRequired() bool { - return s.Required +func (f *StringFlag) IsRequired() bool { + return f.Required } // TakesValue returns true of the flag takes a value, otherwise false -func (s *StringFlag) TakesValue() bool { +func (f *StringFlag) TakesValue() bool { return true } // GetUsage returns the usage string for the flag -func (s *StringFlag) GetUsage() string { - return s.Usage +func (f *StringFlag) GetUsage() string { + return f.Usage } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. -func (s *StringFlag) GetValue() string { - return s.Value +func (f *StringFlag) GetValue() string { + return f.Value } // Apply populates the flag given the flag set and environment -func (s *StringFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(s.EnvVars, s.FilePath); ok { - s.Value = val +func (f *StringFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = val + f.HasBeenSet = true } - for _, name := range s.Names() { - if s.Destination != nil { - set.StringVar(s.Destination, name, s.Value, s.Usage) + for _, name := range f.Names() { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) continue } - set.String(name, s.Value, s.Usage) + set.String(name, f.Value, f.Usage) } return nil @@ -75,15 +82,6 @@ func (c *Context) String(name string) string { return "" } -// GlobalString looks up the value of a global StringFlag, returns -// "" if not found -//func (c *Context) GlobalString(name string) string { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupPath(name, fs) -// } -// return "" -//} - func lookupString(name string, set *flag.FlagSet) string { f := set.Lookup(name) if f != nil { diff --git a/flag_string_slice.go b/flag_string_slice.go index 2dea966..a3a263b 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -69,6 +69,12 @@ type StringSliceFlag struct { TakesFile bool Value *StringSlice DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringSliceFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -110,11 +116,14 @@ func (f *StringSliceFlag) GetValue() string { func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &StringSlice{} + for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) } } + + f.HasBeenSet = true } for _, name := range f.Names() { @@ -136,15 +145,6 @@ func (c *Context) StringSlice(name string) []string { return nil } -// GlobalStringSlice looks up the value of a global StringSliceFlag, returns -// nil if not found -//func (c *Context) GlobalStringSlice(name string) []string { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupStringSlice(name, fs) -// } -// return nil -//} - func lookupStringSlice(name string, set *flag.FlagSet) []string { f := set.Lookup(name) if f != nil { diff --git a/flag_uint.go b/flag_uint.go index d1eb7f3..9f59238 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -18,6 +18,12 @@ type UintFlag struct { Value uint DefaultText string Destination *uint + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *UintFlag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -56,6 +62,7 @@ func (f *UintFlag) Apply(set *flag.FlagSet) error { } f.Value = uint(valInt) + f.HasBeenSet = true } } @@ -85,15 +92,6 @@ func (c *Context) Uint(name string) uint { return 0 } -// GlobalUint looks up the value of a global UintFlag, returns -// 0 if not found -//func (c *Context) GlobalUint(name string) uint { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupUint(name, fs) -// } -// return 0 -//} - func lookupUint(name string, set *flag.FlagSet) uint { f := set.Lookup(name) if f != nil { diff --git a/flag_uint64.go b/flag_uint64.go index 441c0fa..5bbd1fa 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -18,6 +18,12 @@ type Uint64Flag struct { Value uint64 DefaultText string Destination *uint64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Uint64Flag) IsSet() bool { + return f.HasBeenSet } // String returns a readable representation of this value @@ -56,6 +62,7 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) error { } f.Value = valInt + f.HasBeenSet = true } } @@ -85,15 +92,6 @@ func (c *Context) Uint64(name string) uint64 { return 0 } -// GlobalUint64 looks up the value of a global Uint64Flag, returns -// 0 if not found -//func (c *Context) GlobalUint64(name string) uint64 { -// if fs := lookupGlobalFlagSet(name, c); fs != nil { -// return lookupUint64(name, fs) -// } -// return 0 -//} - func lookupUint64(name string, set *flag.FlagSet) uint64 { f := set.Lookup(name) if f != nil {