From 867aa0912da4ccb7498eac8cb90f07c7abbc4563 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 5 Apr 2016 12:35:30 -0400 Subject: [PATCH 001/158] Overwrite slice flag defaults when set Closes #160 --- altsrc/flag.go | 4 +- flag.go | 71 ++++++++++++++++----- flag_test.go | 169 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 191 insertions(+), 53 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index f13ffb4..2869905 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -122,7 +122,7 @@ func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputS return err } if value != nil { - var sliceValue cli.StringSlice = value + var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...)) eachName(f.Name, func(name string) { underlyingFlag := f.set.Lookup(f.Name) if underlyingFlag != nil { @@ -163,7 +163,7 @@ func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour return err } if value != nil { - var sliceValue cli.IntSlice = value + var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...)) eachName(f.Name, func(name string) { underlyingFlag := f.set.Lookup(f.Name) if underlyingFlag != nil { diff --git a/flag.go b/flag.go index e951c2d..c7b367b 100644 --- a/flag.go +++ b/flag.go @@ -111,26 +111,39 @@ func (f GenericFlag) GetName() string { return f.Name } -// StringSlice is an opaque type for []string to satisfy flag.Value -type StringSlice []string +// StringSlice wraps a []string to satisfy flag.Value +type StringSlice struct { + slice []string + hasBeenSet bool +} + +// NewStringSlice creates a *StringSlice with default values +func NewStringSlice(defaults ...string) *StringSlice { + return &StringSlice{slice: append([]string{}, defaults...)} +} // Set appends the string value to the list of values func (f *StringSlice) Set(value string) error { - *f = append(*f, value) + if !f.hasBeenSet { + f.slice = []string{} + f.hasBeenSet = true + } + + f.slice = append(f.slice, value) return nil } // String returns a readable representation of this value (for usage defaults) func (f *StringSlice) String() string { - return fmt.Sprintf("%s", *f) + return fmt.Sprintf("%s", f.slice) } // Value returns the slice of strings set by this flag func (f *StringSlice) Value() []string { - return *f + return f.slice } -// StringSlice is a string flag that can be specified multiple times on the +// StringSliceFlag is a string flag that can be specified multiple times on the // command-line type StringSliceFlag struct { Name string @@ -163,10 +176,11 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { } } + if f.Value == nil { + f.Value = &StringSlice{} + } + eachName(f.Name, func(name string) { - if f.Value == nil { - f.Value = &StringSlice{} - } set.Var(f.Value, name, f.Usage) }) } @@ -175,28 +189,51 @@ func (f StringSliceFlag) GetName() string { return f.Name } -// StringSlice is an opaque type for []int to satisfy flag.Value -type IntSlice []int +// IntSlice wraps an []int to satisfy flag.Value +type IntSlice struct { + slice []int + hasBeenSet bool +} + +// NewIntSlice makes an *IntSlice with default values +func NewIntSlice(defaults ...int) *IntSlice { + return &IntSlice{slice: append([]int{}, defaults...)} +} + +// SetInt directly adds an integer to the list of values +func (i *IntSlice) SetInt(value int) { + if !i.hasBeenSet { + i.slice = []int{} + i.hasBeenSet = true + } + + i.slice = append(i.slice, value) +} // Set parses the value into an integer and appends it to the list of values -func (f *IntSlice) Set(value string) error { +func (i *IntSlice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []int{} + i.hasBeenSet = true + } + tmp, err := strconv.Atoi(value) if err != nil { return err } else { - *f = append(*f, tmp) + i.slice = append(i.slice, tmp) } return nil } // String returns a readable representation of this value (for usage defaults) -func (f *IntSlice) String() string { - return fmt.Sprintf("%d", *f) +func (i *IntSlice) String() string { + return fmt.Sprintf("%v", i.slice) } // Value returns the slice of ints set by this flag -func (f *IntSlice) Value() []int { - return *f +func (i *IntSlice) Value() []int { + return i.slice } // IntSliceFlag is an int flag that can be specified multiple times on the diff --git a/flag_test.go b/flag_test.go index 3caa70a..ba8cc0d 100644 --- a/flag_test.go +++ b/flag_test.go @@ -4,9 +4,9 @@ import ( "fmt" "os" "reflect" + "runtime" "strings" "testing" - "runtime" ) var boolFlagTests = []struct { @@ -64,7 +64,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_FOO%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -74,30 +74,14 @@ var stringSliceFlagTests = []struct { value *StringSlice expected string }{ - {"help", func() *StringSlice { - s := &StringSlice{} - s.Set("") - return s - }(), "--help [--help option --help option]\t"}, - {"h", func() *StringSlice { - s := &StringSlice{} - s.Set("") - return s - }(), "-h [-h option -h option]\t"}, - {"h", func() *StringSlice { - s := &StringSlice{} - s.Set("") - return s - }(), "-h [-h option -h option]\t"}, - {"test", func() *StringSlice { - s := &StringSlice{} - s.Set("Something") - return s - }(), "--test [--test option --test option]\t"}, + {"help", NewStringSlice(""), "--help [--help option --help option]\t"}, + {"h", NewStringSlice(""), "-h [-h option -h option]\t"}, + {"h", NewStringSlice(""), "-h [-h option -h option]\t"}, + {"test", NewStringSlice("Something"), "--test [--test option --test option]\t"}, + {"d, dee", NewStringSlice("Inka", "Dinka", "dooo"), "-d, --dee [-d option -d option]\t"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { - for _, test := range stringSliceFlagTests { flag := StringSliceFlag{Name: test.name, Value: test.value} output := flag.String() @@ -120,7 +104,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_QWWX%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%q does not end with" + expectedSuffix, output) + t.Errorf("%q does not end with"+expectedSuffix, output) } } } @@ -157,7 +141,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAR%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -194,7 +178,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAR%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -207,11 +191,7 @@ var intSliceFlagTests = []struct { {"help", &IntSlice{}, "--help [--help option --help option]\t"}, {"h", &IntSlice{}, "-h [-h option -h option]\t"}, {"h", &IntSlice{}, "-h [-h option -h option]\t"}, - {"test", func() *IntSlice { - i := &IntSlice{} - i.Set("9") - return i - }(), "--test [--test option --test option]\t"}, + {"test", NewIntSlice(9), "--test [--test option --test option]\t"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { @@ -238,7 +218,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_SMURF%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%q does not end with" + expectedSuffix, output) + t.Errorf("%q does not end with"+expectedSuffix, output) } } } @@ -275,7 +255,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_BAZ%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -313,7 +293,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { expectedSuffix = " [%APP_ZAP%]" } if !strings.HasSuffix(output, expectedSuffix) { - t.Errorf("%s does not end with" + expectedSuffix, output) + t.Errorf("%s does not end with"+expectedSuffix, output) } } } @@ -404,6 +384,38 @@ func TestParseMultiStringSlice(t *testing.T) { }).Run([]string{"run", "-s", "10", "-s", "20"}) } +func TestParseMultiStringSliceWithDefaults(t *testing.T) { + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { + t.Errorf("main name not set: %v", ctx.StringSlice("serve")) + } + if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { + t.Errorf("short name not set: %v", ctx.StringSlice("s")) + } + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { + t.Errorf("main name not set: %v", ctx.StringSlice("serve")) + } + if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"9", "2"}) { + t.Errorf("short name not set: %v", ctx.StringSlice("s")) + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiStringSliceFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_INTERVALS", "20,30,40") @@ -423,6 +435,25 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { os.Clearenv() os.Setenv("APP_INTERVALS", "20,30,40") @@ -442,6 +473,25 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiInt(t *testing.T) { a := App{ Flags: []Flag{ @@ -531,6 +581,38 @@ func TestParseMultiIntSlice(t *testing.T) { }).Run([]string{"run", "-s", "10", "-s", "20"}) } +func TestParseMultiIntSliceWithDefaults(t *testing.T) { + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.IntSlice("s"), []int{9, 2}) { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiIntSliceFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_INTERVALS", "20,30,40") @@ -550,6 +632,25 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(1, 2, 5), EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { os.Clearenv() os.Setenv("APP_INTERVALS", "20,30,40") From cb433e7468f95fdf70780e87cf770e4a1c2ebabf Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 5 Apr 2016 22:38:31 -0400 Subject: [PATCH 002/158] Use NewIntSlice and NewStringSlice internally --- app_test.go | 4 ++-- flag.go | 11 ++++++----- flag_test.go | 18 +++++++++--------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app_test.go b/app_test.go index ebf26c7..0d1820e 100644 --- a/app_test.go +++ b/app_test.go @@ -313,8 +313,8 @@ func TestApp_ParseSliceFlags(t *testing.T) { command := Command{ Name: "cmd", Flags: []Flag{ - IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"}, - StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"}, + IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"}, + StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"}, }, Action: func(c *Context) { parsedIntSlice = c.IntSlice("p") diff --git a/flag.go b/flag.go index c7b367b..f93d3e8 100644 --- a/flag.go +++ b/flag.go @@ -165,7 +165,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { - newVal := &StringSlice{} + newVal := NewStringSlice() for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) newVal.Set(s) @@ -177,7 +177,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { } if f.Value == nil { - f.Value = &StringSlice{} + f.Value = NewStringSlice() } eachName(f.Name, func(name string) { @@ -272,10 +272,11 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { } } + if f.Value == nil { + f.Value = NewIntSlice() + } + eachName(f.Name, func(name string) { - if f.Value == nil { - f.Value = &IntSlice{} - } set.Var(f.Value, name, f.Usage) }) } diff --git a/flag_test.go b/flag_test.go index ba8cc0d..3bc6be7 100644 --- a/flag_test.go +++ b/flag_test.go @@ -188,9 +188,9 @@ var intSliceFlagTests = []struct { value *IntSlice expected string }{ - {"help", &IntSlice{}, "--help [--help option --help option]\t"}, - {"h", &IntSlice{}, "-h [-h option -h option]\t"}, - {"h", &IntSlice{}, "-h [-h option -h option]\t"}, + {"help", NewIntSlice(), "--help [--help option --help option]\t"}, + {"h", NewIntSlice(), "-h [-h option -h option]\t"}, + {"h", NewIntSlice(), "-h [-h option -h option]\t"}, {"test", NewIntSlice(9), "--test [--test option --test option]\t"}, } @@ -371,7 +371,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: &StringSlice{}}, + StringSliceFlag{Name: "serve, s", Value: NewStringSlice()}, }, Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { @@ -422,7 +422,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"}, + StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "APP_INTERVALS"}, }, Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -460,7 +460,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -568,7 +568,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { func TestParseMultiIntSlice(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: &IntSlice{}}, + IntSliceFlag{Name: "serve, s", Value: NewIntSlice()}, }, Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -619,7 +619,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"}, + IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "APP_INTERVALS"}, }, Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -657,7 +657,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, Action: func(ctx *Context) { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { From 7a5bfc850d32c2062bdaefb8aaa33347119d95ba Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 5 Apr 2016 22:40:12 -0400 Subject: [PATCH 003/158] Update dangling IntSlice literal --- flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flag.go b/flag.go index f93d3e8..a96325e 100644 --- a/flag.go +++ b/flag.go @@ -258,7 +258,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { - newVal := &IntSlice{} + newVal := NewIntSlice() for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) err := newVal.Set(s) From 47f6721faea7f216e7f355ed2ca5a0b1d193a3f7 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2016 12:35:56 -0400 Subject: [PATCH 004/158] Update CHANGELOG and add a script to help with slice type migration --- CHANGELOG.md | 10 ++++++ cli-migrate-slice-types | 75 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100755 cli-migrate-slice-types diff --git a/CHANGELOG.md b/CHANGELOG.md index 074bfb2..f17f2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ ### Added - Support for placeholders in flag usage strings +## [2.0.0] +### Added +- `NewStringSlice` and `NewIntSlice` for creating their related types + +### Removed +- the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`. +To migrate to the new API, you may choose to run [this helper +(python) script](./cli-migrate-slice-types). + ## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) ### Added - Codebeat badge @@ -220,6 +229,7 @@ - Initial implementation. [Unreleased]: https://github.com/codegangsta/cli/compare/v1.14.0...HEAD +[2.0.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v2.0.0 [1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 [1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 [1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0 diff --git a/cli-migrate-slice-types b/cli-migrate-slice-types new file mode 100755 index 0000000..5c6cb1f --- /dev/null +++ b/cli-migrate-slice-types @@ -0,0 +1,75 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +import argparse +import os +import re +import sys + + +DESCRIPTION = """\ +Migrate arbitrary `.go` sources from the pre-v2.0.0 API for StringSlice and +IntSlice types, optionally writing the changes back to file. +""" +SLICE_TYPE_RE = re.compile( + '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', + flags=re.DOTALL +) + + +def main(sysargs=sys.argv[:]): + parser = argparse.ArgumentParser( + description=DESCRIPTION, + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('basedir', nargs='?', metavar='BASEDIR', + type=os.path.abspath, default=os.getcwd()) + parser.add_argument('-w', '--write', help='write changes back to file', + action='store_true', default=False) + + args = parser.parse_args(sysargs[1:]) + + for filepath in _find_candidate_files(args.basedir): + updated_source = _update_source(filepath) + if args.write: + print('Updating {!r}'.format(filepath)) + + with open(filepath, 'w') as outfile: + outfile.write(updated_source) + else: + print('// -> Updated {!r}'.format(filepath)) + print(updated_source) + + return 0 + + +def _update_source(filepath): + with open(filepath) as infile: + source = infile.read() + return SLICE_TYPE_RE.sub(_slice_type_repl, source) + + +def _slice_type_repl(match): + return 'cli.New{}({})'.format( + match.groupdict()['type'], match.groupdict()['args'] + ) + + +def _find_candidate_files(basedir): + for curdir, dirs, files in os.walk(basedir): + for i, dirname in enumerate(dirs[:]): + if dirname.startswith('.'): + dirs.pop(i) + + for filename in files: + if not filename.endswith('.go'): + continue + + filepath = os.path.join(curdir, filename) + if not os.access(filepath, os.R_OK | os.W_OK): + continue + + yield filepath + + +if __name__ == '__main__': + sys.exit(main()) From d1b0c49a988426023ab923a5b3b6baf4fae372df Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 29 Apr 2016 02:30:49 -0400 Subject: [PATCH 005/158] Ensure slice types can safely round-trip through flag.FlagSet --- context.go | 3 ++- flag.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index c542a67..a4c3904 100644 --- a/context.go +++ b/context.go @@ -362,7 +362,8 @@ func lookupBoolT(name string, set *flag.FlagSet) bool { func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { switch ff.Value.(type) { - case *StringSlice: + case Serializeder: + set.Set(name, ff.Value.(Serializeder).Serialized()) default: set.Set(name, ff.Value.String()) } diff --git a/flag.go b/flag.go index 95eefc4..38029c4 100644 --- a/flag.go +++ b/flag.go @@ -1,6 +1,7 @@ package cli import ( + "encoding/json" "flag" "fmt" "os" @@ -10,6 +11,10 @@ import ( "time" ) +var ( + slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) +) + // This flag enables bash-completion for all commands and subcommands var BashCompletionFlag = BoolFlag{ Name: "generate-bash-completion", @@ -29,6 +34,11 @@ var HelpFlag = BoolFlag{ Usage: "show help", } +// Serializeder is used to circumvent the limitations of flag.FlagSet.Set +type Serializeder interface { + Serialized() string +} + // Flag is a common interface related to parsing flags in cli. // For more advanced flag parsing techniques, it is recommended that // this interface be implemented. @@ -130,6 +140,13 @@ func (f *StringSlice) Set(value string) error { f.hasBeenSet = true } + if strings.HasPrefix(value, slPfx) { + v := []string{} + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), v) + f.slice = append(f.slice, v...) + return nil + } + f.slice = append(f.slice, value) return nil } @@ -139,6 +156,12 @@ func (f *StringSlice) String() string { return fmt.Sprintf("%s", f.slice) } +// Serialized allows StringSlice to fulfill Serializeder +func (f *StringSlice) Serialized() string { + jsonBytes, _ := json.Marshal(f.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + // Value returns the slice of strings set by this flag func (f *StringSlice) Value() []string { return f.slice @@ -219,6 +242,13 @@ func (i *IntSlice) Set(value string) error { i.hasBeenSet = true } + if strings.HasPrefix(slPfx, value) { + v := []int{} + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), v) + i.slice = append(i.slice, v...) + return nil + } + tmp, err := strconv.Atoi(value) if err != nil { return err From 1a91f3dce5b5847ac96cd3f459efe2fcb4ba29ba Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 29 Apr 2016 02:53:58 -0400 Subject: [PATCH 006/158] Ensure IntSlice & StringSlice serialization works as expected --- flag.go | 20 +++++++++++++------- flag_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/flag.go b/flag.go index 38029c4..a5c8d2c 100644 --- a/flag.go +++ b/flag.go @@ -141,9 +141,9 @@ func (f *StringSlice) Set(value string) error { } if strings.HasPrefix(value, slPfx) { - v := []string{} - _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), v) - f.slice = append(f.slice, v...) + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) + f.hasBeenSet = true return nil } @@ -242,10 +242,10 @@ func (i *IntSlice) Set(value string) error { i.hasBeenSet = true } - if strings.HasPrefix(slPfx, value) { - v := []int{} - _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), v) - i.slice = append(i.slice, v...) + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true return nil } @@ -263,6 +263,12 @@ func (i *IntSlice) String() string { return fmt.Sprintf("%v", i.slice) } +// Serialized allows IntSlice to fulfill Serializeder +func (i *IntSlice) Serialized() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + // Value returns the slice of ints set by this flag func (i *IntSlice) Value() []int { return i.slice diff --git a/flag_test.go b/flag_test.go index 1288a73..843f175 100644 --- a/flag_test.go +++ b/flag_test.go @@ -43,7 +43,6 @@ var stringFlagTests = []struct { } func TestStringFlagHelpOutput(t *testing.T) { - for _, test := range stringFlagTests { flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} output := flag.String() @@ -376,11 +375,12 @@ func TestParseMultiStringSlice(t *testing.T) { StringSliceFlag{Name: "serve, s", Value: NewStringSlice()}, }, Action: func(ctx *Context) { - if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { - t.Errorf("main name not set") + expected := []string{"10", "20"} + if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) { + t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) } - if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { - t.Errorf("short name not set") + if !reflect.DeepEqual(ctx.StringSlice("s"), expected) { + t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) } }, }).Run([]string{"run", "-s", "10", "-s", "20"}) @@ -392,11 +392,12 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) { - if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { - t.Errorf("main name not set: %v", ctx.StringSlice("serve")) + expected := []string{"10", "20"} + if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) { + t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) } - if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { - t.Errorf("short name not set: %v", ctx.StringSlice("s")) + if !reflect.DeepEqual(ctx.StringSlice("s"), expected) { + t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) } }, }).Run([]string{"run", "-s", "10", "-s", "20"}) @@ -960,3 +961,35 @@ func TestParseGenericFromEnvCascade(t *testing.T) { } a.Run([]string{"run"}) } + +func TestStringSlice_Serialized_Set(t *testing.T) { + sl0 := NewStringSlice("a", "b") + ser0 := sl0.Serialized() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewStringSlice("c", "d") + sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} + +func TestIntSlice_Serialized_Set(t *testing.T) { + sl0 := NewIntSlice(1, 2) + ser0 := sl0.Serialized() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewIntSlice(3, 4) + sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} From f585ec7cb8b0d9189ac9016fe83f911f3a360f2e Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 7 May 2016 16:36:54 -0700 Subject: [PATCH 007/158] Remove reordering of flags and arguments This was introduced by #36, but only worked in the specific case of all arguments being passed before all flags. If the user mixed them, they ended up with odd parsing behavior where the arguments were reordered (causing #103 and #355). Given the tradeoffs I think we should remove support for flag reordering. Fixes #103 Fixes #355 --- CHANGELOG.md | 11 +++++++++-- app_test.go | 29 +++-------------------------- command.go | 38 +++----------------------------------- command_test.go | 8 ++++---- 4 files changed, 19 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea6c483..b0e1d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,15 @@ ### Removed - the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`. -To migrate to the new API, you may choose to run [this helper -(python) script](./cli-migrate-slice-types). + To migrate to the new API, you may choose to run [this helper + (python) script](./cli-migrate-slice-types). +- The optimistic reordering of arguments and flags introduced by + https://github.com/codegangsta/cli/pull/36. This behavior only worked when + all arguments appeared before all flags, but caused [weird issues with boolean + flags](https://github.com/codegangsta/cli/issues/103) and [reordering of the + arguments](https://github.com/codegangsta/cli/issues/355) when the user + attempted to mix flags and arguments. Given the trade-offs we removed support + for this reordering. ## [Unreleased] - (1.x series) ### Added diff --git a/app_test.go b/app_test.go index ca4abce..a66cfa4 100644 --- a/app_test.go +++ b/app_test.go @@ -189,29 +189,6 @@ func TestApp_Command(t *testing.T) { } } -func TestApp_CommandWithArgBeforeFlags(t *testing.T) { - var parsedOption, firstArg string - - app := NewApp() - command := Command{ - Name: "cmd", - Flags: []Flag{ - StringFlag{Name: "option", Value: "", Usage: "some option"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - firstArg = c.Args().First() - return nil - }, - } - app.Commands = []Command{command} - - app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) - - expect(t, parsedOption, "my-option") - expect(t, firstArg, "my-arg") -} - func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context @@ -257,7 +234,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { } app.Commands = []Command{command} - app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) + app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}) expect(t, parsedOption, "my-option") expect(t, args[0], "my-arg") @@ -342,7 +319,7 @@ func TestApp_ParseSliceFlags(t *testing.T) { } app.Commands = []Command{command} - app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) + app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"}) IntsEquals := func(a, b []int) bool { if len(a) != len(b) { @@ -398,7 +375,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { } app.Commands = []Command{command} - app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) + app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"}) var expectedIntSlice = []int{2} var expectedStringSlice = []string{"A"} diff --git a/command.go b/command.go index 9ca7e51..7c94de3 100644 --- a/command.go +++ b/command.go @@ -86,42 +86,10 @@ func (c Command) Run(ctx *Context) (err error) { set := flagSet(c.Name, c.Flags) set.SetOutput(ioutil.Discard) - if !c.SkipFlagParsing { - firstFlagIndex := -1 - terminatorIndex := -1 - for index, arg := range ctx.Args() { - if arg == "--" { - terminatorIndex = index - break - } else if arg == "-" { - // Do nothing. A dash alone is not really a flag. - continue - } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { - firstFlagIndex = index - } - } - - if firstFlagIndex > -1 { - args := ctx.Args() - regularArgs := make([]string, len(args[1:firstFlagIndex])) - copy(regularArgs, args[1:firstFlagIndex]) - - var flagArgs []string - if terminatorIndex > -1 { - flagArgs = args[firstFlagIndex:terminatorIndex] - regularArgs = append(regularArgs, args[terminatorIndex:]...) - } else { - flagArgs = args[firstFlagIndex:] - } - - err = set.Parse(append(flagArgs, regularArgs...)) - } else { - err = set.Parse(ctx.Args().Tail()) - } + if c.SkipFlagParsing { + err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) } else { - if c.SkipFlagParsing { - err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) - } + err = set.Parse(ctx.Args().Tail()) } if err != nil { diff --git a/command_test.go b/command_test.go index 2687212..5e51843 100644 --- a/command_test.go +++ b/command_test.go @@ -15,10 +15,10 @@ func TestCommandFlagParsing(t *testing.T) { skipFlagParsing bool expectedErr error }{ - {[]string{"blah", "blah", "-break"}, false, errors.New("flag provided but not defined: -break")}, // Test normal "not ignoring flags" flow - {[]string{"blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags - {[]string{"blah", "-break"}, true, nil}, // Test SkipFlagParsing with random flag arg - {[]string{"blah", "-help"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg + {[]string{"test-cmd", "-break", "blah", "blah"}, false, errors.New("flag provided but not defined: -break")}, // Test normal "not ignoring flags" flow + {[]string{"test-cmd", "blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags + {[]string{"test-cmd", "-break", "blah"}, true, nil}, // Test SkipFlagParsing with random flag arg + {[]string{"test-cmd", "-help", "blah"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg } for _, c := range cases { From bef835d455a7351fc745869c5a9fd456f2bff4a4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 16 May 2016 10:18:15 -0400 Subject: [PATCH 008/158] Remove all Context.Global* methods and change the behavior of the non-Global variants to always search up the context lineage. Closes #385 --- CHANGELOG.md | 11 ++ altsrc/helpers_test.go | 14 ++- context.go | 229 ++++++++++++++++------------------------- context_test.go | 163 ++++++----------------------- flag_test.go | 21 ++-- help.go | 8 +- 6 files changed, 161 insertions(+), 285 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d089c0..4a6b2f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ ## 2.0.0 - (unreleased 2.x series) ### Added - `NewStringSlice` and `NewIntSlice` for creating their related types +- `Context.Lineage` to get all contexts from current up to global +- `Context.LocalFlagNames` to get the flag names from *only* the current context + +### Changed +- `Context.FlagNames` now returns all flags in the context lineage +- `Context.IsSet` now considers the full context lineage ### Removed - the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`. @@ -17,6 +23,11 @@ arguments](https://github.com/codegangsta/cli/issues/355) when the user attempted to mix flags and arguments. Given the trade-offs we removed support for this reordering. +- All `Context.Global*` methods, as the non-global versions now traverse up + the context lineage automatically. + +### Fixed +- `Context.BoolT` now returns `true` when not found ## [Unreleased] - (1.x series) ### Added diff --git a/altsrc/helpers_test.go b/altsrc/helpers_test.go index 3b7f7e9..33e8a4b 100644 --- a/altsrc/helpers_test.go +++ b/altsrc/helpers_test.go @@ -1,13 +1,23 @@ package altsrc import ( + "os" "reflect" + "runtime" + "strings" "testing" ) +var ( + wd, _ = os.Getwd() +) + func expect(t *testing.T, a interface{}, b interface{}) { - if !reflect.DeepEqual(b, a) { - t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + _, fn, line, _ := runtime.Caller(1) + fn = strings.Replace(fn, wd+"/", "", -1) + + if !reflect.DeepEqual(a, b) { + t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } diff --git a/context.go b/context.go index 87ad6d4..af80bdb 100644 --- a/context.go +++ b/context.go @@ -28,129 +28,76 @@ func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { // Int looks up the value of a local int flag, returns 0 if no int flag exists func (c *Context) Int(name string) int { - return lookupInt(name, c.flagSet) -} - -// Duration looks up the value of a local time.Duration flag, returns 0 if no -// time.Duration flag exists -func (c *Context) Duration(name string) time.Duration { - return lookupDuration(name, c.flagSet) -} - -// Float64 looks up the value of a local float64 flag, returns 0 if no float64 -// flag exists -func (c *Context) Float64(name string) float64 { - return lookupFloat64(name, c.flagSet) -} - -// Bool looks up the value of a local bool flag, returns false if no bool flag exists -func (c *Context) Bool(name string) bool { - return lookupBool(name, c.flagSet) -} - -// BoolT looks up the value of a local boolT flag, returns false if no bool flag exists -func (c *Context) BoolT(name string) bool { - return lookupBoolT(name, c.flagSet) -} - -// String looks up the value of a local string flag, returns "" if no string flag exists -func (c *Context) String(name string) string { - return lookupString(name, c.flagSet) -} - -// StringSlice looks up the value of a local string slice flag, returns nil if no -// string slice flag exists -func (c *Context) StringSlice(name string) []string { - return lookupStringSlice(name, c.flagSet) -} - -// IntSlice looks up the value of a local int slice flag, returns nil if no int -// slice flag exists -func (c *Context) IntSlice(name string) []int { - return lookupIntSlice(name, c.flagSet) -} - -// Generic looks up the value of a local generic flag, returns nil if no generic -// flag exists -func (c *Context) Generic(name string) interface{} { - return lookupGeneric(name, c.flagSet) -} - -// GlobalInt looks up the value of a global int flag, returns 0 if no int flag exists -func (c *Context) GlobalInt(name string) int { - if fs := lookupGlobalFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { return lookupInt(name, fs) } return 0 } -// GlobalFloat64 looks up the value of a global float64 flag, returns float64(0) -// if no float64 flag exists -func (c *Context) GlobalFloat64(name string) float64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupFloat64(name, fs) - } - return float64(0) -} - -// GlobalDuration looks up the value of a global time.Duration flag, returns 0 -// if no time.Duration flag exists -func (c *Context) GlobalDuration(name string) time.Duration { - if fs := lookupGlobalFlagSet(name, c); fs != nil { +// Duration looks up the value of a local time.Duration flag, returns 0 if no +// time.Duration flag exists +func (c *Context) Duration(name string) time.Duration { + if fs := lookupFlagSet(name, c); fs != nil { return lookupDuration(name, fs) } return 0 } -// GlobalBool looks up the value of a global bool flag, returns false if no bool +// Float64 looks up the value of a local float64 flag, returns 0 if no float64 // flag exists -func (c *Context) GlobalBool(name string) bool { - if fs := lookupGlobalFlagSet(name, c); fs != nil { +func (c *Context) Float64(name string) float64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64(name, fs) + } + return 0 +} + +// Bool looks up the value of a local bool flag, returns false if no bool flag exists +func (c *Context) Bool(name string) bool { + if fs := lookupFlagSet(name, c); fs != nil { return lookupBool(name, fs) } return false } -// GlobalBoolT looks up the value of a global bool flag, returns true if no bool -// flag exists -func (c *Context) GlobalBoolT(name string) bool { - if fs := lookupGlobalFlagSet(name, c); fs != nil { +// BoolT looks up the value of a local boolT flag, returns false if no bool flag exists +func (c *Context) BoolT(name string) bool { + if fs := lookupFlagSet(name, c); fs != nil { return lookupBoolT(name, fs) } - return false + return true } -// GlobalString looks up the value of a global string flag, returns "" if no -// string flag exists -func (c *Context) GlobalString(name string) string { - if fs := lookupGlobalFlagSet(name, c); fs != nil { +// String looks up the value of a local string flag, returns "" if no string flag exists +func (c *Context) String(name string) string { + if fs := lookupFlagSet(name, c); fs != nil { return lookupString(name, fs) } return "" } -// GlobalStringSlice looks up the value of a global string slice flag, returns -// nil if no string slice flag exists -func (c *Context) GlobalStringSlice(name string) []string { - if fs := lookupGlobalFlagSet(name, c); fs != nil { +// StringSlice looks up the value of a local string slice flag, returns nil if no +// string slice flag exists +func (c *Context) StringSlice(name string) []string { + if fs := lookupFlagSet(name, c); fs != nil { return lookupStringSlice(name, fs) } return nil } -// GlobalIntSlice looks up the value of a global int slice flag, returns nil if -// no int slice flag exists -func (c *Context) GlobalIntSlice(name string) []int { - if fs := lookupGlobalFlagSet(name, c); fs != nil { +// IntSlice looks up the value of a local int slice flag, returns nil if no int +// slice flag exists +func (c *Context) IntSlice(name string) []int { + if fs := lookupFlagSet(name, c); fs != nil { return lookupIntSlice(name, fs) } return nil } -// GlobalGeneric looks up the value of a global generic flag, returns nil if no -// generic flag exists -func (c *Context) GlobalGeneric(name string) interface{} { - if fs := lookupGlobalFlagSet(name, c); fs != nil { +// Generic looks up the value of a local generic flag, returns nil if no generic +// flag exists +func (c *Context) Generic(name string) interface{} { + if fs := lookupFlagSet(name, c); fs != nil { return lookupGeneric(name, fs) } return nil @@ -166,61 +113,37 @@ func (c *Context) Set(name, value string) error { return c.flagSet.Set(name, value) } -// GlobalSet sets a context flag to a value on the global flagset -func (c *Context) GlobalSet(name, value string) error { - return globalContext(c).flagSet.Set(name, value) -} - // IsSet determines if the flag was actually set func (c *Context) IsSet(name string) bool { - if c.setFlags == nil { - c.setFlags = make(map[string]bool) + if fs := lookupFlagSet(name, c); fs != nil { + isSet := false c.flagSet.Visit(func(f *flag.Flag) { - c.setFlags[f.Name] = true + if f.Name == name { + isSet = true + } }) + return isSet } - return c.setFlags[name] == true + return false } -// GlobalIsSet determines if the global flag was actually set -func (c *Context) GlobalIsSet(name string) bool { - if c.globalSetFlags == nil { - c.globalSetFlags = make(map[string]bool) - ctx := c - if ctx.parentContext != nil { - ctx = ctx.parentContext - } - for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { - ctx.flagSet.Visit(func(f *flag.Flag) { - c.globalSetFlags[f.Name] = true - }) - } - } - return c.globalSetFlags[name] +// LocalFlagNames returns a slice of flag names used in this context. +func (c *Context) LocalFlagNames() []string { + names := []string{} + c.flagSet.Visit(makeFlagNameVisitor(&names)) + return names } -// FlagNames returns a slice of flag names used in this context. -func (c *Context) FlagNames() (names []string) { - for _, flag := range c.Command.Flags { - name := strings.Split(flag.GetName(), ",")[0] - if name == "help" { - continue - } - names = append(names, name) - } - return -} +// FlagNames returns a slice of flag names used by the this context and all of +// its parent contexts. +func (c *Context) FlagNames() []string { + names := []string{} -// GlobalFlagNames returns a slice of global flag names used by the app. -func (c *Context) GlobalFlagNames() (names []string) { - for _, flag := range c.App.Flags { - name := strings.Split(flag.GetName(), ",")[0] - if name == "help" || name == "version" { - continue - } - names = append(names, name) + for _, ctx := range c.Lineage() { + ctx.flagSet.Visit(makeFlagNameVisitor(&names)) } - return + + return names } // Parent returns the parent context, if any @@ -228,6 +151,25 @@ func (c *Context) Parent() *Context { return c.parentContext } +// Lineage returns *this* context and all of its ancestor contexts in order from +// child to parent +func (c *Context) Lineage() []*Context { + lineage := []*Context{} + cur := c + + for { + lineage = append(lineage, cur) + + if cur.parentContext == nil { + break + } + + cur = cur.parentContext + } + + return lineage +} + // Args contains apps console arguments type Args []string @@ -291,15 +233,13 @@ func globalContext(ctx *Context) *Context { } } -func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { - if ctx.parentContext != nil { - ctx = ctx.parentContext - } - for ; ctx != nil; ctx = ctx.parentContext { - if f := ctx.flagSet.Lookup(name); f != nil { - return ctx.flagSet +func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { + for _, c := range ctx.Lineage() { + if f := c.flagSet.Lookup(name); f != nil { + return c.flagSet } } + return nil } @@ -401,7 +341,7 @@ func lookupBoolT(name string, set *flag.FlagSet) bool { return val } - return false + return true } func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { @@ -445,3 +385,12 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { } return nil } + +func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { + return func(f *flag.Flag) { + name := strings.Split(f.Name, ",")[0] + if name != "help" && name != "version" { + (*names) = append(*names, name) + } + } +} diff --git a/context_test.go b/context_test.go index 28d4884..e819dbe 100644 --- a/context_test.go +++ b/context_test.go @@ -2,6 +2,7 @@ package cli import ( "flag" + "sort" "testing" "time" ) @@ -19,8 +20,6 @@ func TestNewContext(t *testing.T) { c.Command = command expect(t, c.Int("myflag"), 12) expect(t, c.Float64("myflag64"), float64(17)) - expect(t, c.GlobalInt("myflag"), 42) - expect(t, c.GlobalFloat64("myflag64"), float64(47)) expect(t, c.Command.Name, "mycommand") } @@ -31,14 +30,6 @@ func TestContext_Int(t *testing.T) { expect(t, c.Int("myflag"), 12) } -func TestContext_GlobalInt(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Int("myflag", 12, "doc") - c := NewContext(nil, set, nil) - expect(t, c.GlobalInt("myflag"), 12) - expect(t, c.GlobalInt("nope"), 0) -} - func TestContext_Float64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Float64("myflag", float64(17), "doc") @@ -46,14 +37,6 @@ func TestContext_Float64(t *testing.T) { expect(t, c.Float64("myflag"), float64(17)) } -func TestContext_GlobalFloat64(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Float64("myflag", float64(17), "doc") - c := NewContext(nil, set, nil) - expect(t, c.GlobalFloat64("myflag"), float64(17)) - expect(t, c.GlobalFloat64("nope"), float64(0)) -} - func TestContext_Duration(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Duration("myflag", time.Duration(12*time.Second), "doc") @@ -82,30 +65,6 @@ func TestContext_BoolT(t *testing.T) { expect(t, c.BoolT("myflag"), true) } -func TestContext_GlobalBool(t *testing.T) { - set := flag.NewFlagSet("test", 0) - - globalSet := flag.NewFlagSet("test-global", 0) - globalSet.Bool("myflag", false, "doc") - globalCtx := NewContext(nil, globalSet, nil) - - c := NewContext(nil, set, globalCtx) - expect(t, c.GlobalBool("myflag"), false) - expect(t, c.GlobalBool("nope"), false) -} - -func TestContext_GlobalBoolT(t *testing.T) { - set := flag.NewFlagSet("test", 0) - - globalSet := flag.NewFlagSet("test-global", 0) - globalSet.Bool("myflag", true, "doc") - globalCtx := NewContext(nil, globalSet, nil) - - c := NewContext(nil, set, globalCtx) - expect(t, c.GlobalBoolT("myflag"), true) - expect(t, c.GlobalBoolT("nope"), false) -} - func TestContext_Args(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") @@ -139,25 +98,6 @@ func TestContext_IsSet(t *testing.T) { expect(t, c.IsSet("myflagGlobal"), false) } -func TestContext_GlobalIsSet(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Bool("myflag", false, "doc") - set.String("otherflag", "hello world", "doc") - globalSet := flag.NewFlagSet("test", 0) - globalSet.Bool("myflagGlobal", true, "doc") - globalSet.Bool("myflagGlobalUnset", true, "doc") - globalCtx := NewContext(nil, globalSet, nil) - c := NewContext(nil, set, globalCtx) - set.Parse([]string{"--myflag", "bat", "baz"}) - globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) - expect(t, c.GlobalIsSet("myflag"), false) - expect(t, c.GlobalIsSet("otherflag"), false) - expect(t, c.GlobalIsSet("bogusflag"), false) - expect(t, c.GlobalIsSet("myflagGlobal"), true) - expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) - expect(t, c.GlobalIsSet("bogusGlobal"), false) -} - func TestContext_NumFlags(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") @@ -171,62 +111,6 @@ func TestContext_NumFlags(t *testing.T) { expect(t, c.NumFlags(), 2) } -func TestContext_GlobalFlag(t *testing.T) { - var globalFlag string - var globalFlagSet bool - app := NewApp() - app.Flags = []Flag{ - StringFlag{Name: "global, g", Usage: "global"}, - } - app.Action = func(c *Context) error { - globalFlag = c.GlobalString("global") - globalFlagSet = c.GlobalIsSet("global") - return nil - } - app.Run([]string{"command", "-g", "foo"}) - expect(t, globalFlag, "foo") - expect(t, globalFlagSet, true) - -} - -func TestContext_GlobalFlagsInSubcommands(t *testing.T) { - subcommandRun := false - parentFlag := false - app := NewApp() - - app.Flags = []Flag{ - BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, - } - - app.Commands = []Command{ - { - Name: "foo", - Flags: []Flag{ - BoolFlag{Name: "parent, p", Usage: "Parent flag"}, - }, - Subcommands: []Command{ - { - Name: "bar", - Action: func(c *Context) error { - if c.GlobalBool("debug") { - subcommandRun = true - } - if c.GlobalBool("parent") { - parentFlag = true - } - return nil - }, - }, - }, - }, - } - - app.Run([]string{"command", "-d", "foo", "-p", "bar"}) - - expect(t, subcommandRun, true) - expect(t, parentFlag, true) -} - func TestContext_Set(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("int", 5, "an int") @@ -236,21 +120,36 @@ func TestContext_Set(t *testing.T) { expect(t, c.Int("int"), 1) } -func TestContext_GlobalSet(t *testing.T) { - gSet := flag.NewFlagSet("test", 0) - gSet.Int("int", 5, "an int") +func TestContext_LocalFlagNames(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("one-flag", false, "doc") + set.String("two-flag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("top-flag", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) + set.Parse([]string{"--one-flag", "--two-flag=foo"}) + globalSet.Parse([]string{"--top-flag"}) - set := flag.NewFlagSet("sub", 0) - set.Int("int", 3, "an int") + actualFlags := c.LocalFlagNames() + sort.Strings(actualFlags) - pc := NewContext(nil, gSet, nil) - c := NewContext(nil, set, pc) - - c.Set("int", "1") - expect(t, c.Int("int"), 1) - expect(t, c.GlobalInt("int"), 5) - - c.GlobalSet("int", "1") - expect(t, c.Int("int"), 1) - expect(t, c.GlobalInt("int"), 1) + expect(t, actualFlags, []string{"one-flag", "two-flag"}) +} + +func TestContext_FlagNames(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("one-flag", false, "doc") + set.String("two-flag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("top-flag", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) + set.Parse([]string{"--one-flag", "--two-flag=foo"}) + globalSet.Parse([]string{"--top-flag"}) + + actualFlags := c.FlagNames() + sort.Strings(actualFlags) + + expect(t, actualFlags, []string{"one-flag", "top-flag", "two-flag"}) } diff --git a/flag_test.go b/flag_test.go index f914da3..f163468 100644 --- a/flag_test.go +++ b/flag_test.go @@ -391,7 +391,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { expected := []string{"10", "20"} if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) { t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) @@ -399,6 +399,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { if !reflect.DeepEqual(ctx.StringSlice("s"), expected) { t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) } + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -408,13 +409,14 @@ func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { t.Errorf("main name not set: %v", ctx.StringSlice("serve")) } if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"9", "2"}) { t.Errorf("short name not set: %v", ctx.StringSlice("s")) } + return nil }, }).Run([]string{"run"}) } @@ -427,13 +429,14 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } @@ -466,13 +469,14 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } @@ -596,13 +600,14 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -612,13 +617,14 @@ func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.IntSlice("s"), []int{9, 2}) { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run"}) } @@ -631,13 +637,14 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } diff --git a/help.go b/help.go index a9e7327..893b1be 100644 --- a/help.go +++ b/help.go @@ -209,7 +209,7 @@ func checkVersion(c *Context) bool { found := false if VersionFlag.Name != "" { eachName(VersionFlag.Name, func(name string) { - if c.GlobalBool(name) || c.Bool(name) { + if c.Bool(name) { found = true } }) @@ -221,7 +221,7 @@ func checkHelp(c *Context) bool { found := false if HelpFlag.Name != "" { eachName(HelpFlag.Name, func(name string) { - if c.GlobalBool(name) || c.Bool(name) { + if c.Bool(name) { found = true } }) @@ -239,7 +239,7 @@ func checkCommandHelp(c *Context, name string) bool { } func checkSubcommandHelp(c *Context) bool { - if c.GlobalBool("h") || c.GlobalBool("help") { + if c.Bool("h") || c.Bool("help") { ShowSubcommandHelp(c) return true } @@ -248,7 +248,7 @@ func checkSubcommandHelp(c *Context) bool { } func checkCompletions(c *Context) bool { - if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { + if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { ShowCompletions(c) return true } From c720f37e15082fbc5d89d0b7f836db0bc15c78d1 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 17 May 2016 03:16:26 -0400 Subject: [PATCH 009/158] Context tests and tweaks around lineage traversal per feedback on #410 --- context.go | 40 ++++------------------ context_test.go | 89 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/context.go b/context.go index af80bdb..002fe6e 100644 --- a/context.go +++ b/context.go @@ -13,12 +13,11 @@ import ( // can be used to retrieve context-specific Args and // parsed command-line options. type Context struct { - App *App - Command Command - flagSet *flag.FlagSet - setFlags map[string]bool - globalSetFlags map[string]bool - parentContext *Context + App *App + Command Command + + flagSet *flag.FlagSet + parentContext *Context } // NewContext creates a new context. For use in when invoking an App or Command action. @@ -117,7 +116,7 @@ func (c *Context) Set(name, value string) error { func (c *Context) IsSet(name string) bool { if fs := lookupFlagSet(name, c); fs != nil { isSet := false - c.flagSet.Visit(func(f *flag.Flag) { + fs.Visit(func(f *flag.Flag) { if f.Name == name { isSet = true } @@ -146,25 +145,13 @@ func (c *Context) FlagNames() []string { return names } -// Parent returns the parent context, if any -func (c *Context) Parent() *Context { - return c.parentContext -} - // Lineage returns *this* context and all of its ancestor contexts in order from // child to parent func (c *Context) Lineage() []*Context { lineage := []*Context{} - cur := c - for { + for cur := c; cur != nil; cur = cur.parentContext { lineage = append(lineage, cur) - - if cur.parentContext == nil { - break - } - - cur = cur.parentContext } return lineage @@ -220,19 +207,6 @@ func (a Args) Swap(from, to int) error { return nil } -func globalContext(ctx *Context) *Context { - if ctx == nil { - return nil - } - - for { - if ctx.parentContext == nil { - return ctx - } - ctx = ctx.parentContext - } -} - func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { for _, c := range ctx.Lineage() { if f := c.flagSet.Lookup(name); f != nil { diff --git a/context_test.go b/context_test.go index e819dbe..bdb77fd 100644 --- a/context_test.go +++ b/context_test.go @@ -84,18 +84,22 @@ func TestContext_NArg(t *testing.T) { func TestContext_IsSet(t *testing.T) { set := flag.NewFlagSet("test", 0) - set.Bool("myflag", false, "doc") - set.String("otherflag", "hello world", "doc") - globalSet := flag.NewFlagSet("test", 0) - globalSet.Bool("myflagGlobal", true, "doc") - globalCtx := NewContext(nil, globalSet, nil) - c := NewContext(nil, set, globalCtx) - set.Parse([]string{"--myflag", "bat", "baz"}) - globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) - expect(t, c.IsSet("myflag"), true) - expect(t, c.IsSet("otherflag"), false) - expect(t, c.IsSet("bogusflag"), false) - expect(t, c.IsSet("myflagGlobal"), false) + set.Bool("one-flag", false, "doc") + set.Bool("two-flag", false, "doc") + set.String("three-flag", "hello world", "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + ctx := NewContext(nil, set, parentCtx) + + set.Parse([]string{"--one-flag", "--two-flag", "--three-flag", "frob"}) + parentSet.Parse([]string{"--top-flag"}) + + expect(t, ctx.IsSet("one-flag"), true) + expect(t, ctx.IsSet("two-flag"), true) + expect(t, ctx.IsSet("three-flag"), true) + expect(t, ctx.IsSet("top-flag"), true) + expect(t, ctx.IsSet("bogus"), false) } func TestContext_NumFlags(t *testing.T) { @@ -124,14 +128,14 @@ func TestContext_LocalFlagNames(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("one-flag", false, "doc") set.String("two-flag", "hello world", "doc") - globalSet := flag.NewFlagSet("test", 0) - globalSet.Bool("top-flag", true, "doc") - globalCtx := NewContext(nil, globalSet, nil) - c := NewContext(nil, set, globalCtx) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + ctx := NewContext(nil, set, parentCtx) set.Parse([]string{"--one-flag", "--two-flag=foo"}) - globalSet.Parse([]string{"--top-flag"}) + parentSet.Parse([]string{"--top-flag"}) - actualFlags := c.LocalFlagNames() + actualFlags := ctx.LocalFlagNames() sort.Strings(actualFlags) expect(t, actualFlags, []string{"one-flag", "two-flag"}) @@ -141,15 +145,52 @@ func TestContext_FlagNames(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("one-flag", false, "doc") set.String("two-flag", "hello world", "doc") - globalSet := flag.NewFlagSet("test", 0) - globalSet.Bool("top-flag", true, "doc") - globalCtx := NewContext(nil, globalSet, nil) - c := NewContext(nil, set, globalCtx) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + ctx := NewContext(nil, set, parentCtx) set.Parse([]string{"--one-flag", "--two-flag=foo"}) - globalSet.Parse([]string{"--top-flag"}) + parentSet.Parse([]string{"--top-flag"}) - actualFlags := c.FlagNames() + actualFlags := ctx.FlagNames() sort.Strings(actualFlags) expect(t, actualFlags, []string{"one-flag", "top-flag", "two-flag"}) } + +func TestContext_Lineage(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("local-flag", false, "doc") + parentSet := flag.NewFlagSet("test", 0) + 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"}) + + lineage := ctx.Lineage() + expect(t, len(lineage), 2) + expect(t, lineage[0], ctx) + expect(t, lineage[1], parentCtx) +} + +func TestContext_lookupFlagSet(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("local-flag", false, "doc") + parentSet := flag.NewFlagSet("test", 0) + 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"}) + + fs := lookupFlagSet("top-flag", ctx) + expect(t, fs, parentCtx.flagSet) + + fs = lookupFlagSet("local-flag", ctx) + expect(t, fs, ctx.flagSet) + + if fs := lookupFlagSet("frob", ctx); fs != nil { + t.Fail() + } +} From 7318e27528b3be6a024e85c16d1776342ac40d70 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 17 May 2016 03:46:16 -0400 Subject: [PATCH 010/158] Tidy up the flag name visitor func in Context --- context.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index 002fe6e..03c61b2 100644 --- a/context.go +++ b/context.go @@ -137,11 +137,9 @@ func (c *Context) LocalFlagNames() []string { // its parent contexts. func (c *Context) FlagNames() []string { names := []string{} - for _, ctx := range c.Lineage() { ctx.flagSet.Visit(makeFlagNameVisitor(&names)) } - return names } @@ -362,8 +360,17 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { return func(f *flag.Flag) { - name := strings.Split(f.Name, ",")[0] - if name != "help" && name != "version" { + nameParts := strings.Split(f.Name, ",") + name := strings.TrimSpace(nameParts[0]) + + for _, part := range nameParts { + part = strings.TrimSpace(part) + if len(part) > len(name) { + name = part + } + } + + if name != "" { (*names) = append(*names, name) } } From c19dbaf230a53e86ed7ad8c93641336cabd793e9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 17 May 2016 15:11:44 -0400 Subject: [PATCH 011/158] Remove deprecated things and supporting code --- CHANGELOG.md | 2 ++ app.go | 84 ++-------------------------------------------------- app_test.go | 76 +---------------------------------------------- command.go | 18 ++--------- flag_test.go | 21 ++++++++----- help_test.go | 4 +-- 6 files changed, 25 insertions(+), 180 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d089c0..f73065c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ arguments](https://github.com/codegangsta/cli/issues/355) when the user attempted to mix flags and arguments. Given the trade-offs we removed support for this reordering. +- adapter code for deprecated `Action` func signature +- deprecated `App.Author`, `App.Email`, and `Command.ShortName` fields ## [Unreleased] - (1.x series) ### Added diff --git a/app.go b/app.go index 7c9b958..066ea7a 100644 --- a/app.go +++ b/app.go @@ -6,26 +6,10 @@ import ( "io/ioutil" "os" "path/filepath" - "reflect" "sort" "time" ) -var ( - changeLogURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md" - appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) - runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) - - contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." - - errNonFuncAction = NewExitError("ERROR invalid Action type. "+ - fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+ - fmt.Sprintf("See %s", appActionDeprecationURL), 2) - errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+ - fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+ - fmt.Sprintf("See %s", appActionDeprecationURL), 2) -) - // App is the main structure of a cli application. It is recommended that // an app be created with the cli.NewApp() function type App struct { @@ -62,10 +46,7 @@ type App struct { // It is run even if Action() panics After AfterFunc // The action to execute when no subcommands are specified - Action interface{} - // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind - // of deprecation period has passed, maybe? - + Action ActionFunc // Execute this function if the proper command cannot be found CommandNotFound CommandNotFoundFunc // Execute this function if an usage error occurs @@ -76,10 +57,6 @@ type App struct { Authors []Author // Copyright of the binary if any Copyright string - // Name of Author (Note: Use App.Authors, this is deprecated) - Author string - // Email of Author (Note: Use App.Authors, this is deprecated) - Email string // Writer writer to write output to Writer io.Writer // ErrWriter writes error output @@ -126,10 +103,6 @@ func (a *App) Setup() { a.didSetup = true - if a.Author != "" || a.Email != "" { - a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) - } - newCmds := []Command{} for _, c := range a.Commands { if c.HelpName == "" { @@ -238,23 +211,12 @@ func (a *App) Run(arguments []string) (err error) { } // Run default Action - err = HandleAction(a.Action, context) + err = a.Action(context) HandleExitCoder(err) return err } -// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling -func (a *App) RunAndExitOnError() { - fmt.Fprintf(a.errWriter(), - "DEPRECATED cli.App.RunAndExitOnError. %s See %s\n", - contactSysadmin, runAndExitOnErrorDeprecationURL) - if err := a.Run(os.Args); err != nil { - fmt.Fprintln(a.errWriter(), err) - OsExiter(1) - } -} - // RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to // generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { @@ -358,7 +320,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } // Run default Action - err = HandleAction(a.Action, context) + err = a.Action(context) HandleExitCoder(err) return err @@ -456,43 +418,3 @@ func (a Author) String() string { return fmt.Sprintf("%v %v", a.Name, e) } - -// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an -// ActionFunc, a func with the legacy signature for Action, or some other -// invalid thing. If it's an ActionFunc or a func with the legacy signature for -// Action, the func is run! -func HandleAction(action interface{}, context *Context) (err error) { - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case error: - err = r.(error) - default: - err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2) - } - } - }() - - if reflect.TypeOf(action).Kind() != reflect.Func { - return errNonFuncAction - } - - vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)}) - - if len(vals) == 0 { - fmt.Fprintf(ErrWriter, - "DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n", - contactSysadmin, appActionDeprecationURL) - return nil - } - - if len(vals) > 1 { - return errInvalidActionSignature - } - - if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok { - return retErr - } - - return err -} diff --git a/app_test.go b/app_test.go index 5b9d9f3..8d81a5d 100644 --- a/app_test.go +++ b/app_test.go @@ -31,9 +31,7 @@ func ExampleApp_Run() { return nil } app.UsageText = "app [first_arg] [second_arg]" - app.Author = "Harrison" - app.Email = "harrison@lolwut.com" - app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} app.Run(os.Args) // Output: // Hello Jeremy @@ -1357,75 +1355,3 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { t.Errorf("Expect an intercepted error, but got \"%v\"", err) } } - -func TestHandleAction_WithNonFuncAction(t *testing.T) { - app := NewApp() - app.Action = 42 - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { - t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { - app := NewApp() - app.Action = func() string { return "" } - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR unknown Action error") { - t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { - app := NewApp() - app.Action = func(_ *Context) (int, error) { return 0, nil } - err := HandleAction(app.Action, NewContext(app, flagSet(app.Name, app.Flags), nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action signature") { - t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} diff --git a/command.go b/command.go index 376e222..7a77953 100644 --- a/command.go +++ b/command.go @@ -11,8 +11,6 @@ import ( type Command struct { // The name of the command Name string - // short name of the command. Typically one character (deprecated, use `Aliases`) - ShortName string // A list of aliases for the command Aliases []string // A short description of the usage of this command @@ -34,10 +32,7 @@ type Command struct { // It is run even if Action() panics After AfterFunc // The function to call when this command is invoked - Action interface{} - // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind - // of deprecation period has passed, maybe? - + Action ActionFunc // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc // List of child commands @@ -151,7 +146,7 @@ func (c Command) Run(ctx *Context) (err error) { } context.Command = c - err = HandleAction(c.Action, context) + err = c.Action(context) if err != nil { HandleExitCoder(err) @@ -162,15 +157,10 @@ func (c Command) Run(ctx *Context) (err error) { // Names returns the names including short names and aliases. func (c Command) Names() []string { names := []string{c.Name} - - if c.ShortName != "" { - names = append(names, c.ShortName) - } - return append(names, c.Aliases...) } -// HasName returns true if Command.Name or Command.ShortName matches given name +// HasName returns true if Command.Name matches given name func (c Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { @@ -208,8 +198,6 @@ func (c Command) startApp(ctx *Context) error { app.Version = ctx.App.Version app.HideVersion = ctx.App.HideVersion app.Compiled = ctx.App.Compiled - app.Author = ctx.App.Author - app.Email = ctx.App.Email app.Writer = ctx.App.Writer app.categories = CommandCategories{} diff --git a/flag_test.go b/flag_test.go index f914da3..f163468 100644 --- a/flag_test.go +++ b/flag_test.go @@ -391,7 +391,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { expected := []string{"10", "20"} if !reflect.DeepEqual(ctx.StringSlice("serve"), expected) { t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) @@ -399,6 +399,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { if !reflect.DeepEqual(ctx.StringSlice("s"), expected) { t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) } + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -408,13 +409,14 @@ func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { t.Errorf("main name not set: %v", ctx.StringSlice("serve")) } if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"9", "2"}) { t.Errorf("short name not set: %v", ctx.StringSlice("s")) } + return nil }, }).Run([]string{"run"}) } @@ -427,13 +429,14 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } @@ -466,13 +469,14 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { Flags: []Flag{ StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } @@ -596,13 +600,14 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run", "-s", "10", "-s", "20"}) } @@ -612,13 +617,14 @@ func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { t.Errorf("main name not set") } if !reflect.DeepEqual(ctx.IntSlice("s"), []int{9, 2}) { t.Errorf("short name not set") } + return nil }, }).Run([]string{"run"}) } @@ -631,13 +637,14 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { Flags: []Flag{ IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "APP_INTERVALS"}, }, - Action: func(ctx *Context) { + Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { t.Errorf("main name not set from env") } if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { t.Errorf("short name not set from env") } + return nil }, }).Run([]string{"run"}) } diff --git a/help_test.go b/help_test.go index a664865..d19bc4c 100644 --- a/help_test.go +++ b/help_test.go @@ -121,7 +121,7 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { c := NewContext(app, set, nil) - err := helpCommand.Action.(func(*Context) error)(c) + err := helpCommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil") @@ -149,7 +149,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { c := NewContext(app, set, nil) - err := helpSubcommand.Action.(func(*Context) error)(c) + err := helpSubcommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil") From a02aab6b4bd3faa000dd633de3862090fcc19e0e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 17 May 2016 20:27:42 -0400 Subject: [PATCH 012/158] Add note about removal of `Context.Parent` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3470ccd..1c94a17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - deprecated `App.Author`, `App.Email`, and `Command.ShortName` fields - All `Context.Global*` methods, as the non-global versions now traverse up the context lineage automatically. +- `Context.Parent` method, as this is now available via `Context.Lineage` ### Fixed - `Context.BoolT` now returns `true` when not found From f2d5ed9933b417a4c4d5781c6aa4c1a2e73b6bbb Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2016 08:20:15 -0400 Subject: [PATCH 013/158] Replace BoolTFlag type with BoolFlag.Value Closes #412 --- CHANGELOG.md | 5 +-- README.md | 3 +- altsrc/flag.go | 38 ----------------- altsrc/flag_test.go | 30 ------------- altsrc/input_source_context.go | 1 - altsrc/map_input_source.go | 22 ---------- context.go | 21 ---------- context_test.go | 7 ---- flag.go | 55 +++--------------------- flag_test.go | 77 ---------------------------------- 10 files changed, 9 insertions(+), 250 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c94a17..a0c20a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - `NewStringSlice` and `NewIntSlice` for creating their related types - `Context.Lineage` to get all contexts from current up to global - `Context.LocalFlagNames` to get the flag names from *only* the current context +- `BoolFlag.Value` to handle both default-false and default-true ### Changed - `Context.FlagNames` now returns all flags in the context lineage @@ -28,9 +29,7 @@ - All `Context.Global*` methods, as the non-global versions now traverse up the context lineage automatically. - `Context.Parent` method, as this is now available via `Context.Lineage` - -### Fixed -- `Context.BoolT` now returns `true` when not found +- `BoolTFlag` and related code, as this is now available via `BoolFlag.Value` ## [Unreleased] - (1.x series) ### Added diff --git a/README.md b/README.md index a5bab32..fb218dc 100644 --- a/README.md +++ b/README.md @@ -472,8 +472,9 @@ import ( func main() { app := cli.NewApp() app.Flags = []cli.Flag{ - cli.BoolTFlag{ + cli.BoolFlag{ Name: "ginger-crouton", + Value: true, Usage: "is it in the soup?", }, } diff --git a/altsrc/flag.go b/altsrc/flag.go index d934e6a..aea142e 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -220,44 +220,6 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) { f.BoolFlag.Apply(set) } -// BoolTFlag is the flag type that wraps cli.BoolTFlag to allow -// for other values to be specified -type BoolTFlag struct { - cli.BoolTFlag - set *flag.FlagSet -} - -// NewBoolTFlag creates a new BoolTFlag -func NewBoolTFlag(flag cli.BoolTFlag) *BoolTFlag { - return &BoolTFlag{BoolTFlag: flag, set: nil} -} - -// ApplyInputSourceValue applies a BoolT value to the flagSet if required -func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { - value, err := isc.BoolT(f.BoolTFlag.Name) - if err != nil { - return err - } - if !value { - eachName(f.Name, func(name string) { - f.set.Set(f.Name, strconv.FormatBool(value)) - }) - } - } - } - return nil -} - -// Apply saves the flagSet for later usage then calls -// the wrapped BoolTFlag.Apply -func (f *BoolTFlag) Apply(set *flag.FlagSet) { - f.set = set - - f.BoolTFlag.Apply(set) -} - // StringFlag is the flag type that wraps cli.StringFlag to allow // for other values to be specified type StringFlag struct { diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 4e25be6..be7166f 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -145,36 +145,6 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { expect(t, true, c.Bool("test")) } -func TestBoolTApplyInputSourceMethodSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ - Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}), - FlagName: "test", - MapValue: false, - }) - expect(t, false, c.BoolT("test")) -} - -func TestBoolTApplyInputSourceMethodContextSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ - Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}), - FlagName: "test", - MapValue: true, - ContextValueString: "false", - }) - expect(t, false, c.BoolT("test")) -} - -func TestBoolTApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ - Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test", EnvVar: "TEST"}), - FlagName: "test", - MapValue: true, - EnvVarName: "TEST", - EnvVarValue: "false", - }) - expect(t, false, c.BoolT("test")) -} - func TestStringApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewStringFlag(cli.StringFlag{Name: "test"}), diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 6d695ff..0f391b2 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -17,5 +17,4 @@ type InputSourceContext interface { IntSlice(name string) ([]int, error) Generic(name string) (cli.Generic, error) Bool(name string) (bool, error) - BoolT(name string) (bool, error) } diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 19f87af..a7fc628 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -215,28 +215,6 @@ func (fsm *MapInputSource) Bool(name string) (bool, error) { return false, nil } -// BoolT returns an bool from the map otherwise returns true -func (fsm *MapInputSource) BoolT(name string) (bool, error) { - otherGenericValue, exists := fsm.valueMap[name] - if exists { - otherValue, isType := otherGenericValue.(bool) - if !isType { - return true, incorrectTypeForFlagError(name, "bool", otherGenericValue) - } - return otherValue, nil - } - nestedGenericValue, exists := nestedVal(name, fsm.valueMap) - if exists { - otherValue, isType := nestedGenericValue.(bool) - if !isType { - return true, incorrectTypeForFlagError(name, "bool", nestedGenericValue) - } - return otherValue, nil - } - - return true, nil -} - func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error { valueType := reflect.TypeOf(value) valueTypeName := "" diff --git a/context.go b/context.go index 03c61b2..45face1 100644 --- a/context.go +++ b/context.go @@ -59,14 +59,6 @@ func (c *Context) Bool(name string) bool { return false } -// BoolT looks up the value of a local boolT flag, returns false if no bool flag exists -func (c *Context) BoolT(name string) bool { - if fs := lookupFlagSet(name, c); fs != nil { - return lookupBoolT(name, fs) - } - return true -} - // String looks up the value of a local string flag, returns "" if no string flag exists func (c *Context) String(name string) string { if fs := lookupFlagSet(name, c); fs != nil { @@ -303,19 +295,6 @@ func lookupBool(name string, set *flag.FlagSet) bool { return false } -func lookupBoolT(name string, set *flag.FlagSet) bool { - f := set.Lookup(name) - if f != nil { - val, err := strconv.ParseBool(f.Value.String()) - if err != nil { - return true - } - return val - } - - return true -} - func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { switch ff.Value.(type) { case Serializeder: diff --git a/context_test.go b/context_test.go index bdb77fd..8147965 100644 --- a/context_test.go +++ b/context_test.go @@ -58,13 +58,6 @@ func TestContext_Bool(t *testing.T) { expect(t, c.Bool("myflag"), false) } -func TestContext_BoolT(t *testing.T) { - set := flag.NewFlagSet("test", 0) - set.Bool("myflag", true, "doc") - c := NewContext(nil, set, nil) - expect(t, c.BoolT("myflag"), true) -} - func TestContext_Args(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") diff --git a/flag.go b/flag.go index 5694142..97e502c 100644 --- a/flag.go +++ b/flag.go @@ -321,6 +321,7 @@ func (f IntSliceFlag) GetName() string { // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string + Value bool Usage string EnvVar string Destination *bool @@ -334,14 +335,13 @@ func (f BoolFlag) String() string { // Apply populates the flag given the flag set and environment func (f BoolFlag) Apply(set *flag.FlagSet) { - val := false if f.EnvVar != "" { for _, envVar := range strings.Split(f.EnvVar, ",") { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValBool, err := strconv.ParseBool(envVal) if err == nil { - val = envValBool + f.Value = envValBool } break } @@ -350,10 +350,10 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { eachName(f.Name, func(name string) { if f.Destination != nil { - set.BoolVar(f.Destination, name, val, f.Usage) + set.BoolVar(f.Destination, name, f.Value, f.Usage) return } - set.Bool(name, val, f.Usage) + set.Bool(name, f.Value, f.Usage) }) } @@ -362,51 +362,6 @@ func (f BoolFlag) GetName() string { return f.Name } -// BoolTFlag this represents a boolean flag that is true by default, but can -// still be set to false by --some-flag=false -type BoolTFlag struct { - Name string - Usage string - EnvVar string - Destination *bool - Hidden bool -} - -// String returns a readable representation of this value (for usage defaults) -func (f BoolTFlag) String() string { - return FlagStringer(f) -} - -// Apply populates the flag given the flag set and environment -func (f BoolTFlag) Apply(set *flag.FlagSet) { - val := true - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { - envValBool, err := strconv.ParseBool(envVal) - if err == nil { - val = envValBool - break - } - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.BoolVar(f.Destination, name, val, f.Usage) - return - } - set.Bool(name, val, f.Usage) - }) -} - -// GetName returns the name of the flag. -func (f BoolTFlag) GetName() string { - return f.Name -} - // StringFlag represents a flag that takes as string value type StringFlag struct { Name string @@ -669,7 +624,7 @@ func stringifyFlag(f Flag) string { defaultValueString := "" val := fv.FieldByName("Value") - if val.IsValid() { + if val.IsValid() && val.Kind() != reflect.Bool { needsPlaceholder = true defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) diff --git a/flag_test.go b/flag_test.go index f163468..3cf6833 100644 --- a/flag_test.go +++ b/flag_test.go @@ -843,83 +843,6 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { a.Run([]string{"run"}) } -func TestParseMultiBoolT(t *testing.T) { - a := App{ - Flags: []Flag{ - BoolTFlag{Name: "serve, s"}, - }, - Action: func(ctx *Context) error { - if ctx.BoolT("serve") != true { - t.Errorf("main name not set") - } - if ctx.BoolT("s") != true { - t.Errorf("short name not set") - } - return nil - }, - } - a.Run([]string{"run", "--serve"}) -} - -func TestParseDestinationBoolT(t *testing.T) { - var dest bool - a := App{ - Flags: []Flag{ - BoolTFlag{ - Name: "dest", - Destination: &dest, - }, - }, - Action: func(ctx *Context) error { - if dest != true { - t.Errorf("expected destination BoolT true") - } - return nil - }, - } - a.Run([]string{"run", "--dest"}) -} - -func TestParseMultiBoolTFromEnv(t *testing.T) { - os.Clearenv() - os.Setenv("APP_DEBUG", "0") - a := App{ - Flags: []Flag{ - BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, - }, - Action: func(ctx *Context) error { - if ctx.BoolT("debug") != false { - t.Errorf("main name not set from env") - } - if ctx.BoolT("d") != false { - t.Errorf("short name not set from env") - } - return nil - }, - } - a.Run([]string{"run"}) -} - -func TestParseMultiBoolTFromEnvCascade(t *testing.T) { - os.Clearenv() - os.Setenv("APP_DEBUG", "0") - a := App{ - Flags: []Flag{ - BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, - }, - Action: func(ctx *Context) error { - if ctx.BoolT("debug") != false { - t.Errorf("main name not set from env") - } - if ctx.BoolT("d") != false { - t.Errorf("short name not set from env") - } - return nil - }, - } - a.Run([]string{"run"}) -} - type Parser [2]string func (p *Parser) Set(value string) error { From 9c132d990a17351e6274ae24eab7e07752305194 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2016 08:30:28 -0400 Subject: [PATCH 014/158] Add more tests for BoolFlag{Value: true} --- flag_test.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/flag_test.go b/flag_test.go index 3cf6833..a3414cf 100644 --- a/flag_test.go +++ b/flag_test.go @@ -843,6 +843,93 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { a.Run([]string{"run"}) } +func TestParseMultiBoolTrue(t *testing.T) { + a := App{ + Flags: []Flag{ + BoolFlag{Name: "implode, i", Value: true}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("implode") { + t.Errorf("main name not set") + } + if ctx.Bool("i") { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run", "--implode=false"}) +} + +func TestParseDestinationBoolTrue(t *testing.T) { + dest := true + + a := App{ + Flags: []Flag{ + BoolFlag{ + Name: "dest", + Value: true, + Destination: &dest, + }, + }, + Action: func(ctx *Context) error { + if dest { + t.Errorf("expected destination Bool false") + } + return nil + }, + } + a.Run([]string{"run", "--dest=false"}) +} + +func TestParseMultiBoolTrueFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "0") + a := App{ + Flags: []Flag{ + BoolFlag{ + Name: "debug, d", + Value: true, + EnvVar: "APP_DEBUG", + }, + }, + Action: func(ctx *Context) error { + if ctx.Bool("debug") { + t.Errorf("main name not set from env") + } + if ctx.Bool("d") { + t.Errorf("short name not set from env") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "0") + a := App{ + Flags: []Flag{ + BoolFlag{ + Name: "debug, d", + Value: true, + EnvVar: "COMPAT_DEBUG,APP_DEBUG", + }, + }, + Action: func(ctx *Context) error { + if ctx.Bool("debug") { + t.Errorf("main name not set from env") + } + if ctx.Bool("d") { + t.Errorf("short name not set from env") + } + return nil + }, + } + a.Run([]string{"run"}) +} + type Parser [2]string func (p *Parser) Set(value string) error { From fac67ac91ab4928f484fa425a4f66ff0537333c2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 21 May 2016 20:02:08 -0400 Subject: [PATCH 015/158] Show BoolFlag default value in help usage now that BoolTFlag is gone --- flag.go | 4 ++-- flag_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flag.go b/flag.go index 97e502c..b5241e0 100644 --- a/flag.go +++ b/flag.go @@ -624,8 +624,8 @@ func stringifyFlag(f Flag) string { defaultValueString := "" val := fv.FieldByName("Value") - if val.IsValid() && val.Kind() != reflect.Bool { - needsPlaceholder = true + if val.IsValid() { + needsPlaceholder = val.Kind() != reflect.Bool defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) if val.Kind() == reflect.String && val.String() != "" { diff --git a/flag_test.go b/flag_test.go index a3414cf..ce786b7 100644 --- a/flag_test.go +++ b/flag_test.go @@ -14,8 +14,8 @@ var boolFlagTests = []struct { name string expected string }{ - {"help", "--help\t"}, - {"h", "-h\t"}, + {"help", "--help\t(default: false)"}, + {"h", "-h\t(default: false)"}, } func TestBoolFlagHelpOutput(t *testing.T) { From 81fcf706ea1c2bdde52d04088a9c5b0bc1a97f7a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 21 May 2016 21:29:45 -0400 Subject: [PATCH 016/158] Replace all "stringly typed" fields with slice equivalents and alter the Flag interface accordingly Closes #415 --- README.md | 34 +++-- altsrc/flag.go | 78 +++++------ altsrc/flag_test.go | 20 +-- altsrc/yaml_command_test.go | 8 +- app.go | 7 +- command.go | 3 +- context.go | 2 +- flag.go | 264 +++++++++++++++++++++--------------- flag_test.go | 132 ++++++++++-------- help.go | 8 +- help_test.go | 14 +- 11 files changed, 314 insertions(+), 256 deletions(-) diff --git a/README.md b/README.md index de73458..0a1f624 100644 --- a/README.md +++ b/README.md @@ -265,8 +265,9 @@ For example this: ```go cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", } ``` @@ -285,9 +286,10 @@ You can set alternate (or short) names for flags by providing a comma-delimited ``` go app.Flags = []cli.Flag { cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", }, } ``` @@ -296,28 +298,30 @@ That flag can then be set with `--lang spanish` or `-l spanish`. Note that givin #### Values from the Environment -You can also have the default value set from the environment via `EnvVar`. e.g. +You can also have the default value set from the environment via `EnvVars`. e.g. ``` go app.Flags = []cli.Flag { cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "APP_LANG", + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, }, } ``` -The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. +If `EnvVars` contains more than one string, the first environment variable that resolves is used as the default. ``` go app.Flags = []cli.Flag { cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, }, } ``` diff --git a/altsrc/flag.go b/altsrc/flag.go index aea142e..6c68bf5 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "strconv" - "strings" "github.com/codegangsta/cli" ) @@ -78,15 +77,15 @@ func NewGenericFlag(flag cli.GenericFlag) *GenericFlag { // ApplyInputSourceValue applies a generic value to the flagSet if required func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.Generic(f.GenericFlag.Name) if err != nil { return err } if value != nil { - eachName(f.Name, func(name string) { - f.set.Set(f.Name, value.String()) - }) + for _, name := range f.Names() { + f.set.Set(name, value.String()) + } } } } @@ -116,19 +115,19 @@ func NewStringSliceFlag(flag cli.StringSliceFlag) *StringSliceFlag { // ApplyInputSourceValue applies a StringSlice value to the flagSet if required func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.StringSlice(f.StringSliceFlag.Name) if err != nil { return err } if value != nil { var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...)) - eachName(f.Name, func(name string) { - underlyingFlag := f.set.Lookup(f.Name) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } - }) + } } } } @@ -157,19 +156,19 @@ func NewIntSliceFlag(flag cli.IntSliceFlag) *IntSliceFlag { // ApplyInputSourceValue applies a IntSlice value if required func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.IntSlice(f.IntSliceFlag.Name) if err != nil { return err } if value != nil { var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...)) - eachName(f.Name, func(name string) { - underlyingFlag := f.set.Lookup(f.Name) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } - }) + } } } } @@ -198,15 +197,15 @@ func NewBoolFlag(flag cli.BoolFlag) *BoolFlag { // ApplyInputSourceValue applies a Bool value to the flagSet if required func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.Bool(f.BoolFlag.Name) if err != nil { return err } if value { - eachName(f.Name, func(name string) { - f.set.Set(f.Name, strconv.FormatBool(value)) - }) + for _, name := range f.Names() { + f.set.Set(name, strconv.FormatBool(value)) + } } } } @@ -235,15 +234,15 @@ func NewStringFlag(flag cli.StringFlag) *StringFlag { // ApplyInputSourceValue applies a String value to the flagSet if required func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.String(f.StringFlag.Name) if err != nil { return err } if value != "" { - eachName(f.Name, func(name string) { - f.set.Set(f.Name, value) - }) + for _, name := range f.Names() { + f.set.Set(name, value) + } } } } @@ -273,15 +272,15 @@ func NewIntFlag(flag cli.IntFlag) *IntFlag { // ApplyInputSourceValue applies a int value to the flagSet if required func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Int(f.IntFlag.Name) if err != nil { return err } if value > 0 { - eachName(f.Name, func(name string) { - f.set.Set(f.Name, strconv.FormatInt(int64(value), 10)) - }) + for _, name := range f.Names() { + f.set.Set(name, strconv.FormatInt(int64(value), 10)) + } } } } @@ -310,15 +309,15 @@ func NewDurationFlag(flag cli.DurationFlag) *DurationFlag { // ApplyInputSourceValue applies a Duration value to the flagSet if required func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Duration(f.DurationFlag.Name) if err != nil { return err } if value > 0 { - eachName(f.Name, func(name string) { - f.set.Set(f.Name, value.String()) - }) + for _, name := range f.Names() { + f.set.Set(name, value.String()) + } } } } @@ -348,16 +347,16 @@ func NewFloat64Flag(flag cli.Float64Flag) *Float64Flag { // ApplyInputSourceValue applies a Float64 value to the flagSet if required func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Float64(f.Float64Flag.Name) if err != nil { return err } if value > 0 { floatStr := float64ToString(value) - eachName(f.Name, func(name string) { - f.set.Set(f.Name, floatStr) - }) + for _, name := range f.Names() { + f.set.Set(name, floatStr) + } } } } @@ -372,9 +371,8 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) { f.Float64Flag.Apply(set) } -func isEnvVarSet(envVars string) bool { - for _, envVar := range strings.Split(envVars, ",") { - envVar = strings.TrimSpace(envVar) +func isEnvVarSet(envVars []string) bool { + for _, envVar := range envVars { if envVal := os.Getenv(envVar); envVal != "" { // TODO: Can't use this for bools as // set means that it was true or false based on @@ -391,11 +389,3 @@ func isEnvVarSet(envVars string) bool { func float64ToString(f float64) string { return fmt.Sprintf("%v", f) } - -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index be7166f..0b188da 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -46,7 +46,11 @@ func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}), + Flag: NewGenericFlag(cli.GenericFlag{ + Name: "test", + Value: &Parser{}, + EnvVars: []string{"TEST"}, + }), FlagName: "test", MapValue: &Parser{"efg", "hij"}, EnvVarName: "TEST", @@ -76,7 +80,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []string{"hello", "world"}, EnvVarName: "TEST", @@ -106,7 +110,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []int{1, 2}, EnvVarName: "TEST", @@ -136,7 +140,7 @@ func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: false, EnvVarName: "TEST", @@ -166,7 +170,7 @@ func TestStringApplyInputSourceMethodContextSet(t *testing.T) { func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: "hello", EnvVarName: "TEST", @@ -196,7 +200,7 @@ func TestIntApplyInputSourceMethodContextSet(t *testing.T) { func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 15, EnvVarName: "TEST", @@ -226,7 +230,7 @@ func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}), + Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: time.Duration(30 * time.Second), EnvVarName: "TEST", @@ -256,7 +260,7 @@ func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}), + Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 1.3, EnvVarName: "TEST", diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 519bd81..d1a15d7 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -68,7 +68,7 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), + NewIntFlag(cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}), cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -103,7 +103,7 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), + NewIntFlag(cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}), cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -268,7 +268,7 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), + NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}), cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -303,7 +303,7 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *tes return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}), cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) diff --git a/app.go b/app.go index 066ea7a..622c7a1 100644 --- a/app.go +++ b/app.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "sort" "time" ) @@ -121,7 +122,7 @@ func (a *App) Setup() { // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { a.appendFlag(HelpFlag) } } @@ -224,7 +225,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { a.appendFlag(HelpFlag) } } @@ -379,7 +380,7 @@ func (a *App) VisibleFlags() []Flag { func (a *App) hasFlag(flag Flag) bool { for _, f := range a.Flags { - if flag == f { + if reflect.DeepEqual(flag, f) { return true } } diff --git a/command.go b/command.go index 7a77953..c2f39d0 100644 --- a/command.go +++ b/command.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "io/ioutil" + "reflect" "sort" "strings" ) @@ -69,7 +70,7 @@ func (c Command) Run(ctx *Context) (err error) { return c.startApp(ctx) } - if !c.HideHelp && (HelpFlag != BoolFlag{}) { + if !c.HideHelp && !reflect.DeepEqual(HelpFlag, BoolFlag{}) { // append help to flags c.Flags = append( c.Flags, diff --git a/context.go b/context.go index 45face1..3202c75 100644 --- a/context.go +++ b/context.go @@ -310,7 +310,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error { visited[f.Name] = true }) for _, f := range flags { - parts := strings.Split(f.GetName(), ",") + parts := f.Names() if len(parts) == 1 { continue } diff --git a/flag.go b/flag.go index 97e502c..ba55471 100644 --- a/flag.go +++ b/flag.go @@ -24,16 +24,18 @@ var BashCompletionFlag = BoolFlag{ // VersionFlag prints the version for the application var VersionFlag = BoolFlag{ - Name: "version, v", - Usage: "print the version", + Name: "version", + Aliases: []string{"v"}, + Usage: "print the version", } -// HelpFlag prints the help for all commands and subcommands -// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand -// unless HideHelp is set to true) +// HelpFlag prints the help for all commands and subcommands. +// Set to the zero value (BoolFlag{}) to disable the flag. The subcommand +// will still be added unless HideHelp is set to true. var HelpFlag = BoolFlag{ - Name: "help, h", - Usage: "show help", + Name: "help", + Aliases: []string{"h"}, + Usage: "show help", } // FlagStringer converts a flag definition to a string. This is used by help @@ -52,7 +54,7 @@ type Flag interface { fmt.Stringer // Apply Flag settings to the given flag set Apply(*flag.FlagSet) - GetName() string + Names() []string } func flagSet(name string, flags []Flag) *flag.FlagSet { @@ -64,14 +66,6 @@ func flagSet(name string, flags []Flag) *flag.FlagSet { return set } -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} - // Generic is a generic parseable type identified by a specific flag type Generic interface { Set(value string) error @@ -80,11 +74,12 @@ type Generic interface { // GenericFlag is the flag type for types implementing Generic type GenericFlag struct { - Name string - Value Generic - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value Generic + Usage string + EnvVars []string + Hidden bool } // String returns the string representation of the generic flag to display the @@ -98,8 +93,8 @@ func (f GenericFlag) String() string { // provided by the user for parsing by the flag func (f GenericFlag) Apply(set *flag.FlagSet) { val := f.Value - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { val.Set(envVal) @@ -108,14 +103,14 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of a flag. -func (f GenericFlag) GetName() string { - return f.Name +// Names returns the names of a flag. +func (f GenericFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // StringSlice wraps a []string to satisfy flag.Value @@ -166,11 +161,12 @@ func (f *StringSlice) Value() []string { // StringSliceFlag is a string flag that can be specified multiple times on the // command-line type StringSliceFlag struct { - Name string - Value *StringSlice - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value *StringSlice + Usage string + EnvVars []string + Hidden bool } // String returns the usage @@ -180,8 +176,8 @@ func (f StringSliceFlag) String() string { // Apply populates the flag given the flag set and environment func (f StringSliceFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { newVal := NewStringSlice() @@ -199,14 +195,14 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { f.Value = NewStringSlice() } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of a flag. -func (f StringSliceFlag) GetName() string { - return f.Name +// Names returns the name of a flag. +func (f StringSliceFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // IntSlice wraps an []int to satisfy flag.Value @@ -272,11 +268,12 @@ func (i *IntSlice) Value() []int { // IntSliceFlag is an int flag that can be specified multiple times on the // command-line type IntSliceFlag struct { - Name string - Value *IntSlice - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value *IntSlice + Usage string + EnvVars []string + Hidden bool } // String returns the usage @@ -286,8 +283,8 @@ func (f IntSliceFlag) String() string { // Apply populates the flag given the flag set and environment func (f IntSliceFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { newVal := NewIntSlice() @@ -308,22 +305,23 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { f.Value = NewIntSlice() } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f IntSliceFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f IntSliceFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string + Aliases []string Value bool Usage string - EnvVar string + EnvVars []string Destination *bool Hidden bool } @@ -335,8 +333,8 @@ func (f BoolFlag) String() string { // Apply populates the flag given the flag set and environment func (f BoolFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValBool, err := strconv.ParseBool(envVal) @@ -348,26 +346,27 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.BoolVar(f.Destination, name, f.Value, f.Usage) return } set.Bool(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f BoolFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f BoolFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // StringFlag represents a flag that takes as string value type StringFlag struct { Name string + Aliases []string Value string Usage string - EnvVar string + EnvVars []string Destination *string Hidden bool } @@ -379,8 +378,8 @@ func (f StringFlag) String() string { // Apply populates the flag given the flag set and environment func (f StringFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { f.Value = envVal @@ -389,27 +388,28 @@ func (f StringFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.StringVar(f.Destination, name, f.Value, f.Usage) return } set.String(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f StringFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f StringFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // IntFlag is a flag that takes an integer // Errors if the value provided cannot be parsed type IntFlag struct { Name string + Aliases []string Value int Usage string - EnvVar string + EnvVars []string Destination *int Hidden bool } @@ -421,8 +421,8 @@ func (f IntFlag) String() string { // Apply populates the flag given the flag set and environment func (f IntFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValInt, err := strconv.ParseInt(envVal, 0, 64) @@ -434,27 +434,28 @@ func (f IntFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.IntVar(f.Destination, name, f.Value, f.Usage) return } set.Int(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f IntFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f IntFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // DurationFlag is a flag that takes a duration specified in Go's duration // format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { Name string + Aliases []string Value time.Duration Usage string - EnvVar string + EnvVars []string Destination *time.Duration Hidden bool } @@ -466,8 +467,8 @@ func (f DurationFlag) String() string { // Apply populates the flag given the flag set and environment func (f DurationFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValDuration, err := time.ParseDuration(envVal) @@ -479,27 +480,28 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.DurationVar(f.Destination, name, f.Value, f.Usage) return } set.Duration(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f DurationFlag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f DurationFlag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } // Float64Flag is a flag that takes an float value // Errors if the value provided cannot be parsed type Float64Flag struct { Name string + Aliases []string Value float64 Usage string - EnvVar string + EnvVars []string Destination *float64 Hidden bool } @@ -511,8 +513,8 @@ func (f Float64Flag) String() string { // Apply populates the flag given the flag set and environment func (f Float64Flag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValFloat, err := strconv.ParseFloat(envVal, 10) @@ -523,18 +525,18 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.Float64Var(f.Destination, name, f.Value, f.Usage) return } set.Float64(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f Float64Flag) GetName() string { - return f.Name +// Names returns the name of the flag. +func (f Float64Flag) Names() []string { + return append([]string{f.Name}, f.Aliases...) } func visibleFlags(fl []Flag) []Flag { @@ -574,25 +576,27 @@ func unquoteUsage(usage string) (string, string) { return "", usage } -func prefixedNames(fullName, placeholder string) string { +func prefixedNames(names []string, placeholder string) string { var prefixed string - parts := strings.Split(fullName, ",") - for i, name := range parts { - name = strings.Trim(name, " ") + for i, name := range names { + if name == "" { + continue + } + prefixed += prefixFor(name) + name if placeholder != "" { prefixed += " " + placeholder } - if i < len(parts)-1 { + if i < len(names)-1 { prefixed += ", " } } return prefixed } -func withEnvHint(envVar, str string) string { +func withEnvHint(envVars []string, str string) string { envText := "" - if envVar != "" { + if envVars != nil && len(envVars) > 0 { prefix := "$" suffix := "" sep := ", $" @@ -601,21 +605,49 @@ func withEnvHint(envVar, str string) string { suffix = "%" sep = "%, %" } - envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix) } return str + envText } -func stringifyFlag(f Flag) string { +func flagStringSliceField(f Flag, name string) []string { + fv := flagValue(f) + field := fv.FieldByName(name) + + if field.IsValid() { + return field.Interface().([]string) + } + + return []string{} +} + +func flagStringField(f Flag, name string) string { + fv := flagValue(f) + field := fv.FieldByName(name) + + if field.IsValid() { + return field.String() + } + + return "" +} + +func flagValue(f Flag) reflect.Value { fv := reflect.ValueOf(f) + for fv.Kind() == reflect.Ptr { + fv = reflect.Indirect(fv) + } + return fv +} + +func stringifyFlag(f Flag) string { + fv := flagValue(f) switch f.(type) { case IntSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyIntSliceFlag(f.(IntSliceFlag))) + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(IntSliceFlag))) case StringSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyStringSliceFlag(f.(StringSliceFlag))) + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(StringSliceFlag))) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) @@ -643,8 +675,8 @@ func stringifyFlag(f Flag) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) - return withEnvHint(fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) + return withEnvHint(flagStringSliceField(f, "EnvVars"), + fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } func stringifyIntSliceFlag(f IntSliceFlag) string { @@ -655,7 +687,7 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) } func stringifyStringSliceFlag(f StringSliceFlag) string { @@ -668,10 +700,10 @@ func stringifyStringSliceFlag(f StringSliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) } -func stringifySliceFlag(usage, name string, defaultVals []string) string { +func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { placeholder = defaultPlaceholder @@ -683,5 +715,15 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) + return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault) +} + +func hasFlag(flags []Flag, fl Flag) bool { + for _, existing := range flags { + if fl == existing { + return true + } + } + + return false } diff --git a/flag_test.go b/flag_test.go index a3414cf..8ff64fb 100644 --- a/flag_test.go +++ b/flag_test.go @@ -31,21 +31,22 @@ func TestBoolFlagHelpOutput(t *testing.T) { var stringFlagTests = []struct { name string + aliases []string usage string value string expected string }{ - {"foo", "", "", "--foo value\t"}, - {"f", "", "", "-f value\t"}, - {"f", "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"}, - {"test", "", "Something", "--test value\t(default: \"Something\")"}, - {"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, - {"config,c", "Load configuration from `CONFIG`", "config.json", "--config CONFIG, -c CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, + {"foo", nil, "", "", "--foo value\t"}, + {"f", nil, "", "", "-f value\t"}, + {"f", nil, "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"}, + {"test", nil, "", "Something", "--test value\t(default: \"Something\")"}, + {"config", []string{"c"}, "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, + {"config", []string{"c"}, "Load configuration from `CONFIG`", "config.json", "--config CONFIG, -c CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, } func TestStringFlagHelpOutput(t *testing.T) { for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} + flag := StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} output := flag.String() if output != test.expected { @@ -58,7 +59,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_FOO", "derp") for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} + flag := StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} output := flag.String() expectedSuffix := " [$APP_FOO]" @@ -73,19 +74,20 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { var stringSliceFlagTests = []struct { name string + aliases []string value *StringSlice expected string }{ - {"foo", NewStringSlice(""), "--foo value\t"}, - {"f", NewStringSlice(""), "-f value\t"}, - {"f", NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, - {"test", NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, - {"d, dee", NewStringSlice("Inka", "Dinka", "dooo"), "-d value, --dee value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, + {"foo", nil, NewStringSlice(""), "--foo value\t"}, + {"f", nil, NewStringSlice(""), "-f value\t"}, + {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, + {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, + {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Value: test.value} + flag := StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -98,7 +100,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_QWWX", "11,4") for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} + flag := StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} output := flag.String() expectedSuffix := " [$APP_QWWX]" @@ -134,7 +136,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -170,7 +172,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2h3m6s") for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -185,17 +187,18 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { var intSliceFlagTests = []struct { name string + aliases []string value *IntSlice expected string }{ - {"heads", NewIntSlice(), "--heads value\t"}, - {"H", NewIntSlice(), "-H value\t"}, - {"H, heads", NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"}, + {"heads", nil, NewIntSlice(), "--heads value\t"}, + {"H", nil, NewIntSlice(), "-H value\t"}, + {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Value: test.value} + flag := IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -208,7 +211,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_SMURF", "42,3") for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} + flag := IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() expectedSuffix := " [$APP_SMURF]" @@ -244,7 +247,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAZ", "99.4") for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} + flag := Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} output := flag.String() expectedSuffix := " [$APP_BAZ]" @@ -281,7 +284,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_ZAP", "3") for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} + flag := GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} output := flag.String() expectedSuffix := " [$APP_ZAP]" @@ -297,7 +300,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { func TestParseMultiString(t *testing.T) { (&App{ Flags: []Flag{ - StringFlag{Name: "serve, s"}, + StringFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.String("serve") != "10" { @@ -335,7 +338,7 @@ func TestParseMultiStringFromEnv(t *testing.T) { os.Setenv("APP_COUNT", "20") (&App{ Flags: []Flag{ - StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, + StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -354,7 +357,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { os.Setenv("APP_COUNT", "20") (&App{ Flags: []Flag{ - StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, + StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -371,7 +374,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice()}, + StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -389,7 +392,7 @@ func TestParseMultiStringSlice(t *testing.T) { func TestParseMultiStringSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, + StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -407,7 +410,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve, s", Value: NewStringSlice("9", "2")}, + StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { @@ -427,7 +430,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "APP_INTERVALS"}, + StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -447,7 +450,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "APP_INTERVALS"}, + StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -467,7 +470,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -487,7 +490,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals, i", Value: NewStringSlice("1", "2", "5"), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -504,7 +507,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { func TestParseMultiInt(t *testing.T) { a := App{ Flags: []Flag{ - IntFlag{Name: "serve, s"}, + IntFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Int("serve") != 10 { @@ -543,7 +546,7 @@ func TestParseMultiIntFromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := App{ Flags: []Flag{ - IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -563,7 +566,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := App{ Flags: []Flag{ - IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -581,7 +584,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { func TestParseMultiIntSlice(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice()}, + IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -598,7 +601,7 @@ func TestParseMultiIntSlice(t *testing.T) { func TestParseMultiIntSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, + IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -615,7 +618,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve, s", Value: NewIntSlice(9, 2)}, + IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { @@ -635,7 +638,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "APP_INTERVALS"}, + IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -655,7 +658,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(1, 2, 5), EnvVar: "APP_INTERVALS"}, + IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(1, 2, 5), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -675,7 +678,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals, i", Value: NewIntSlice(), EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -692,7 +695,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { func TestParseMultiFloat64(t *testing.T) { a := App{ Flags: []Flag{ - Float64Flag{Name: "serve, s"}, + Float64Flag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Float64("serve") != 10.2 { @@ -731,7 +734,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := App{ Flags: []Flag{ - Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { @@ -751,7 +754,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := App{ Flags: []Flag{ - Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { @@ -769,7 +772,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { func TestParseMultiBool(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "serve, s"}, + BoolFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { @@ -808,7 +811,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := App{ Flags: []Flag{ - BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -828,7 +831,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := App{ Flags: []Flag{ - BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -846,7 +849,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { func TestParseMultiBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "implode, i", Value: true}, + BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, }, Action: func(ctx *Context) error { if ctx.Bool("implode") { @@ -888,9 +891,10 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) { a := App{ Flags: []Flag{ BoolFlag{ - Name: "debug, d", - Value: true, - EnvVar: "APP_DEBUG", + Name: "debug", + Aliases: []string{"d"}, + Value: true, + EnvVars: []string{"APP_DEBUG"}, }, }, Action: func(ctx *Context) error { @@ -912,9 +916,10 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { a := App{ Flags: []Flag{ BoolFlag{ - Name: "debug, d", - Value: true, - EnvVar: "COMPAT_DEBUG,APP_DEBUG", + Name: "debug", + Aliases: []string{"d"}, + Value: true, + EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}, }, }, Action: func(ctx *Context) error { @@ -951,7 +956,7 @@ func (p *Parser) String() string { func TestParseGeneric(t *testing.T) { a := App{ Flags: []Flag{ - GenericFlag{Name: "serve, s", Value: &Parser{}}, + GenericFlag{Name: "serve", Aliases: []string{"s"}, Value: &Parser{}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { @@ -971,7 +976,12 @@ func TestParseGenericFromEnv(t *testing.T) { os.Setenv("APP_SERVE", "20,30") a := App{ Flags: []Flag{ - GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, + GenericFlag{ + Name: "serve", + Aliases: []string{"s"}, + Value: &Parser{}, + EnvVars: []string{"APP_SERVE"}, + }, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { @@ -991,7 +1001,11 @@ func TestParseGenericFromEnvCascade(t *testing.T) { os.Setenv("APP_FOO", "99,2000") a := App{ Flags: []Flag{ - GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, + GenericFlag{ + Name: "foos", + Value: &Parser{}, + EnvVars: []string{"COMPAT_FOO", "APP_FOO"}, + }, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { diff --git a/help.go b/help.go index 893b1be..2675289 100644 --- a/help.go +++ b/help.go @@ -208,11 +208,11 @@ func printHelp(out io.Writer, templ string, data interface{}) { func checkVersion(c *Context) bool { found := false if VersionFlag.Name != "" { - eachName(VersionFlag.Name, func(name string) { + for _, name := range VersionFlag.Names() { if c.Bool(name) { found = true } - }) + } } return found } @@ -220,11 +220,11 @@ func checkVersion(c *Context) bool { func checkHelp(c *Context) bool { found := false if HelpFlag.Name != "" { - eachName(HelpFlag.Name, func(name string) { + for _, name := range HelpFlag.Names() { if c.Bool(name) { found = true } - }) + } } return found } diff --git a/help_test.go b/help_test.go index d19bc4c..9ca4c0d 100644 --- a/help_test.go +++ b/help_test.go @@ -60,13 +60,14 @@ func Test_Help_Custom_Flags(t *testing.T) { }() HelpFlag = BoolFlag{ - Name: "help, x", - Usage: "show help", + Name: "help", + Aliases: []string{"x"}, + Usage: "show help", } app := App{ Flags: []Flag{ - BoolFlag{Name: "foo, h"}, + BoolFlag{Name: "foo", Aliases: []string{"h"}}, }, Action: func(ctx *Context) error { if ctx.Bool("h") != true { @@ -90,13 +91,14 @@ func Test_Version_Custom_Flags(t *testing.T) { }() VersionFlag = BoolFlag{ - Name: "version, V", - Usage: "show version", + Name: "version", + Aliases: []string{"V"}, + Usage: "show version", } app := App{ Flags: []Flag{ - BoolFlag{Name: "foo, v"}, + BoolFlag{Name: "foo", Aliases: []string{"v"}}, }, Action: func(ctx *Context) error { if ctx.Bool("v") != true { From 334e66cb8f24be3190fded4ad5952a07244fbc8b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 10:59:42 -0400 Subject: [PATCH 017/158] Remove unused (so far) func --- flag.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/flag.go b/flag.go index ba55471..067e9ee 100644 --- a/flag.go +++ b/flag.go @@ -717,13 +717,3 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault) } - -func hasFlag(flags []Flag, fl Flag) bool { - for _, existing := range flags { - if fl == existing { - return true - } - } - - return false -} From cd10b49473a6178b5225df751c143cf07359439e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 15:20:52 -0400 Subject: [PATCH 018/158] Minimize struct copying by using pointer func receivers and slices of struct pointers where possible. --- README.md | 28 ++-- altsrc/flag.go | 48 +++---- altsrc/flag_test.go | 48 +++---- altsrc/yaml_command_test.go | 36 ++--- app.go | 46 ++++--- app_test.go | 259 ++++++++++++++++++++---------------- category.go | 34 +++-- command.go | 48 ++++--- command_test.go | 8 +- context.go | 53 +++++--- context_test.go | 4 +- flag.go | 80 ++++++----- flag_test.go | 102 +++++++------- help.go | 14 +- help_test.go | 16 +-- 15 files changed, 452 insertions(+), 372 deletions(-) diff --git a/README.md b/README.md index 0a1f624..3fe7556 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ Setting and querying flags is simple. ``` go ... app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -232,7 +232,7 @@ You can also set a destination variable for a flag, to which the content will be ... var language string app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -264,7 +264,7 @@ indicated with back quotes. For example this: ```go -cli.StringFlag{ +&cli.StringFlag{ Name: "config", Aliases: []string{"c"}, Usage: "Load configuration from `FILE`", @@ -285,7 +285,7 @@ You can set alternate (or short) names for flags by providing a comma-delimited ``` go app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Aliases: []string{"l"}, Value: "english", @@ -302,7 +302,7 @@ You can also have the default value set from the environment via `EnvVars`. e.g ``` go app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Aliases: []string{"l"}, Value: "english", @@ -316,7 +316,7 @@ If `EnvVars` contains more than one string, the first environment variable that ``` go app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "lang", Aliases: []string{"l"}, Value: "english", @@ -333,7 +333,7 @@ There is a separate package altsrc that adds support for getting flag values fro In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below: ``` go - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}) ``` Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below. @@ -362,8 +362,8 @@ Here is a more complete sample of a command using YAML support: return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -375,7 +375,7 @@ Subcommands can be defined for a more git-like command line app. ```go ... -app.Commands = []cli.Command{ +app.Commands = []*cli.Command{ { Name: "add", Aliases: []string{"a"}, @@ -398,7 +398,7 @@ app.Commands = []cli.Command{ Name: "template", Aliases: []string{"r"}, Usage: "options for task templates", - Subcommands: []cli.Command{ + Subcommands: []*cli.Command{ { Name: "add", Usage: "add a new template", @@ -431,7 +431,7 @@ E.g. ```go ... - app.Commands = []cli.Command{ + app.Commands = []*cli.Command{ { Name: "noop", }, @@ -479,7 +479,7 @@ import ( func main() { app := cli.NewApp() app.Flags = []cli.Flag{ - cli.BoolFlag{ + &cli.BoolFlag{ Name: "ginger-crouton", Value: true, Usage: "is it in the soup?", @@ -508,7 +508,7 @@ the App or its subcommands. var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} app := cli.NewApp() app.EnableBashCompletion = true -app.Commands = []cli.Command{ +app.Commands = []*cli.Command{ { Name: "complete", Aliases: []string{"c"}, diff --git a/altsrc/flag.go b/altsrc/flag.go index 6c68bf5..1e6ebce 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -62,15 +62,15 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context } } -// GenericFlag is the flag type that wraps cli.GenericFlag to allow +// GenericFlag is the flag type that wraps *cli.GenericFlag to allow // for other values to be specified type GenericFlag struct { - cli.GenericFlag + *cli.GenericFlag set *flag.FlagSet } // NewGenericFlag creates a new GenericFlag -func NewGenericFlag(flag cli.GenericFlag) *GenericFlag { +func NewGenericFlag(flag *cli.GenericFlag) *GenericFlag { return &GenericFlag{GenericFlag: flag, set: nil} } @@ -100,15 +100,15 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) { f.GenericFlag.Apply(set) } -// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow +// StringSliceFlag is the flag type that wraps *cli.StringSliceFlag to allow // for other values to be specified type StringSliceFlag struct { - cli.StringSliceFlag + *cli.StringSliceFlag set *flag.FlagSet } // NewStringSliceFlag creates a new StringSliceFlag -func NewStringSliceFlag(flag cli.StringSliceFlag) *StringSliceFlag { +func NewStringSliceFlag(flag *cli.StringSliceFlag) *StringSliceFlag { return &StringSliceFlag{StringSliceFlag: flag, set: nil} } @@ -141,15 +141,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) { f.StringSliceFlag.Apply(set) } -// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow +// IntSliceFlag is the flag type that wraps *cli.IntSliceFlag to allow // for other values to be specified type IntSliceFlag struct { - cli.IntSliceFlag + *cli.IntSliceFlag set *flag.FlagSet } // NewIntSliceFlag creates a new IntSliceFlag -func NewIntSliceFlag(flag cli.IntSliceFlag) *IntSliceFlag { +func NewIntSliceFlag(flag *cli.IntSliceFlag) *IntSliceFlag { return &IntSliceFlag{IntSliceFlag: flag, set: nil} } @@ -182,15 +182,15 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) { f.IntSliceFlag.Apply(set) } -// BoolFlag is the flag type that wraps cli.BoolFlag to allow +// BoolFlag is the flag type that wraps *cli.BoolFlag to allow // for other values to be specified type BoolFlag struct { - cli.BoolFlag + *cli.BoolFlag set *flag.FlagSet } // NewBoolFlag creates a new BoolFlag -func NewBoolFlag(flag cli.BoolFlag) *BoolFlag { +func NewBoolFlag(flag *cli.BoolFlag) *BoolFlag { return &BoolFlag{BoolFlag: flag, set: nil} } @@ -219,15 +219,15 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) { f.BoolFlag.Apply(set) } -// StringFlag is the flag type that wraps cli.StringFlag to allow +// StringFlag is the flag type that wraps *cli.StringFlag to allow // for other values to be specified type StringFlag struct { - cli.StringFlag + *cli.StringFlag set *flag.FlagSet } // NewStringFlag creates a new StringFlag -func NewStringFlag(flag cli.StringFlag) *StringFlag { +func NewStringFlag(flag *cli.StringFlag) *StringFlag { return &StringFlag{StringFlag: flag, set: nil} } @@ -257,15 +257,15 @@ func (f *StringFlag) Apply(set *flag.FlagSet) { f.StringFlag.Apply(set) } -// IntFlag is the flag type that wraps cli.IntFlag to allow +// IntFlag is the flag type that wraps *cli.IntFlag to allow // for other values to be specified type IntFlag struct { - cli.IntFlag + *cli.IntFlag set *flag.FlagSet } // NewIntFlag creates a new IntFlag -func NewIntFlag(flag cli.IntFlag) *IntFlag { +func NewIntFlag(flag *cli.IntFlag) *IntFlag { return &IntFlag{IntFlag: flag, set: nil} } @@ -294,15 +294,15 @@ func (f *IntFlag) Apply(set *flag.FlagSet) { f.IntFlag.Apply(set) } -// DurationFlag is the flag type that wraps cli.DurationFlag to allow +// DurationFlag is the flag type that wraps *cli.DurationFlag to allow // for other values to be specified type DurationFlag struct { - cli.DurationFlag + *cli.DurationFlag set *flag.FlagSet } // NewDurationFlag creates a new DurationFlag -func NewDurationFlag(flag cli.DurationFlag) *DurationFlag { +func NewDurationFlag(flag *cli.DurationFlag) *DurationFlag { return &DurationFlag{DurationFlag: flag, set: nil} } @@ -332,15 +332,15 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) { f.DurationFlag.Apply(set) } -// Float64Flag is the flag type that wraps cli.Float64Flag to allow +// Float64Flag is the flag type that wraps *cli.Float64Flag to allow // for other values to be specified type Float64Flag struct { - cli.Float64Flag + *cli.Float64Flag set *flag.FlagSet } // NewFloat64Flag creates a new Float64Flag -func NewFloat64Flag(flag cli.Float64Flag) *Float64Flag { +func NewFloat64Flag(flag *cli.Float64Flag) *Float64Flag { return &Float64Flag{Float64Flag: flag, set: nil} } diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 0b188da..54055f3 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -26,7 +26,7 @@ type testApplyInputSource struct { func TestGenericApplyInputSourceValue(t *testing.T) { v := &Parser{"abc", "def"} c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}), FlagName: "test", MapValue: v, }) @@ -36,7 +36,7 @@ func TestGenericApplyInputSourceValue(t *testing.T) { func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { p := &Parser{"abc", "def"} c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}), FlagName: "test", MapValue: &Parser{"efg", "hig"}, ContextValueString: p.String(), @@ -46,7 +46,7 @@ func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewGenericFlag(cli.GenericFlag{ + Flag: NewGenericFlag(&cli.GenericFlag{ Name: "test", Value: &Parser{}, EnvVars: []string{"TEST"}, @@ -61,7 +61,7 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestStringSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), FlagName: "test", MapValue: []string{"hello", "world"}, }) @@ -70,7 +70,7 @@ func TestStringSliceApplyInputSourceValue(t *testing.T) { func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), FlagName: "test", MapValue: []string{"hello", "world"}, ContextValueString: "ohno", @@ -80,7 +80,7 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []string{"hello", "world"}, EnvVarName: "TEST", @@ -91,7 +91,7 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestIntSliceApplyInputSourceValue(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), FlagName: "test", MapValue: []int{1, 2}, }) @@ -100,7 +100,7 @@ func TestIntSliceApplyInputSourceValue(t *testing.T) { func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), FlagName: "test", MapValue: []int{1, 2}, ContextValueString: "3", @@ -110,7 +110,7 @@ func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []int{1, 2}, EnvVarName: "TEST", @@ -121,7 +121,7 @@ func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestBoolApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}), FlagName: "test", MapValue: true, }) @@ -130,7 +130,7 @@ func TestBoolApplyInputSourceMethodSet(t *testing.T) { func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}), FlagName: "test", MapValue: false, ContextValueString: "true", @@ -140,7 +140,7 @@ func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: false, EnvVarName: "TEST", @@ -151,7 +151,7 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestStringApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", MapValue: "hello", }) @@ -160,7 +160,7 @@ func TestStringApplyInputSourceMethodSet(t *testing.T) { func TestStringApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", MapValue: "hello", ContextValueString: "goodbye", @@ -170,7 +170,7 @@ func TestStringApplyInputSourceMethodContextSet(t *testing.T) { func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: "hello", EnvVarName: "TEST", @@ -181,7 +181,7 @@ func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestIntApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: 15, }) @@ -190,7 +190,7 @@ func TestIntApplyInputSourceMethodSet(t *testing.T) { func TestIntApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: 15, ContextValueString: "7", @@ -200,7 +200,7 @@ func TestIntApplyInputSourceMethodContextSet(t *testing.T) { func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 15, EnvVarName: "TEST", @@ -211,7 +211,7 @@ func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestDurationApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: time.Duration(30 * time.Second), }) @@ -220,7 +220,7 @@ func TestDurationApplyInputSourceMethodSet(t *testing.T) { func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: time.Duration(30 * time.Second), ContextValueString: time.Duration(15 * time.Second).String(), @@ -230,7 +230,7 @@ func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: time.Duration(30 * time.Second), EnvVarName: "TEST", @@ -241,7 +241,7 @@ func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: 1.3, }) @@ -250,7 +250,7 @@ func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: 1.3, ContextValueString: fmt.Sprintf("%v", 1.4), @@ -260,7 +260,7 @@ func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ - Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}), + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 1.3, EnvVarName: "TEST", diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index d1a15d7..44e8351 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -35,8 +35,8 @@ func TestCommandYamlFileTest(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -68,8 +68,8 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -103,8 +103,8 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -135,8 +135,8 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -168,8 +168,8 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test"}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test"}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -200,8 +200,8 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -233,8 +233,8 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) @@ -268,8 +268,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) @@ -303,8 +303,8 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *tes return nil }, Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}), - cli.StringFlag{Name: "load"}}, + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, } command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) err := command.Run(c) diff --git a/app.go b/app.go index 622c7a1..68a92a2 100644 --- a/app.go +++ b/app.go @@ -27,7 +27,7 @@ type App struct { // Version of the program Version string // List of commands to execute - Commands []Command + Commands []*Command // List of flags to parse Flags []Flag // Boolean to enable bash completion commands @@ -37,7 +37,7 @@ type App struct { // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool // Populate on app startup, only gettable through method Categories() - categories CommandCategories + categories *CommandCategories // An action to execute when the bash-completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -55,7 +55,7 @@ type App struct { // Compilation date Compiled time.Time // List of all authors who contributed - Authors []Author + Authors []*Author // Copyright of the binary if any Copyright string // Writer writer to write output to @@ -104,7 +104,7 @@ func (a *App) Setup() { a.didSetup = true - newCmds := []Command{} + newCmds := []*Command{} for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) @@ -113,7 +113,7 @@ func (a *App) Setup() { } a.Commands = newCmds - a.categories = CommandCategories{} + a.categories = NewCommandCategories() for _, command := range a.Commands { a.categories = a.categories.AddCommand(command.Category, command) } @@ -121,8 +121,9 @@ func (a *App) Setup() { // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } @@ -224,14 +225,15 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // append help to commands if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if !reflect.DeepEqual(HelpFlag, BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } } - newCmds := []Command{} + newCmds := []*Command{} for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) @@ -331,7 +333,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { func (a *App) Command(name string) *Command { for _, c := range a.Commands { if c.HasName(name) { - return &c + return c } } @@ -339,7 +341,7 @@ func (a *App) Command(name string) *Command { } // Categories returns a slice containing all the categories with the commands they contain -func (a *App) Categories() CommandCategories { +func (a *App) Categories() *CommandCategories { return a.categories } @@ -347,7 +349,7 @@ func (a *App) Categories() CommandCategories { // Hidden=false func (a *App) VisibleCategories() []*CommandCategory { ret := []*CommandCategory{} - for _, category := range a.categories { + for _, category := range a.categories.Categories { if visible := func() *CommandCategory { for _, command := range category.Commands { if !command.Hidden { @@ -363,8 +365,8 @@ func (a *App) VisibleCategories() []*CommandCategory { } // VisibleCommands returns a slice of the Commands with Hidden=false -func (a *App) VisibleCommands() []Command { - ret := []Command{} +func (a *App) VisibleCommands() []*Command { + ret := []*Command{} for _, command := range a.Commands { if !command.Hidden { ret = append(ret, command) @@ -398,9 +400,15 @@ func (a *App) errWriter() io.Writer { return a.ErrWriter } -func (a *App) appendFlag(flag Flag) { - if !a.hasFlag(flag) { - a.Flags = append(a.Flags, flag) +func (a *App) appendFlag(fl Flag) { + if !hasFlag(a.Flags, fl) { + a.Flags = append(a.Flags, fl) + } +} + +func (a *App) appendCommand(c *Command) { + if !hasCommand(a.Commands, c) { + a.Commands = append(a.Commands, c) } } @@ -411,7 +419,7 @@ type Author struct { } // String makes Author comply to the Stringer interface, to allow an easy print in the templating process -func (a Author) String() string { +func (a *Author) String() string { e := "" if a.Email != "" { e = "<" + a.Email + "> " diff --git a/app_test.go b/app_test.go index 8d81a5d..3c79766 100644 --- a/app_test.go +++ b/app_test.go @@ -24,14 +24,14 @@ func ExampleApp_Run() { app := NewApp() app.Name = "greet" app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } app.Action = func(c *Context) error { fmt.Printf("Hello %v\n", c.String("name")) return nil } app.UsageText = "app [first_arg] [second_arg]" - app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} + app.Authors = []*Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} app.Run(os.Args) // Output: // Hello Jeremy @@ -42,20 +42,20 @@ func ExampleApp_Run_subcommand() { os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} app := NewApp() app.Name = "say" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "hello", Aliases: []string{"hi"}, Usage: "use it to see a description", Description: "This is how we describe hello the function", - Subcommands: []Command{ + Subcommands: []*Command{ { Name: "english", Aliases: []string{"en"}, Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "name", Value: "Bob", Usage: "Name of the person to greet", @@ -82,9 +82,9 @@ func ExampleApp_Run_help() { app := NewApp() app.Name = "greet" app.Flags = []Flag{ - StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "describeit", Aliases: []string{"d"}, @@ -115,7 +115,7 @@ func ExampleApp_Run_bashComplete() { app := NewApp() app.Name = "greet" app.EnableBashCompletion = true - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "describeit", Aliases: []string{"d"}, @@ -175,9 +175,9 @@ var commandAppTests = []struct { func TestApp_Command(t *testing.T) { app := NewApp() - fooCommand := Command{Name: "foobar", Aliases: []string{"f"}} - batCommand := Command{Name: "batbaz", Aliases: []string{"b"}} - app.Commands = []Command{ + fooCommand := &Command{Name: "foobar", Aliases: []string{"f"}} + batCommand := &Command{Name: "batbaz", Aliases: []string{"b"}} + app.Commands = []*Command{ fooCommand, batCommand, } @@ -191,7 +191,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context a := NewApp() - a.Commands = []Command{ + a.Commands = []*Command{ { Name: "foo", Action: func(c *Context) error { @@ -199,7 +199,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { return nil }, Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", @@ -216,13 +216,13 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string - var args []string + var args *Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - StringFlag{Name: "option", Value: "", Usage: "some option"}, + &StringFlag{Name: "option", Value: "", Usage: "some option"}, }, Action: func(c *Context) error { parsedOption = c.String("option") @@ -230,58 +230,58 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}) expect(t, parsedOption, "my-option") - expect(t, args[0], "my-arg") - expect(t, args[1], "--") - expect(t, args[2], "--notARealFlag") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "--") + expect(t, args.Get(2), "--notARealFlag") } func TestApp_CommandWithDash(t *testing.T) { - var args []string + var args *Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Action: func(c *Context) error { args = c.Args() return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "my-arg", "-"}) - expect(t, args[0], "my-arg") - expect(t, args[1], "-") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "-") } func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { - var args []string + var args *Args app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Action: func(c *Context) error { args = c.Args() return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) - expect(t, args[0], "my-arg") - expect(t, args[1], "--") - expect(t, args[2], "notAFlagAtAll") + expect(t, args.Get(0), "my-arg") + expect(t, args.Get(1), "--") + expect(t, args.Get(2), "notAFlagAtAll") } func TestApp_VisibleCommands(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "frob", HelpName: "foo frob", @@ -296,7 +296,7 @@ func TestApp_VisibleCommands(t *testing.T) { } app.Setup() - expected := []Command{ + expected := []*Command{ app.Commands[0], app.Commands[2], // help } @@ -310,14 +310,22 @@ func TestApp_VisibleCommands(t *testing.T) { expect(t, fmt.Sprintf("%p", expectedCommand.Action), fmt.Sprintf("%p", actualCommand.Action)) } - // nil out funcs, as they cannot be compared - // (https://github.com/golang/go/issues/8554) - expectedCommand.Action = nil - actualCommand.Action = nil + func() { + // nil out funcs, as they cannot be compared + // (https://github.com/golang/go/issues/8554) + expectedAction := expectedCommand.Action + actualAction := actualCommand.Action + defer func() { + expectedCommand.Action = expectedAction + actualCommand.Action = actualAction + }() + expectedCommand.Action = nil + actualCommand.Action = nil - if !reflect.DeepEqual(expectedCommand, actualCommand) { - t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand) - } + if !reflect.DeepEqual(expectedCommand, actualCommand) { + t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand) + } + }() } } @@ -326,7 +334,7 @@ func TestApp_Float64Flag(t *testing.T) { app := NewApp() app.Flags = []Flag{ - Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, + &Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, } app.Action = func(c *Context) error { meters = c.Float64("height") @@ -343,11 +351,11 @@ func TestApp_ParseSliceFlags(t *testing.T) { var parsedStringSlice []string app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"}, - StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"}, + &IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"}, + &StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"}, }, Action: func(c *Context) error { parsedIntSlice = c.IntSlice("p") @@ -357,7 +365,7 @@ func TestApp_ParseSliceFlags(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"}) @@ -401,11 +409,11 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { var parsedStringSlice []string app := NewApp() - command := Command{ + command := &Command{ Name: "cmd", Flags: []Flag{ - IntSliceFlag{Name: "a", Usage: "set numbers"}, - StringSliceFlag{Name: "str", Usage: "set strings"}, + &IntSliceFlag{Name: "a", Usage: "set numbers"}, + &StringSliceFlag{Name: "str", Usage: "set strings"}, }, Action: func(c *Context) error { parsedIntSlice = c.IntSlice("a") @@ -413,7 +421,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { return nil }, } - app.Commands = []Command{command} + app.Commands = []*Command{command} app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"}) @@ -491,7 +499,7 @@ func TestApp_BeforeFunc(t *testing.T) { return nil } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "sub", Action: func(c *Context) error { @@ -503,7 +511,7 @@ func TestApp_BeforeFunc(t *testing.T) { } app.Flags = []Flag{ - StringFlag{Name: "opt"}, + &StringFlag{Name: "opt"}, } // run with the Before() func succeeding @@ -583,7 +591,7 @@ func TestApp_AfterFunc(t *testing.T) { return nil } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "sub", Action: func(c *Context) error { @@ -595,7 +603,7 @@ func TestApp_AfterFunc(t *testing.T) { } app.Flags = []Flag{ - StringFlag{Name: "opt"}, + &StringFlag{Name: "opt"}, } // run with the After() func succeeding @@ -639,7 +647,7 @@ func TestAppNoHelpFlag(t *testing.T) { HelpFlag = oldFlag }() - HelpFlag = BoolFlag{} + HelpFlag = nil app := NewApp() app.Writer = ioutil.Discard @@ -698,7 +706,7 @@ func TestApp_CommandNotFound(t *testing.T) { counts.CommandNotFound = counts.Total } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Action: func(c *Context) error { @@ -765,7 +773,7 @@ func TestApp_OrderOfOperations(t *testing.T) { } app.After = afterNoError - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Action: func(c *Context) error { @@ -871,21 +879,21 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf - subCmdBar := Command{ + subCmdBar := &Command{ Name: "bar", Usage: "does bar things", } - subCmdBaz := Command{ + subCmdBaz := &Command{ Name: "baz", Usage: "does baz things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "descriptive wall of text about how it does foo things", - Subcommands: []Command{subCmdBar, subCmdBaz}, + Subcommands: []*Command{subCmdBar, subCmdBaz}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run(flagSet) if err != nil { @@ -916,16 +924,16 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -933,11 +941,14 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "command foo bar - does bar things") { - t.Errorf("expected full path to subcommand: %s", output) + expected := "command foo bar - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "command foo bar [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "command foo bar [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } @@ -946,17 +957,17 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", HelpName: "custom", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -964,11 +975,15 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "custom - does bar things") { - t.Errorf("expected HelpName for subcommand: %s", output) + + expected := "custom - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "custom [arguments...]") { - t.Errorf("expected HelpName to subcommand: %s", output) + + expected = "custom [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } @@ -977,17 +992,17 @@ func TestApp_Run_CommandHelpName(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" - subCmd := Command{ + subCmd := &Command{ Name: "bar", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", HelpName: "custom", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "bar", "--help"}) if err != nil { @@ -995,11 +1010,15 @@ func TestApp_Run_CommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "command foo bar - does bar things") { - t.Errorf("expected full path to subcommand: %s", output) + + expected := "command foo bar - does bar things" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } - if !strings.Contains(output, "command foo bar [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "command foo bar [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %s", expected, output) } } @@ -1008,17 +1027,17 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf app.Name = "base" - subCmd := Command{ + subCmd := &Command{ Name: "bar", HelpName: "custom", Usage: "does bar things", } - cmd := Command{ + cmd := &Command{ Name: "foo", Description: "foo commands", - Subcommands: []Command{subCmd}, + Subcommands: []*Command{subCmd}, } - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run([]string{"command", "foo", "--help"}) if err != nil { @@ -1026,11 +1045,15 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "base foo - foo commands") { - t.Errorf("expected full path to subcommand: %s", output) + + expected := "base foo - foo commands" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %q", expected, output) } - if !strings.Contains(output, "base foo command [command options] [arguments...]") { - t.Errorf("expected full path to subcommand: %s", output) + + expected = "base foo command [command options] [arguments...]" + if !strings.Contains(output, expected) { + t.Errorf("expected %q in output: %q", expected, output) } } @@ -1100,7 +1123,7 @@ func TestApp_Run_Version(t *testing.T) { func TestApp_Run_Categories(t *testing.T) { app := NewApp() app.Name = "categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1119,18 +1142,20 @@ func TestApp_Run_Categories(t *testing.T) { app.Run([]string{"categories"}) - expect := CommandCategories{ - &CommandCategory{ - Name: "1", - Commands: []Command{ - app.Commands[0], - app.Commands[1], + expect := &CommandCategories{ + Categories: []*CommandCategory{ + { + Name: "1", + Commands: []*Command{ + app.Commands[0], + app.Commands[1], + }, }, - }, - &CommandCategory{ - Name: "2", - Commands: []Command{ - app.Commands[2], + { + Name: "2", + Commands: []*Command{ + app.Commands[2], + }, }, }, } @@ -1149,7 +1174,7 @@ func TestApp_Run_Categories(t *testing.T) { func TestApp_VisibleCategories(t *testing.T) { app := NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1171,13 +1196,13 @@ func TestApp_VisibleCategories(t *testing.T) { expected := []*CommandCategory{ { Name: "2", - Commands: []Command{ + Commands: []*Command{ app.Commands[1], }, }, { Name: "3", - Commands: []Command{ + Commands: []*Command{ app.Commands[2], }, }, @@ -1188,7 +1213,7 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1211,7 +1236,7 @@ func TestApp_VisibleCategories(t *testing.T) { expected = []*CommandCategory{ { Name: "3", - Commands: []Command{ + Commands: []*Command{ app.Commands[2], }, }, @@ -1222,7 +1247,7 @@ func TestApp_VisibleCategories(t *testing.T) { app = NewApp() app.Name = "visible-categories" - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "command1", Category: "1", @@ -1270,9 +1295,9 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { - Subcommands: []Command{ + Subcommands: []*Command{ { Name: "sub", }, @@ -1299,7 +1324,7 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() app.Flags = []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, } app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { if isSubcommand { @@ -1310,7 +1335,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { } return errors.New("intercepted: " + err.Error()) } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", }, @@ -1329,7 +1354,7 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { app := NewApp() app.Flags = []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, } app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { if isSubcommand { @@ -1340,7 +1365,7 @@ func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { } return errors.New("intercepted: " + err.Error()) } - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", }, diff --git a/category.go b/category.go index 1a60550..9fb00f7 100644 --- a/category.go +++ b/category.go @@ -1,40 +1,48 @@ package cli // CommandCategories is a slice of *CommandCategory. -type CommandCategories []*CommandCategory +type CommandCategories struct { + Categories []*CommandCategory +} + +func NewCommandCategories() *CommandCategories { + return &CommandCategories{Categories: []*CommandCategory{}} +} // CommandCategory is a category containing commands. type CommandCategory struct { Name string - Commands Commands + Commands []*Command } -func (c CommandCategories) Less(i, j int) bool { - return c[i].Name < c[j].Name +func (c *CommandCategories) Less(i, j int) bool { + return c.Categories[i].Name < c.Categories[j].Name } -func (c CommandCategories) Len() int { - return len(c) +func (c *CommandCategories) Len() int { + return len(c.Categories) } -func (c CommandCategories) Swap(i, j int) { - c[i], c[j] = c[j], c[i] +func (c *CommandCategories) Swap(i, j int) { + c.Categories[i], c.Categories[j] = c.Categories[j], c.Categories[i] } // AddCommand adds a command to a category. -func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { - for _, commandCategory := range c { +func (c *CommandCategories) AddCommand(category string, command *Command) *CommandCategories { + for _, commandCategory := range c.Categories { if commandCategory.Name == category { commandCategory.Commands = append(commandCategory.Commands, command) return c } } - return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) + c.Categories = append(c.Categories, + &CommandCategory{Name: category, Commands: []*Command{command}}) + return c } // VisibleCommands returns a slice of the Commands with Hidden=false -func (c *CommandCategory) VisibleCommands() []Command { - ret := []Command{} +func (c *CommandCategory) VisibleCommands() []*Command { + ret := []*Command{} for _, command := range c.Commands { if !command.Hidden { ret = append(ret, command) diff --git a/command.go b/command.go index c2f39d0..fe31a36 100644 --- a/command.go +++ b/command.go @@ -3,7 +3,6 @@ package cli import ( "fmt" "io/ioutil" - "reflect" "sort" "strings" ) @@ -37,7 +36,7 @@ type Command struct { // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc // List of child commands - Subcommands Commands + Subcommands []*Command // List of flags to parse Flags []Flag // Treat all flags as normal arguments if true @@ -54,32 +53,26 @@ type Command struct { // FullName returns the full name of the command. // For subcommands this ensures that parent commands are part of the command path -func (c Command) FullName() string { +func (c *Command) FullName() string { if c.commandNamePath == nil { return c.Name } return strings.Join(c.commandNamePath, " ") } -// Commands is a slice of Command -type Commands []Command - // Run invokes the command given the context, parses ctx.Args() to generate command-specific flags -func (c Command) Run(ctx *Context) (err error) { +func (c *Command) Run(ctx *Context) (err error) { if len(c.Subcommands) > 0 { return c.startApp(ctx) } - if !c.HideHelp && !reflect.DeepEqual(HelpFlag, BoolFlag{}) { + if !c.HideHelp && HelpFlag != nil { // append help to flags - c.Flags = append( - c.Flags, - HelpFlag, - ) + c.appendFlag(HelpFlag) } if ctx.App.EnableBashCompletion { - c.Flags = append(c.Flags, BashCompletionFlag) + c.appendFlag(BashCompletionFlag) } set := flagSet(c.Name, c.Flags) @@ -156,13 +149,12 @@ func (c Command) Run(ctx *Context) (err error) { } // Names returns the names including short names and aliases. -func (c Command) Names() []string { - names := []string{c.Name} - return append(names, c.Aliases...) +func (c *Command) Names() []string { + return append([]string{c.Name}, c.Aliases...) } // HasName returns true if Command.Name matches given name -func (c Command) HasName(name string) bool { +func (c *Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { return true @@ -171,7 +163,7 @@ func (c Command) HasName(name string) bool { return false } -func (c Command) startApp(ctx *Context) error { +func (c *Command) startApp(ctx *Context) error { app := NewApp() app.Metadata = ctx.App.Metadata // set the name and usage @@ -201,7 +193,7 @@ func (c Command) startApp(ctx *Context) error { app.Compiled = ctx.App.Compiled app.Writer = ctx.App.Writer - app.categories = CommandCategories{} + app.categories = NewCommandCategories() for _, command := range c.Subcommands { app.categories = app.categories.AddCommand(command.Category, command) } @@ -231,6 +223,22 @@ func (c Command) startApp(ctx *Context) error { } // VisibleFlags returns a slice of the Flags with Hidden=false -func (c Command) VisibleFlags() []Flag { +func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } + +func (c *Command) appendFlag(fl Flag) { + if !hasFlag(c.Flags, fl) { + c.Flags = append(c.Flags, fl) + } +} + +func hasCommand(commands []*Command, command *Command) bool { + for _, existing := range commands { + if command == existing { + return true + } + } + + return false +} diff --git a/command_test.go b/command_test.go index a03f8bb..033149f 100644 --- a/command_test.go +++ b/command_test.go @@ -42,13 +42,13 @@ func TestCommandFlagParsing(t *testing.T) { err := command.Run(context) expect(t, err, c.expectedErr) - expect(t, []string(context.Args()), c.testArgs) + expect(t, context.Args().Slice(), c.testArgs) } } func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Before: func(c *Context) error { @@ -75,11 +75,11 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { app := NewApp() - app.Commands = []Command{ + app.Commands = []*Command{ { Name: "bar", Flags: []Flag{ - IntFlag{Name: "flag"}, + &IntFlag{Name: "flag"}, }, OnUsageError: func(c *Context, err error, _ bool) error { if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { diff --git a/context.go b/context.go index 3202c75..0728da9 100644 --- a/context.go +++ b/context.go @@ -14,7 +14,7 @@ import ( // parsed command-line options. type Context struct { App *App - Command Command + Command *Command flagSet *flag.FlagSet parentContext *Context @@ -147,56 +147,69 @@ func (c *Context) Lineage() []*Context { return lineage } -// Args contains apps console arguments -type Args []string - // Args returns the command line arguments associated with the context. -func (c *Context) Args() Args { - args := Args(c.flagSet.Args()) - return args +func (c *Context) Args() *Args { + return &Args{slice: c.flagSet.Args()} } // NArg returns the number of the command line arguments. func (c *Context) NArg() int { - return len(c.Args()) + return c.Args().Len() +} + +// Args wraps a string slice with some convenience methods +type Args struct { + slice []string } // Get returns the nth argument, or else a blank string -func (a Args) Get(n int) string { - if len(a) > n { - return a[n] +func (a *Args) Get(n int) string { + if len(a.slice) > n { + return a.slice[n] } return "" } // First returns the first argument, or else a blank string -func (a Args) First() string { +func (a *Args) First() string { return a.Get(0) } // Tail returns the rest of the arguments (not the first one) // or else an empty string slice -func (a Args) Tail() []string { - if len(a) >= 2 { - return []string(a)[1:] +func (a *Args) Tail() []string { + if a.Len() >= 2 { + return a.slice[1:] } return []string{} } +// Len returns the length of the wrapped slice +func (a *Args) Len() int { + return len(a.slice) +} + // Present checks if there are any arguments present -func (a Args) Present() bool { - return len(a) != 0 +func (a *Args) Present() bool { + return a.Len() != 0 } // Swap swaps arguments at the given indexes -func (a Args) Swap(from, to int) error { - if from >= len(a) || to >= len(a) { +func (a *Args) Swap(from, to int) error { + if from >= a.Len() || to >= a.Len() { return errors.New("index out of range") } - a[from], a[to] = a[to], a[from] + a.slice[from], a.slice[to] = a.slice[to], a.slice[from] return nil } +// Slice returns a copy of the internal slice +func (a *Args) Slice() []string { + ret := make([]string, len(a.slice)) + copy(ret, a.slice) + return ret +} + func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { for _, c := range ctx.Lineage() { if f := c.flagSet.Lookup(name); f != nil { diff --git a/context_test.go b/context_test.go index 8147965..229275c 100644 --- a/context_test.go +++ b/context_test.go @@ -15,7 +15,7 @@ func TestNewContext(t *testing.T) { globalSet.Int("myflag", 42, "doc") globalSet.Float64("myflag64", float64(47), "doc") globalCtx := NewContext(nil, globalSet, nil) - command := Command{Name: "mycommand"} + command := &Command{Name: "mycommand"} c := NewContext(nil, set, globalCtx) c.Command = command expect(t, c.Int("myflag"), 12) @@ -63,7 +63,7 @@ func TestContext_Args(t *testing.T) { set.Bool("myflag", false, "doc") c := NewContext(nil, set, nil) set.Parse([]string{"--myflag", "bat", "baz"}) - expect(t, len(c.Args()), 2) + expect(t, c.Args().Len(), 2) expect(t, c.Bool("myflag"), true) } diff --git a/flag.go b/flag.go index 067e9ee..dd07d27 100644 --- a/flag.go +++ b/flag.go @@ -17,22 +17,22 @@ const defaultPlaceholder = "value" var slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) // BashCompletionFlag enables bash-completion for all commands and subcommands -var BashCompletionFlag = BoolFlag{ +var BashCompletionFlag = &BoolFlag{ Name: "generate-bash-completion", Hidden: true, } // VersionFlag prints the version for the application -var VersionFlag = BoolFlag{ +var VersionFlag = &BoolFlag{ Name: "version", Aliases: []string{"v"}, Usage: "print the version", } // HelpFlag prints the help for all commands and subcommands. -// Set to the zero value (BoolFlag{}) to disable the flag. The subcommand +// Set to nil to disable the flag. The subcommand // will still be added unless HideHelp is set to true. -var HelpFlag = BoolFlag{ +var HelpFlag = &BoolFlag{ Name: "help", Aliases: []string{"h"}, Usage: "show help", @@ -85,13 +85,13 @@ type GenericFlag struct { // String returns the string representation of the generic flag to display the // help text to the user (uses the String() method of the generic flag to show // the value) -func (f GenericFlag) String() string { +func (f *GenericFlag) String() string { return FlagStringer(f) } // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag -func (f GenericFlag) Apply(set *flag.FlagSet) { +func (f *GenericFlag) Apply(set *flag.FlagSet) { val := f.Value if f.EnvVars != nil { for _, envVar := range f.EnvVars { @@ -109,7 +109,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { } // Names returns the names of a flag. -func (f GenericFlag) Names() []string { +func (f *GenericFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -170,12 +170,12 @@ type StringSliceFlag struct { } // String returns the usage -func (f StringSliceFlag) String() string { +func (f *StringSliceFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f StringSliceFlag) Apply(set *flag.FlagSet) { +func (f *StringSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -201,7 +201,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { } // Names returns the name of a flag. -func (f StringSliceFlag) Names() []string { +func (f *StringSliceFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -277,12 +277,12 @@ type IntSliceFlag struct { } // String returns the usage -func (f IntSliceFlag) String() string { +func (f *IntSliceFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f IntSliceFlag) Apply(set *flag.FlagSet) { +func (f *IntSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -311,7 +311,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f IntSliceFlag) Names() []string { +func (f *IntSliceFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -327,12 +327,12 @@ type BoolFlag struct { } // String returns a readable representation of this value (for usage defaults) -func (f BoolFlag) String() string { +func (f *BoolFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f BoolFlag) Apply(set *flag.FlagSet) { +func (f *BoolFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -356,7 +356,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f BoolFlag) Names() []string { +func (f *BoolFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -372,12 +372,12 @@ type StringFlag struct { } // String returns the usage -func (f StringFlag) String() string { +func (f *StringFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f StringFlag) Apply(set *flag.FlagSet) { +func (f *StringFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -398,7 +398,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f StringFlag) Names() []string { +func (f *StringFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -415,12 +415,12 @@ type IntFlag struct { } // String returns the usage -func (f IntFlag) String() string { +func (f *IntFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f IntFlag) Apply(set *flag.FlagSet) { +func (f *IntFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -444,7 +444,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f IntFlag) Names() []string { +func (f *IntFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -461,12 +461,12 @@ type DurationFlag struct { } // String returns a readable representation of this value (for usage defaults) -func (f DurationFlag) String() string { +func (f *DurationFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f DurationFlag) Apply(set *flag.FlagSet) { +func (f *DurationFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -490,7 +490,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f DurationFlag) Names() []string { +func (f *DurationFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } @@ -507,12 +507,12 @@ type Float64Flag struct { } // String returns the usage -func (f Float64Flag) String() string { +func (f *Float64Flag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f Float64Flag) Apply(set *flag.FlagSet) { +func (f *Float64Flag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { envVar = strings.TrimSpace(envVar) @@ -535,14 +535,14 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { } // Names returns the name of the flag. -func (f Float64Flag) Names() []string { +func (f *Float64Flag) Names() []string { return append([]string{f.Name}, f.Aliases...) } func visibleFlags(fl []Flag) []Flag { visible := []Flag{} for _, flag := range fl { - if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { + if !flagValue(flag).FieldByName("Hidden").Bool() { visible = append(visible, flag) } } @@ -644,10 +644,10 @@ func stringifyFlag(f Flag) string { fv := flagValue(f) switch f.(type) { - case IntSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(IntSliceFlag))) - case StringSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(StringSliceFlag))) + case *IntSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(*IntSliceFlag))) + case *StringSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(*StringSliceFlag))) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) @@ -679,7 +679,7 @@ func stringifyFlag(f Flag) string { fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } -func stringifyIntSliceFlag(f IntSliceFlag) string { +func stringifyIntSliceFlag(f *IntSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { @@ -690,7 +690,7 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) } -func stringifyStringSliceFlag(f StringSliceFlag) string { +func stringifyStringSliceFlag(f *StringSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, s := range f.Value.Value() { @@ -717,3 +717,13 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault) } + +func hasFlag(flags []Flag, fl Flag) bool { + for _, existing := range flags { + if fl == existing { + return true + } + } + + return false +} diff --git a/flag_test.go b/flag_test.go index 8ff64fb..15166c7 100644 --- a/flag_test.go +++ b/flag_test.go @@ -20,7 +20,7 @@ var boolFlagTests = []struct { func TestBoolFlagHelpOutput(t *testing.T) { for _, test := range boolFlagTests { - flag := BoolFlag{Name: test.name} + flag := &BoolFlag{Name: test.name} output := flag.String() if output != test.expected { @@ -46,7 +46,7 @@ var stringFlagTests = []struct { func TestStringFlagHelpOutput(t *testing.T) { for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + flag := &StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} output := flag.String() if output != test.expected { @@ -59,7 +59,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_FOO", "derp") for _, test := range stringFlagTests { - flag := StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} + flag := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} output := flag.String() expectedSuffix := " [$APP_FOO]" @@ -87,7 +87,7 @@ var stringSliceFlagTests = []struct { func TestStringSliceFlagHelpOutput(t *testing.T) { for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -100,7 +100,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_QWWX", "11,4") for _, test := range stringSliceFlagTests { - flag := StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} + flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} output := flag.String() expectedSuffix := " [$APP_QWWX]" @@ -123,7 +123,7 @@ var intFlagTests = []struct { func TestIntFlagHelpOutput(t *testing.T) { for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, Value: 9} + flag := &IntFlag{Name: test.name, Value: 9} output := flag.String() if output != test.expected { @@ -136,7 +136,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range intFlagTests { - flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -159,7 +159,7 @@ var durationFlagTests = []struct { func TestDurationFlagHelpOutput(t *testing.T) { for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, Value: 1 * time.Second} + flag := &DurationFlag{Name: test.name, Value: 1 * time.Second} output := flag.String() if output != test.expected { @@ -172,7 +172,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2h3m6s") for _, test := range durationFlagTests { - flag := DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -198,7 +198,7 @@ var intSliceFlagTests = []struct { func TestIntSliceFlagHelpOutput(t *testing.T) { for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -211,7 +211,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_SMURF", "42,3") for _, test := range intSliceFlagTests { - flag := IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} + flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() expectedSuffix := " [$APP_SMURF]" @@ -234,7 +234,7 @@ var float64FlagTests = []struct { func TestFloat64FlagHelpOutput(t *testing.T) { for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, Value: float64(0.1)} + flag := &Float64Flag{Name: test.name, Value: float64(0.1)} output := flag.String() if output != test.expected { @@ -247,7 +247,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAZ", "99.4") for _, test := range float64FlagTests { - flag := Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} + flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} output := flag.String() expectedSuffix := " [$APP_BAZ]" @@ -271,7 +271,7 @@ var genericFlagTests = []struct { func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} + flag := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} output := flag.String() if output != test.expected { @@ -284,7 +284,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_ZAP", "3") for _, test := range genericFlagTests { - flag := GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} + flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} output := flag.String() expectedSuffix := " [$APP_ZAP]" @@ -300,7 +300,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { func TestParseMultiString(t *testing.T) { (&App{ Flags: []Flag{ - StringFlag{Name: "serve", Aliases: []string{"s"}}, + &StringFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.String("serve") != "10" { @@ -318,7 +318,7 @@ func TestParseDestinationString(t *testing.T) { var dest string a := App{ Flags: []Flag{ - StringFlag{ + &StringFlag{ Name: "dest", Destination: &dest, }, @@ -338,7 +338,7 @@ func TestParseMultiStringFromEnv(t *testing.T) { os.Setenv("APP_COUNT", "20") (&App{ Flags: []Flag{ - StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, + &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -357,7 +357,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { os.Setenv("APP_COUNT", "20") (&App{ Flags: []Flag{ - StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}}, + &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}}, }, Action: func(ctx *Context) error { if ctx.String("count") != "20" { @@ -374,7 +374,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -392,7 +392,7 @@ func TestParseMultiStringSlice(t *testing.T) { func TestParseMultiStringSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { expected := []string{"10", "20"} @@ -410,7 +410,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, + &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"9", "2"}) { @@ -430,7 +430,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"APP_INTERVALS"}}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -450,7 +450,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"APP_INTERVALS"}}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -470,7 +470,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -490,7 +490,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, + &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { @@ -507,7 +507,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { func TestParseMultiInt(t *testing.T) { a := App{ Flags: []Flag{ - IntFlag{Name: "serve", Aliases: []string{"s"}}, + &IntFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Int("serve") != 10 { @@ -526,7 +526,7 @@ func TestParseDestinationInt(t *testing.T) { var dest int a := App{ Flags: []Flag{ - IntFlag{ + &IntFlag{ Name: "dest", Destination: &dest, }, @@ -546,7 +546,7 @@ func TestParseMultiIntFromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := App{ Flags: []Flag{ - IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, + &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -566,7 +566,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "10") a := App{ Flags: []Flag{ - IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, + &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Int("timeout") != 10 { @@ -584,7 +584,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { func TestParseMultiIntSlice(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -601,7 +601,7 @@ func TestParseMultiIntSlice(t *testing.T) { func TestParseMultiIntSliceWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { @@ -618,7 +618,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{9, 2}) { @@ -638,7 +638,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"APP_INTERVALS"}}, + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -658,7 +658,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(1, 2, 5), EnvVars: []string{"APP_INTERVALS"}}, + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(1, 2, 5), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -678,7 +678,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, + &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { @@ -695,7 +695,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { func TestParseMultiFloat64(t *testing.T) { a := App{ Flags: []Flag{ - Float64Flag{Name: "serve", Aliases: []string{"s"}}, + &Float64Flag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Float64("serve") != 10.2 { @@ -714,7 +714,7 @@ func TestParseDestinationFloat64(t *testing.T) { var dest float64 a := App{ Flags: []Flag{ - Float64Flag{ + &Float64Flag{ Name: "dest", Destination: &dest, }, @@ -734,7 +734,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := App{ Flags: []Flag{ - Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, + &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { @@ -754,7 +754,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { os.Setenv("APP_TIMEOUT_SECONDS", "15.5") a := App{ Flags: []Flag{ - Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, + &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, Action: func(ctx *Context) error { if ctx.Float64("timeout") != 15.5 { @@ -772,7 +772,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { func TestParseMultiBool(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &BoolFlag{Name: "serve", Aliases: []string{"s"}}, }, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { @@ -791,7 +791,7 @@ func TestParseDestinationBool(t *testing.T) { var dest bool a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Destination: &dest, }, @@ -811,7 +811,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := App{ Flags: []Flag{ - BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -831,7 +831,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "1") a := App{ Flags: []Flag{ - BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != true { @@ -849,7 +849,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { func TestParseMultiBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, + &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, }, Action: func(ctx *Context) error { if ctx.Bool("implode") { @@ -869,7 +869,7 @@ func TestParseDestinationBoolTrue(t *testing.T) { a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "dest", Value: true, Destination: &dest, @@ -890,7 +890,7 @@ func TestParseMultiBoolTrueFromEnv(t *testing.T) { os.Setenv("APP_DEBUG", "0") a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "debug", Aliases: []string{"d"}, Value: true, @@ -915,7 +915,7 @@ func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { os.Setenv("APP_DEBUG", "0") a := App{ Flags: []Flag{ - BoolFlag{ + &BoolFlag{ Name: "debug", Aliases: []string{"d"}, Value: true, @@ -956,7 +956,7 @@ func (p *Parser) String() string { func TestParseGeneric(t *testing.T) { a := App{ Flags: []Flag{ - GenericFlag{Name: "serve", Aliases: []string{"s"}, Value: &Parser{}}, + &GenericFlag{Name: "serve", Aliases: []string{"s"}, Value: &Parser{}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { @@ -976,7 +976,7 @@ func TestParseGenericFromEnv(t *testing.T) { os.Setenv("APP_SERVE", "20,30") a := App{ Flags: []Flag{ - GenericFlag{ + &GenericFlag{ Name: "serve", Aliases: []string{"s"}, Value: &Parser{}, @@ -1001,7 +1001,7 @@ func TestParseGenericFromEnvCascade(t *testing.T) { os.Setenv("APP_FOO", "99,2000") a := App{ Flags: []Flag{ - GenericFlag{ + &GenericFlag{ Name: "foos", Value: &Parser{}, EnvVars: []string{"COMPAT_FOO", "APP_FOO"}, diff --git a/help.go b/help.go index 2675289..d02f7fd 100644 --- a/help.go +++ b/help.go @@ -74,7 +74,7 @@ OPTIONS: {{end}}{{end}} ` -var helpCommand = Command{ +var helpCommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -90,7 +90,7 @@ var helpCommand = Command{ }, } -var helpSubcommand = Command{ +var helpSubcommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -158,7 +158,15 @@ func ShowCommandHelp(ctx *Context, command string) error { // ShowSubcommandHelp prints help for the given subcommand func ShowSubcommandHelp(c *Context) error { - return ShowCommandHelp(c, c.Command.Name) + if c == nil { + return nil + } + + if c.Command != nil { + return ShowCommandHelp(c, c.Command.Name) + } + + return ShowCommandHelp(c, "") } // ShowVersion prints the version number of the App diff --git a/help_test.go b/help_test.go index 9ca4c0d..4d1dedc 100644 --- a/help_test.go +++ b/help_test.go @@ -59,7 +59,7 @@ func Test_Help_Custom_Flags(t *testing.T) { HelpFlag = oldFlag }() - HelpFlag = BoolFlag{ + HelpFlag = &BoolFlag{ Name: "help", Aliases: []string{"x"}, Usage: "show help", @@ -67,7 +67,7 @@ func Test_Help_Custom_Flags(t *testing.T) { app := App{ Flags: []Flag{ - BoolFlag{Name: "foo", Aliases: []string{"h"}}, + &BoolFlag{Name: "foo", Aliases: []string{"h"}}, }, Action: func(ctx *Context) error { if ctx.Bool("h") != true { @@ -90,7 +90,7 @@ func Test_Version_Custom_Flags(t *testing.T) { VersionFlag = oldFlag }() - VersionFlag = BoolFlag{ + VersionFlag = &BoolFlag{ Name: "version", Aliases: []string{"V"}, Usage: "show version", @@ -98,7 +98,7 @@ func Test_Version_Custom_Flags(t *testing.T) { app := App{ Flags: []Flag{ - BoolFlag{Name: "foo", Aliases: []string{"v"}}, + &BoolFlag{Name: "foo", Aliases: []string{"v"}}, }, Action: func(ctx *Context) error { if ctx.Bool("v") != true { @@ -173,7 +173,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { func TestShowAppHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob"}, @@ -195,7 +195,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) { func TestShowCommandHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob", "bork"}, @@ -221,7 +221,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) { func TestShowSubcommandHelp_CommandAliases(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Aliases: []string{"fr", "frob", "bork"}, @@ -243,7 +243,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Action: func(ctx *Context) error { From 6d6f7da978ced32210df3312cbf0d2d4bf79fa1b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2016 18:30:07 -0400 Subject: [PATCH 019/158] Merging from master/v1 --- CHANGELOG.md | 52 +++++++++++++++++----------------- README.md | 52 +++++++++++++++++++++------------- altsrc/flag.go | 2 +- altsrc/flag_test.go | 2 +- altsrc/input_source_context.go | 2 +- altsrc/map_input_source.go | 2 +- altsrc/yaml_command_test.go | 2 +- altsrc/yaml_file_loader.go | 2 +- runtests | 2 +- 9 files changed, 65 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c20a2..541bdbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,7 @@ makes it easier to script around apps built using `cli` since they can trust that a 0 exit code indicated a successful execution. - cleanups based on [Go Report Card - feedback](https://goreportcard.com/report/github.com/codegangsta/cli) + feedback](https://goreportcard.com/report/github.com/urfave/cli) ## [1.16.0] - 2016-05-02 ### Added @@ -317,28 +317,28 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/codegangsta/cli/compare/v1.17.0...HEAD -[1.17.0]: https://github.com/codegangsta/cli/compare/v1.16.0...v1.17.0 -[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0 -[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0 -[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0 -[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0 -[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0 -[1.11.1]: https://github.com/codegangsta/cli/compare/v1.11.0...v1.11.1 -[1.11.0]: https://github.com/codegangsta/cli/compare/v1.10.2...v1.11.0 -[1.10.2]: https://github.com/codegangsta/cli/compare/v1.10.1...v1.10.2 -[1.10.1]: https://github.com/codegangsta/cli/compare/v1.10.0...v1.10.1 -[1.10.0]: https://github.com/codegangsta/cli/compare/v1.9.0...v1.10.0 -[1.9.0]: https://github.com/codegangsta/cli/compare/v1.8.0...v1.9.0 -[1.8.0]: https://github.com/codegangsta/cli/compare/v1.7.1...v1.8.0 -[1.7.1]: https://github.com/codegangsta/cli/compare/v1.7.0...v1.7.1 -[1.7.0]: https://github.com/codegangsta/cli/compare/v1.6.0...v1.7.0 -[1.6.0]: https://github.com/codegangsta/cli/compare/v1.5.0...v1.6.0 -[1.5.0]: https://github.com/codegangsta/cli/compare/v1.4.1...v1.5.0 -[1.4.1]: https://github.com/codegangsta/cli/compare/v1.4.0...v1.4.1 -[1.4.0]: https://github.com/codegangsta/cli/compare/v1.3.1...v1.4.0 -[1.3.1]: https://github.com/codegangsta/cli/compare/v1.3.0...v1.3.1 -[1.3.0]: https://github.com/codegangsta/cli/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/codegangsta/cli/compare/v1.1.0...v1.2.0 -[1.1.0]: https://github.com/codegangsta/cli/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/codegangsta/cli/compare/v0.1.0...v1.0.0 +[Unreleased]: https://github.com/urfave/cli/compare/v1.17.0...HEAD +[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 +[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 +[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 +[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0 +[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0 +[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0 +[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1 +[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0 +[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2 +[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1 +[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0 +[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0 +[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0 +[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0 +[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0 diff --git a/README.md b/README.md index 0a1f624..52124e4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ -[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) -[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) -[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) -[![Go Report Card](https://goreportcard.com/badge/codegangsta/cli)](https://goreportcard.com/report/codegangsta/cli) -[![top level coverage](https://gocover.io/_badge/github.com/codegangsta/cli?0 "top level coverage")](http://gocover.io/github.com/codegangsta/cli) / -[![altsrc coverage](https://gocover.io/_badge/github.com/codegangsta/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/codegangsta/cli/altsrc) +[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) +[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) +[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / +[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) # cli +**Notice:** This is the library formally known as +`github.com/codegangsta/cli` -- Github will automatically redirect requests +to this repository, but we recommend updating your references for clarity. + cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. ## Overview @@ -25,7 +29,7 @@ instructions](http://golang.org/doc/install.html). To install cli, simply run: ``` -$ go get github.com/codegangsta/cli +$ go get github.com/urfave/cli ``` Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: @@ -44,13 +48,13 @@ that, please use whatever version pinning of your preference, such as via `gopkg.in`: ``` -$ go get gopkg.in/codegangsta/cli.v2 +$ go get gopkg.in/urfave/cli.v2 ``` ``` go ... import ( - "gopkg.in/codegangsta/cli.v2" // imports as package "cli" + "gopkg.in/urfave/cli.v2" // imports as package "cli" ) ... ``` @@ -62,13 +66,13 @@ to avoid any unexpected compatibility pains once `v2` becomes `master`, then pinning to the `v1` branch is an acceptable option, e.g.: ``` -$ go get gopkg.in/codegangsta/cli.v1 +$ go get gopkg.in/urfave/cli.v1 ``` ``` go ... import ( - "gopkg.in/codegangsta/cli.v1" // imports as package "cli" + "gopkg.in/urfave/cli.v1" // imports as package "cli" ) ... ``` @@ -82,7 +86,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -102,7 +106,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -136,7 +140,7 @@ import ( "fmt" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -254,7 +258,7 @@ app.Action = func(c *cli.Context) error { ... ``` -See full list of flags at http://godoc.org/github.com/codegangsta/cli +See full list of flags at http://godoc.org/github.com/urfave/cli #### Placeholder Values @@ -473,7 +477,7 @@ package main import ( "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -573,7 +577,7 @@ import ( "io" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func main() { @@ -622,8 +626,16 @@ VERSION: ## Contribution Guidelines -Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. +Feel free to put up a pull request to fix a bug or maybe add a feature. I will +give it a code review and make sure that it does not break backwards +compatibility. If I or any other collaborators agree that it is in line with +the vision of the project, we will work with you to get the code into +a mergeable state and merge it into the master branch. -If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. +If you have contributed something significant to the project, we will most +likely add you as a collaborator. As a collaborator you are given the ability +to merge others pull requests. It is very important that new code does not +break existing code, so be careful about what code you do choose to merge. -If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. +If you feel like you have contributed to the project but have not yet been +added as a collaborator, we probably forgot to add you, please open an issue. diff --git a/altsrc/flag.go b/altsrc/flag.go index 6c68bf5..acf68ae 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -6,7 +6,7 @@ import ( "os" "strconv" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // FlagInputSourceExtension is an extension interface of cli.Flag that diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 0b188da..5acdcfd 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) type testApplyInputSource struct { diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 0f391b2..56603cf 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -3,7 +3,7 @@ package altsrc import ( "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // InputSourceContext is an interface used to allow diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index a7fc628..68a749c 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) // MapInputSource implements InputSourceContext to return diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index d1a15d7..02a51cb 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/codegangsta/cli" + "github.com/urfave/cli" ) func TestCommandYamlFileTest(t *testing.T) { diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 01797ad..b4e3365 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -12,7 +12,7 @@ import ( "net/url" "os" - "github.com/codegangsta/cli" + "github.com/urfave/cli" "gopkg.in/yaml.v2" ) diff --git a/runtests b/runtests index 9288f11..b0fd06f 100755 --- a/runtests +++ b/runtests @@ -10,7 +10,7 @@ from subprocess import check_call, check_output PACKAGE_NAME = os.environ.get( - 'CLI_PACKAGE_NAME', 'github.com/codegangsta/cli' + 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' ) From 61710ff108fd7c860d0ef572ac71ecab8f06db3c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:15:35 -0400 Subject: [PATCH 020/158] Make slice wrapping pattern more consistent and move *Args out into its own file --- app.go | 10 ++++---- app_test.go | 12 +++++----- args.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++ category.go | 67 +++++++++++++++++++++++++++++------------------------ command.go | 2 +- context.go | 53 ------------------------------------------ errors.go | 10 ++++---- 7 files changed, 113 insertions(+), 101 deletions(-) create mode 100644 args.go diff --git a/app.go b/app.go index 68a92a2..f6470b7 100644 --- a/app.go +++ b/app.go @@ -115,7 +115,7 @@ func (a *App) Setup() { a.categories = NewCommandCategories() for _, command := range a.Commands { - a.categories = a.categories.AddCommand(command.Category, command) + a.categories.AddCommand(command.Category, command) } sort.Sort(a.categories) @@ -349,12 +349,10 @@ func (a *App) Categories() *CommandCategories { // Hidden=false func (a *App) VisibleCategories() []*CommandCategory { ret := []*CommandCategory{} - for _, category := range a.categories.Categories { + for _, category := range a.categories.Categories() { if visible := func() *CommandCategory { - for _, command := range category.Commands { - if !command.Hidden { - return category - } + if len(category.VisibleCommands()) > 0 { + return category } return nil }(); visible != nil { diff --git a/app_test.go b/app_test.go index 3c79766..7bac196 100644 --- a/app_test.go +++ b/app_test.go @@ -1143,17 +1143,17 @@ func TestApp_Run_Categories(t *testing.T) { app.Run([]string{"categories"}) expect := &CommandCategories{ - Categories: []*CommandCategory{ + slice: []*CommandCategory{ { Name: "1", - Commands: []*Command{ + commands: []*Command{ app.Commands[0], app.Commands[1], }, }, { Name: "2", - Commands: []*Command{ + commands: []*Command{ app.Commands[2], }, }, @@ -1196,13 +1196,13 @@ func TestApp_VisibleCategories(t *testing.T) { expected := []*CommandCategory{ { Name: "2", - Commands: []*Command{ + commands: []*Command{ app.Commands[1], }, }, { Name: "3", - Commands: []*Command{ + commands: []*Command{ app.Commands[2], }, }, @@ -1236,7 +1236,7 @@ func TestApp_VisibleCategories(t *testing.T) { expected = []*CommandCategory{ { Name: "3", - Commands: []*Command{ + commands: []*Command{ app.Commands[2], }, }, diff --git a/args.go b/args.go new file mode 100644 index 0000000..ab44f43 --- /dev/null +++ b/args.go @@ -0,0 +1,60 @@ +package cli + +import "errors" + +var ( + argsRangeErr = errors.New("index out of range") +) + +// Args wraps a string slice with some convenience methods +type Args struct { + slice []string +} + +// Get returns the nth argument, or else a blank string +func (a *Args) Get(n int) string { + if len(a.slice) > n { + return a.slice[n] + } + return "" +} + +// First returns the first argument, or else a blank string +func (a *Args) First() string { + return a.Get(0) +} + +// Tail returns the rest of the arguments (not the first one) +// or else an empty string slice +func (a *Args) Tail() []string { + if a.Len() >= 2 { + return a.slice[1:] + } + return []string{} +} + +// Len returns the length of the wrapped slice +func (a *Args) Len() int { + return len(a.slice) +} + +// Present checks if there are any arguments present +func (a *Args) Present() bool { + return a.Len() != 0 +} + +// Swap swaps arguments at the given indexes +func (a *Args) Swap(from, to int) error { + if from >= a.Len() || to >= a.Len() { + return argsRangeErr + } + a.slice[from], a.slice[to] = a.slice[to], a.slice[from] + return nil +} + +// Slice returns a copy of the internal slice +func (a *Args) Slice() []string { + ret := make([]string, len(a.slice)) + copy(ret, a.slice) + return ret +} diff --git a/category.go b/category.go index 9fb00f7..5899338 100644 --- a/category.go +++ b/category.go @@ -1,49 +1,56 @@ package cli -// CommandCategories is a slice of *CommandCategory. +// CommandCategories wraps a slice of *CommandCategory. type CommandCategories struct { - Categories []*CommandCategory + slice []*CommandCategory } func NewCommandCategories() *CommandCategories { - return &CommandCategories{Categories: []*CommandCategory{}} + return &CommandCategories{slice: []*CommandCategory{}} +} + +func (c *CommandCategories) Less(i, j int) bool { + return c.slice[i].Name < c.slice[j].Name +} + +func (c *CommandCategories) Len() int { + return len(c.slice) +} + +func (c *CommandCategories) Swap(i, j int) { + c.slice[i], c.slice[j] = c.slice[j], c.slice[i] +} + +// AddCommand adds a command to a category, creating a new category if necessary. +func (c *CommandCategories) AddCommand(category string, command *Command) { + for _, commandCategory := range c.slice { + if commandCategory.Name == category { + commandCategory.commands = append(commandCategory.commands, command) + return + } + } + c.slice = append(c.slice, + &CommandCategory{Name: category, commands: []*Command{command}}) +} + +// Categories returns a copy of the category slice +func (c *CommandCategories) Categories() []*CommandCategory { + ret := make([]*CommandCategory, len(c.slice)) + copy(ret, c.slice) + return ret } // CommandCategory is a category containing commands. type CommandCategory struct { - Name string - Commands []*Command -} + Name string -func (c *CommandCategories) Less(i, j int) bool { - return c.Categories[i].Name < c.Categories[j].Name -} - -func (c *CommandCategories) Len() int { - return len(c.Categories) -} - -func (c *CommandCategories) Swap(i, j int) { - c.Categories[i], c.Categories[j] = c.Categories[j], c.Categories[i] -} - -// AddCommand adds a command to a category. -func (c *CommandCategories) AddCommand(category string, command *Command) *CommandCategories { - for _, commandCategory := range c.Categories { - if commandCategory.Name == category { - commandCategory.Commands = append(commandCategory.Commands, command) - return c - } - } - c.Categories = append(c.Categories, - &CommandCategory{Name: category, Commands: []*Command{command}}) - return c + commands []*Command } // VisibleCommands returns a slice of the Commands with Hidden=false func (c *CommandCategory) VisibleCommands() []*Command { ret := []*Command{} - for _, command := range c.Commands { + for _, command := range c.commands { if !command.Hidden { ret = append(ret, command) } diff --git a/command.go b/command.go index fe31a36..b732a8e 100644 --- a/command.go +++ b/command.go @@ -195,7 +195,7 @@ func (c *Command) startApp(ctx *Context) error { app.categories = NewCommandCategories() for _, command := range c.Subcommands { - app.categories = app.categories.AddCommand(command.Category, command) + app.categories.AddCommand(command.Category, command) } sort.Sort(app.categories) diff --git a/context.go b/context.go index 0728da9..39aa9be 100644 --- a/context.go +++ b/context.go @@ -157,59 +157,6 @@ func (c *Context) NArg() int { return c.Args().Len() } -// Args wraps a string slice with some convenience methods -type Args struct { - slice []string -} - -// Get returns the nth argument, or else a blank string -func (a *Args) Get(n int) string { - if len(a.slice) > n { - return a.slice[n] - } - return "" -} - -// First returns the first argument, or else a blank string -func (a *Args) First() string { - return a.Get(0) -} - -// Tail returns the rest of the arguments (not the first one) -// or else an empty string slice -func (a *Args) Tail() []string { - if a.Len() >= 2 { - return a.slice[1:] - } - return []string{} -} - -// Len returns the length of the wrapped slice -func (a *Args) Len() int { - return len(a.slice) -} - -// Present checks if there are any arguments present -func (a *Args) Present() bool { - return a.Len() != 0 -} - -// Swap swaps arguments at the given indexes -func (a *Args) Swap(from, to int) error { - if from >= a.Len() || to >= a.Len() { - return errors.New("index out of range") - } - a.slice[from], a.slice[to] = a.slice[to], a.slice[from] - return nil -} - -// Slice returns a copy of the internal slice -func (a *Args) Slice() []string { - ret := make([]string, len(a.slice)) - copy(ret, a.slice) - return ret -} - func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { for _, c := range ctx.Lineage() { if f := c.flagSet.Lookup(name); f != nil { diff --git a/errors.go b/errors.go index ea551be..a8091f4 100644 --- a/errors.go +++ b/errors.go @@ -16,18 +16,18 @@ var ErrWriter io.Writer = os.Stderr // MultiError is an error that wraps multiple errors. type MultiError struct { - Errors []error + errors []error } // NewMultiError creates a new MultiError. Pass in one or more errors. func NewMultiError(err ...error) MultiError { - return MultiError{Errors: err} + return MultiError{errors: err} } // Error implents the error interface. func (m MultiError) Error() string { - errs := make([]string, len(m.Errors)) - for i, err := range m.Errors { + errs := make([]string, len(m.errors)) + for i, err := range m.errors { errs[i] = err.Error() } @@ -85,7 +85,7 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.Errors { + for _, merr := range multiErr.errors { HandleExitCoder(merr) } } From ec05a8d31b5a959b0ece090bae3b205c396dabf9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 20:55:03 -0400 Subject: [PATCH 021/158] Ensure flag "Name" field values are un-stringly-fied to (hopefully) help with bug triage & pinpointing usage issues since ripping out stringly typed "Name". --- flag.go | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/flag.go b/flag.go index 067e9ee..43e806f 100644 --- a/flag.go +++ b/flag.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "reflect" + "regexp" "runtime" "strconv" "strings" @@ -14,7 +15,11 @@ import ( const defaultPlaceholder = "value" -var slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) +var ( + slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) + + commaWhitespace = regexp.MustCompile("[, ]+.*") +) // BashCompletionFlag enables bash-completion for all commands and subcommands var BashCompletionFlag = BoolFlag{ @@ -95,7 +100,6 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { val := f.Value if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { val.Set(envVal) break @@ -110,7 +114,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { // Names returns the names of a flag. func (f GenericFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // StringSlice wraps a []string to satisfy flag.Value @@ -178,7 +182,6 @@ func (f StringSliceFlag) String() string { func (f StringSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { newVal := NewStringSlice() for _, s := range strings.Split(envVal, ",") { @@ -202,7 +205,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { // Names returns the name of a flag. func (f StringSliceFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // IntSlice wraps an []int to satisfy flag.Value @@ -285,7 +288,6 @@ func (f IntSliceFlag) String() string { func (f IntSliceFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { newVal := NewIntSlice() for _, s := range strings.Split(envVal, ",") { @@ -312,7 +314,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f IntSliceFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // BoolFlag is a switch that defaults to false @@ -335,7 +337,6 @@ func (f BoolFlag) String() string { func (f BoolFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValBool, err := strconv.ParseBool(envVal) if err == nil { @@ -357,7 +358,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f BoolFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // StringFlag represents a flag that takes as string value @@ -380,7 +381,6 @@ func (f StringFlag) String() string { func (f StringFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { f.Value = envVal break @@ -399,7 +399,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f StringFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // IntFlag is a flag that takes an integer @@ -423,7 +423,6 @@ func (f IntFlag) String() string { func (f IntFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err == nil { @@ -445,7 +444,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f IntFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // DurationFlag is a flag that takes a duration specified in Go's duration @@ -469,7 +468,6 @@ func (f DurationFlag) String() string { func (f DurationFlag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValDuration, err := time.ParseDuration(envVal) if err == nil { @@ -491,7 +489,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f DurationFlag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } // Float64Flag is a flag that takes an float value @@ -515,7 +513,6 @@ func (f Float64Flag) String() string { func (f Float64Flag) Apply(set *flag.FlagSet) { if f.EnvVars != nil { for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { envValFloat, err := strconv.ParseFloat(envVal, 10) if err == nil { @@ -536,7 +533,7 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { // Names returns the name of the flag. func (f Float64Flag) Names() []string { - return append([]string{f.Name}, f.Aliases...) + return flagNames(f) } func visibleFlags(fl []Flag) []Flag { @@ -610,6 +607,19 @@ func withEnvHint(envVars []string, str string) string { return str + envText } +func flagNames(f Flag) []string { + ret := []string{} + + name := flagStringField(f, "Name") + aliases := flagStringSliceField(f, "Aliases") + + for _, part := range append([]string{name}, aliases...) { + ret = append(ret, commaWhitespace.ReplaceAllString(part, "")) + } + + return ret +} + func flagStringSliceField(f Flag, name string) []string { fv := flagValue(f) field := fv.FieldByName(name) From 8f25dbb615a179cf7c8e2f32a051cfd5ba684f30 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 22:00:59 -0400 Subject: [PATCH 022/158] Ensure all flag aliases are set when Destination given and extend "Incorrect Usage" message to show the error. Closes #430 --- app.go | 4 +-- command.go | 2 +- flag.go | 12 ++++---- flag_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index 622c7a1..2cc50ad 100644 --- a/app.go +++ b/app.go @@ -164,7 +164,7 @@ func (a *App) Run(arguments []string) (err error) { HandleExitCoder(err) return err } - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + fmt.Fprintf(a.Writer, "Incorrect Usage: %s\n\n", err) ShowAppHelp(context) return err } @@ -273,7 +273,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + fmt.Fprintf(a.Writer, "Incorrect Usage: %s\n\n", err) ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index c2f39d0..c578ce7 100644 --- a/command.go +++ b/command.go @@ -97,7 +97,7 @@ func (c Command) Run(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage: %s\n\n", err) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) return err diff --git a/flag.go b/flag.go index 067e9ee..3d338e8 100644 --- a/flag.go +++ b/flag.go @@ -104,7 +104,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { } for _, name := range f.Names() { - set.Var(f.Value, name, f.Usage) + set.Var(val, name, f.Usage) } } @@ -349,7 +349,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.BoolVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Bool(name, f.Value, f.Usage) } @@ -391,7 +391,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.StringVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.String(name, f.Value, f.Usage) } @@ -437,7 +437,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.IntVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Int(name, f.Value, f.Usage) } @@ -483,7 +483,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.DurationVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.Duration(name, f.Value, f.Usage) } @@ -528,7 +528,7 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.Float64Var(f.Destination, name, f.Value, f.Usage) - return + continue } set.Float64(name, f.Value, f.Usage) } diff --git a/flag_test.go b/flag_test.go index 8ff64fb..eb3661f 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1,6 +1,7 @@ package cli import ( + "flag" "fmt" "os" "reflect" @@ -29,6 +30,17 @@ func TestBoolFlagHelpOutput(t *testing.T) { } } +func TestBoolFlagApply_SetsAllNames(t *testing.T) { + v := false + fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--wat", "-W", "--huh"}) + expect(t, err, nil) + expect(t, v, true) +} + var stringFlagTests = []struct { name string aliases []string @@ -72,6 +84,17 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestStringFlagApply_SetsAllNames(t *testing.T) { + v := "mmm" + fl := StringFlag{Name: "hay", Aliases: []string{"H", "hayyy"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--hay", "u", "-H", "yuu", "--hayyy", "YUUUU"}) + expect(t, err, nil) + expect(t, v, "YUUUU") +} + var stringSliceFlagTests = []struct { name string aliases []string @@ -113,6 +136,15 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestStringSliceFlagApply_SetsAllNames(t *testing.T) { + fl := StringSliceFlag{Name: "goat", Aliases: []string{"G", "gooots"}} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--goat", "aaa", "-G", "bbb", "--gooots", "eeeee"}) + expect(t, err, nil) +} + var intFlagTests = []struct { name string expected string @@ -149,6 +181,17 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestIntFlagApply_SetsAllNames(t *testing.T) { + v := 3 + fl := IntFlag{Name: "banana", Aliases: []string{"B", "banannanana"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--banana", "1", "-B", "2", "--banannanana", "5"}) + expect(t, err, nil) + expect(t, v, 5) +} + var durationFlagTests = []struct { name string expected string @@ -185,6 +228,17 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestDurationFlagApply_SetsAllNames(t *testing.T) { + v := time.Second * 20 + fl := DurationFlag{Name: "howmuch", Aliases: []string{"H", "whyyy"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--howmuch", "30s", "-H", "5m", "--whyyy", "30h"}) + expect(t, err, nil) + expect(t, v, time.Hour*30) +} + var intSliceFlagTests = []struct { name string aliases []string @@ -224,6 +278,15 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { + fl := IntSliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + var float64FlagTests = []struct { name string expected string @@ -260,6 +323,17 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestFloat64FlagApply_SetsAllNames(t *testing.T) { + v := float64(99.1) + fl := Float64Flag{Name: "noodles", Aliases: []string{"N", "nurbles"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--noodles", "1.3", "-N", "11", "--nurbles", "43.33333"}) + expect(t, err, nil) + expect(t, v, float64(43.33333)) +} + var genericFlagTests = []struct { name string value Generic @@ -297,6 +371,15 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestGenericFlagApply_SetsAllNames(t *testing.T) { + fl := GenericFlag{Name: "orbs", Aliases: []string{"O", "obrs"}, Value: &Parser{}} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--orbs", "eleventy,3", "-O", "4,bloop", "--obrs", "19,s"}) + expect(t, err, nil) +} + func TestParseMultiString(t *testing.T) { (&App{ Flags: []Flag{ From a41a43b00f21883beafae537a98308ee2078efec Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2016 22:03:19 -0400 Subject: [PATCH 023/158] Switch to fmt.Fprintf for displaying usage error --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index c578ce7..8bf0d8e 100644 --- a/command.go +++ b/command.go @@ -97,7 +97,7 @@ func (c Command) Run(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintln(ctx.App.Writer, "Incorrect Usage: %s\n\n", err) + fmt.Fprintf(ctx.App.Writer, "Incorrect Usage: %s\n\n", err) fmt.Fprintln(ctx.App.Writer) ShowCommandHelp(ctx, c.Name) return err From 2b288769c74e51a5fb1b449103900069f7b42e9b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 24 May 2016 04:00:37 -0400 Subject: [PATCH 024/158] Add comment about commaWhitespace stripping of flag.Name --- flag.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flag.go b/flag.go index a1c1913..8e8bd70 100644 --- a/flag.go +++ b/flag.go @@ -614,6 +614,10 @@ func flagNames(f Flag) []string { aliases := flagStringSliceField(f, "Aliases") for _, part := range append([]string{name}, aliases...) { + // v1 -> v2 migration warning zone: + // Strip off anything after the first found comma or space, which + // *hopefully* makes it a tiny bit more obvious that unexpected behavior is + // caused by using the v1 form of stringly typed "Name". ret = append(ret, commaWhitespace.ReplaceAllString(part, "")) } From 3d75e9e7112f8e14896483dfefbf734ff05952cf Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 25 May 2016 12:05:14 -0400 Subject: [PATCH 025/158] Go with interfaces + private opaque types rather than public types that wrap slices --- app.go | 27 +++++++----------- app_test.go | 59 ++++++++++++++++++-------------------- args.go | 62 ++++++++++++++++++++-------------------- category.go | 76 +++++++++++++++++++++++++++++++++---------------- command.go | 8 +++--- context.go | 7 +++-- errors.go | 50 +++++++++++++++++++------------- errors_test.go | 6 ++-- help.go | 21 +++++++++++--- help_test.go | 8 +++--- helpers_test.go | 4 +++ 11 files changed, 187 insertions(+), 141 deletions(-) diff --git a/app.go b/app.go index 15ee743..b16f991 100644 --- a/app.go +++ b/app.go @@ -36,8 +36,8 @@ type App struct { HideHelp bool // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool - // Populate on app startup, only gettable through method Categories() - categories *CommandCategories + // Categories contains the categorized commands and is populated on app startup + Categories CommandCategories // An action to execute when the bash-completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -113,11 +113,11 @@ func (a *App) Setup() { } a.Commands = newCmds - a.categories = NewCommandCategories() + a.Categories = newCommandCategories() for _, command := range a.Commands { - a.categories.AddCommand(command.Category, command) + a.Categories.AddCommand(command.Category, command) } - sort.Sort(a.categories) + sort.Sort(a.Categories.(*commandCategories)) // append help to commands if a.Command(helpCommand.Name) == nil && !a.HideHelp { @@ -184,7 +184,7 @@ func (a *App) Run(arguments []string) (err error) { defer func() { if afterErr := a.After(context); afterErr != nil { if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -296,7 +296,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if afterErr != nil { HandleExitCoder(err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -340,17 +340,12 @@ func (a *App) Command(name string) *Command { return nil } -// Categories returns a slice containing all the categories with the commands they contain -func (a *App) Categories() *CommandCategories { - return a.categories -} - // VisibleCategories returns a slice of categories and commands that are // Hidden=false -func (a *App) VisibleCategories() []*CommandCategory { - ret := []*CommandCategory{} - for _, category := range a.categories.Categories() { - if visible := func() *CommandCategory { +func (a *App) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + for _, category := range a.Categories.Categories() { + if visible := func() CommandCategory { if len(category.VisibleCommands()) > 0 { return category } diff --git a/app_test.go b/app_test.go index 7bac196..99bd6a3 100644 --- a/app_test.go +++ b/app_test.go @@ -216,7 +216,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string - var args *Args + var args Args app := NewApp() command := &Command{ @@ -241,7 +241,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { } func TestApp_CommandWithDash(t *testing.T) { - var args *Args + var args Args app := NewApp() command := &Command{ @@ -260,7 +260,7 @@ func TestApp_CommandWithDash(t *testing.T) { } func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { - var args *Args + var args Args app := NewApp() command := &Command{ @@ -1142,25 +1142,24 @@ func TestApp_Run_Categories(t *testing.T) { app.Run([]string{"categories"}) - expect := &CommandCategories{ - slice: []*CommandCategory{ - { - Name: "1", - commands: []*Command{ - app.Commands[0], - app.Commands[1], - }, - }, - { - Name: "2", - commands: []*Command{ - app.Commands[2], - }, + expect := commandCategories([]*commandCategory{ + { + name: "1", + commands: []*Command{ + app.Commands[0], + app.Commands[1], }, }, - } - if !reflect.DeepEqual(app.Categories(), expect) { - t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) + { + name: "2", + commands: []*Command{ + app.Commands[2], + }, + }, + }) + + if !reflect.DeepEqual(app.Categories, &expect) { + t.Fatalf("expected categories %#v, to equal %#v", app.Categories, &expect) } output := buf.String() @@ -1193,15 +1192,15 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected := []*CommandCategory{ - { - Name: "2", + expected := []CommandCategory{ + &commandCategory{ + name: "2", commands: []*Command{ app.Commands[1], }, }, - { - Name: "3", + &commandCategory{ + name: "3", commands: []*Command{ app.Commands[2], }, @@ -1233,9 +1232,9 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected = []*CommandCategory{ - { - Name: "3", + expected = []CommandCategory{ + &commandCategory{ + name: "3", commands: []*Command{ app.Commands[2], }, @@ -1268,10 +1267,8 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - expected = []*CommandCategory{} - app.Setup() - expect(t, expected, app.VisibleCategories()) + expect(t, []CommandCategory{}, app.VisibleCategories()) } func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { diff --git a/args.go b/args.go index ab44f43..5618a47 100644 --- a/args.go +++ b/args.go @@ -6,55 +6,55 @@ var ( argsRangeErr = errors.New("index out of range") ) -// Args wraps a string slice with some convenience methods -type Args struct { - slice []string +type Args interface { + // Get returns the nth argument, or else a blank string + Get(n int) string + // First returns the first argument, or else a blank string + First() string + // Tail returns the rest of the arguments (not the first one) + // or else an empty string slice + Tail() []string + // Len returns the length of the wrapped slice + Len() int + // Present checks if there are any arguments present + Present() bool + // Slice returns a copy of the internal slice + Slice() []string } -// Get returns the nth argument, or else a blank string -func (a *Args) Get(n int) string { - if len(a.slice) > n { - return a.slice[n] +type args []string + +func (a *args) Get(n int) string { + if len(*a) > n { + return (*a)[n] } return "" } -// First returns the first argument, or else a blank string -func (a *Args) First() string { +func (a *args) First() string { return a.Get(0) } -// Tail returns the rest of the arguments (not the first one) -// or else an empty string slice -func (a *Args) Tail() []string { +func (a *args) Tail() []string { if a.Len() >= 2 { - return a.slice[1:] + tail := []string((*a)[1:]) + ret := make([]string, len(tail)) + copy(ret, tail) + return ret } return []string{} } -// Len returns the length of the wrapped slice -func (a *Args) Len() int { - return len(a.slice) +func (a *args) Len() int { + return len(*a) } -// Present checks if there are any arguments present -func (a *Args) Present() bool { +func (a *args) Present() bool { return a.Len() != 0 } -// Swap swaps arguments at the given indexes -func (a *Args) Swap(from, to int) error { - if from >= a.Len() || to >= a.Len() { - return argsRangeErr - } - a.slice[from], a.slice[to] = a.slice[to], a.slice[from] - return nil -} - -// Slice returns a copy of the internal slice -func (a *Args) Slice() []string { - ret := make([]string, len(a.slice)) - copy(ret, a.slice) +func (a *args) Slice() []string { + ret := make([]string, len(*a)) + copy(ret, []string(*a)) return ret } diff --git a/category.go b/category.go index 5899338..f2cb939 100644 --- a/category.go +++ b/category.go @@ -1,54 +1,80 @@ package cli -// CommandCategories wraps a slice of *CommandCategory. -type CommandCategories struct { - slice []*CommandCategory +type CommandCategories interface { + // AddCommand adds a command to a category, creating a new category if necessary. + AddCommand(category string, command *Command) + // Categories returns a copy of the category slice + Categories() []CommandCategory } -func NewCommandCategories() *CommandCategories { - return &CommandCategories{slice: []*CommandCategory{}} +type commandCategories []*commandCategory + +func newCommandCategories() CommandCategories { + ret := commandCategories([]*commandCategory{}) + return &ret } -func (c *CommandCategories) Less(i, j int) bool { - return c.slice[i].Name < c.slice[j].Name +func (c *commandCategories) Less(i, j int) bool { + return (*c)[i].Name() < (*c)[j].Name() } -func (c *CommandCategories) Len() int { - return len(c.slice) +func (c *commandCategories) Len() int { + return len(*c) } -func (c *CommandCategories) Swap(i, j int) { - c.slice[i], c.slice[j] = c.slice[j], c.slice[i] +func (c *commandCategories) Swap(i, j int) { + (*c)[i], (*c)[j] = (*c)[j], (*c)[i] } -// AddCommand adds a command to a category, creating a new category if necessary. -func (c *CommandCategories) AddCommand(category string, command *Command) { - for _, commandCategory := range c.slice { - if commandCategory.Name == category { +func (c *commandCategories) AddCommand(category string, command *Command) { + for _, commandCategory := range []*commandCategory(*c) { + if commandCategory.name == category { commandCategory.commands = append(commandCategory.commands, command) return } } - c.slice = append(c.slice, - &CommandCategory{Name: category, commands: []*Command{command}}) + newVal := commandCategories(append(*c, + &commandCategory{name: category, commands: []*Command{command}})) + (*c) = newVal } -// Categories returns a copy of the category slice -func (c *CommandCategories) Categories() []*CommandCategory { - ret := make([]*CommandCategory, len(c.slice)) - copy(ret, c.slice) +func (c *commandCategories) Categories() []CommandCategory { + ret := []CommandCategory{} + for _, cat := range *c { + ret = append(ret, cat) + } return ret } // CommandCategory is a category containing commands. -type CommandCategory struct { - Name string +type CommandCategory interface { + // Name returns the category name string + Name() string + // VisibleCommands returns a slice of the Commands with Hidden=false + VisibleCommands() []*Command +} +type commandCategory struct { + name string commands []*Command } -// VisibleCommands returns a slice of the Commands with Hidden=false -func (c *CommandCategory) VisibleCommands() []*Command { +func newCommandCategory(name string) *commandCategory { + return &commandCategory{ + name: name, + commands: []*Command{}, + } +} + +func (c *commandCategory) Name() string { + return c.name +} + +func (c *commandCategory) VisibleCommands() []*Command { + if c.commands == nil { + c.commands = []*Command{} + } + ret := []*Command{} for _, command := range c.commands { if !command.Hidden { diff --git a/command.go b/command.go index c8f6099..f05f1e2 100644 --- a/command.go +++ b/command.go @@ -120,7 +120,7 @@ func (c *Command) Run(ctx *Context) (err error) { if afterErr != nil { HandleExitCoder(err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -193,12 +193,12 @@ func (c *Command) startApp(ctx *Context) error { app.Compiled = ctx.App.Compiled app.Writer = ctx.App.Writer - app.categories = NewCommandCategories() + app.Categories = newCommandCategories() for _, command := range c.Subcommands { - app.categories.AddCommand(command.Category, command) + app.Categories.AddCommand(command.Category, command) } - sort.Sort(app.categories) + sort.Sort(app.Categories.(*commandCategories)) // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion diff --git a/context.go b/context.go index 39aa9be..b43c957 100644 --- a/context.go +++ b/context.go @@ -10,7 +10,7 @@ import ( // Context is a type that is passed through to // each Handler action in a cli application. Context -// can be used to retrieve context-specific Args and +// can be used to retrieve context-specific args and // parsed command-line options. type Context struct { App *App @@ -148,8 +148,9 @@ func (c *Context) Lineage() []*Context { } // Args returns the command line arguments associated with the context. -func (c *Context) Args() *Args { - return &Args{slice: c.flagSet.Args()} +func (c *Context) Args() Args { + ret := args(c.flagSet.Args()) + return &ret } // NArg returns the number of the command line arguments. diff --git a/errors.go b/errors.go index a8091f4..ba01537 100644 --- a/errors.go +++ b/errors.go @@ -15,25 +15,39 @@ var OsExiter = os.Exit var ErrWriter io.Writer = os.Stderr // MultiError is an error that wraps multiple errors. -type MultiError struct { - errors []error +type MultiError interface { + error + // Errors returns a copy of the errors slice + Errors() []error } // NewMultiError creates a new MultiError. Pass in one or more errors. -func NewMultiError(err ...error) MultiError { - return MultiError{errors: err} +func newMultiError(err ...error) MultiError { + ret := multiError(err) + return &ret } -// Error implents the error interface. -func (m MultiError) Error() string { - errs := make([]string, len(m.errors)) - for i, err := range m.errors { +type multiError []error + +// Error implements the error interface. +func (m *multiError) Error() string { + errs := make([]string, len(*m)) + for i, err := range *m { errs[i] = err.Error() } return strings.Join(errs, "\n") } +// Errors returns a copy of the errors slice +func (m *multiError) Errors() []error { + errs := make([]error, len(*m)) + for _, err := range *m { + errs = append(errs, err) + } + return errs +} + // ExitCoder is the interface checked by `App` and `Command` for a custom exit // code type ExitCoder interface { @@ -41,29 +55,25 @@ type ExitCoder interface { ExitCode() int } -// ExitError fulfills both the builtin `error` interface and `ExitCoder` -type ExitError struct { +type exitError struct { exitCode int message string } -// NewExitError makes a new *ExitError -func NewExitError(message string, exitCode int) *ExitError { - return &ExitError{ +// Exit wraps a message and exit code into an ExitCoder suitable for handling by +// HandleExitCoder +func Exit(message string, exitCode int) ExitCoder { + return &exitError{ exitCode: exitCode, message: message, } } -// Error returns the string message, fulfilling the interface required by -// `error` -func (ee *ExitError) Error() string { +func (ee *exitError) Error() string { return ee.message } -// ExitCode returns the exit code, fulfilling the interface required by -// `ExitCoder` -func (ee *ExitError) ExitCode() int { +func (ee *exitError) ExitCode() int { return ee.exitCode } @@ -85,7 +95,7 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.errors { + for _, merr := range multiErr.Errors() { HandleExitCoder(merr) } } diff --git a/errors_test.go b/errors_test.go index 8f5f284..5b4981f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - HandleExitCoder(NewExitError("galactic perimeter breach", 9)) + HandleExitCoder(Exit("galactic perimeter breach", 9)) expect(t, exitCode, 9) expect(t, called, true) @@ -51,8 +51,8 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { defer func() { OsExiter = os.Exit }() - exitErr := NewExitError("galactic perimeter breach", 9) - err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) + exitErr := Exit("galactic perimeter breach", 9) + err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr) HandleExitCoder(err) expect(t, exitCode, 9) diff --git a/help.go b/help.go index d02f7fd..c21639d 100644 --- a/help.go +++ b/help.go @@ -149,7 +149,7 @@ func ShowCommandHelp(ctx *Context, command string) error { } if ctx.App.CommandNotFound == nil { - return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3) + return Exit(fmt.Sprintf("No help topic for '%v'", command), 3) } ctx.App.CommandNotFound(ctx, command) @@ -201,15 +201,28 @@ func printHelp(out io.Writer, templ string, data interface{}) { w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + + errDebug := os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" + + defer func() { + if r := recover(); r != nil { + if errDebug { + fmt.Fprintf(ErrWriter, "CLI TEMPLATE PANIC: %#v\n", r) + } + if os.Getenv("CLI_TEMPLATE_REPANIC") != "" { + panic(r) + } + } + }() + err := t.Execute(w, data) if err != nil { - // If the writer is closed, t.Execute will fail, and there's nothing - // we can do to recover. - if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { + if errDebug { fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) } return } + w.Flush() } diff --git a/help_test.go b/help_test.go index 4d1dedc..b81701c 100644 --- a/help_test.go +++ b/help_test.go @@ -129,9 +129,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*ExitError) + exitErr, ok := err.(*exitError) if !ok { - t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { @@ -157,9 +157,9 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*ExitError) + exitErr, ok := err.(*exitError) if !ok { - t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { diff --git a/helpers_test.go b/helpers_test.go index 109ea7a..bcfa46b 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -12,6 +12,10 @@ var ( wd, _ = os.Getwd() ) +func init() { + os.Setenv("CLI_TEMPLATE_REPANIC", "1") +} + func expect(t *testing.T, a interface{}, b interface{}) { _, fn, line, _ := runtime.Caller(1) fn = strings.Replace(fn, wd+"/", "", -1) From ed8f4ac40891a2c1c566447c7d6f005b111e4d39 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 25 May 2016 12:07:49 -0400 Subject: [PATCH 026/158] Fix example that uses ExitCoder --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9ebe31..11a3ea4 100644 --- a/README.md +++ b/README.md @@ -498,7 +498,7 @@ func main() { } app.Action = func(ctx *cli.Context) error { if !ctx.Bool("ginger-crouton") { - return cli.NewExitError("it is not in the soup", 86) + return cli.Exit("it is not in the soup", 86) } return nil } From d616529afc6477226f2941ff03f8da9e91e5ec4d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 27 May 2016 14:38:51 -0400 Subject: [PATCH 027/158] Use make once instead of a loop with append --- category.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/category.go b/category.go index f2cb939..3b405c0 100644 --- a/category.go +++ b/category.go @@ -39,9 +39,9 @@ func (c *commandCategories) AddCommand(category string, command *Command) { } func (c *commandCategories) Categories() []CommandCategory { - ret := []CommandCategory{} - for _, cat := range *c { - ret = append(ret, cat) + ret := make([]CommandCategory, len(*c)) + for i, cat := range *c { + ret[i] = cat } return ret } From 80190b203cb0d63ae8f39b97634369fccbc48147 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 09:51:12 -0400 Subject: [PATCH 028/158] Rename migration script and add more migrators --- .travis.yml | 5 + appveyor.yml | 2 + cli-migrate-slice-types | 75 ------------- cli-v1-to-v2 | 235 ++++++++++++++++++++++++++++++++++++++++ runtests | 27 +++-- 5 files changed, 261 insertions(+), 83 deletions(-) delete mode 100755 cli-migrate-slice-types create mode 100755 cli-v1-to-v2 diff --git a/.travis.yml b/.travis.yml index 657e96a..66643e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: go sudo: false +cache: pip + go: - 1.2.2 - 1.3.3 @@ -25,8 +27,11 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... +- pip install flake8 script: +- flake8 runtests cli-v1-to-v2 - ./runtests vet - ./runtests test - ./runtests gfmxr +- ./cli-v1-to-v2 --selftest diff --git a/appveyor.yml b/appveyor.yml index 173086e..5714f8b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,8 +18,10 @@ install: - go env - go get github.com/urfave/gfmxr/... - go get -v -t ./... +- pip install flake8 build_script: +- flake8 runtests cli-v1-to-v2 - python runtests vet - python runtests test - python runtests gfmxr diff --git a/cli-migrate-slice-types b/cli-migrate-slice-types deleted file mode 100755 index 5c6cb1f..0000000 --- a/cli-migrate-slice-types +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function, unicode_literals - -import argparse -import os -import re -import sys - - -DESCRIPTION = """\ -Migrate arbitrary `.go` sources from the pre-v2.0.0 API for StringSlice and -IntSlice types, optionally writing the changes back to file. -""" -SLICE_TYPE_RE = re.compile( - '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', - flags=re.DOTALL -) - - -def main(sysargs=sys.argv[:]): - parser = argparse.ArgumentParser( - description=DESCRIPTION, - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('basedir', nargs='?', metavar='BASEDIR', - type=os.path.abspath, default=os.getcwd()) - parser.add_argument('-w', '--write', help='write changes back to file', - action='store_true', default=False) - - args = parser.parse_args(sysargs[1:]) - - for filepath in _find_candidate_files(args.basedir): - updated_source = _update_source(filepath) - if args.write: - print('Updating {!r}'.format(filepath)) - - with open(filepath, 'w') as outfile: - outfile.write(updated_source) - else: - print('// -> Updated {!r}'.format(filepath)) - print(updated_source) - - return 0 - - -def _update_source(filepath): - with open(filepath) as infile: - source = infile.read() - return SLICE_TYPE_RE.sub(_slice_type_repl, source) - - -def _slice_type_repl(match): - return 'cli.New{}({})'.format( - match.groupdict()['type'], match.groupdict()['args'] - ) - - -def _find_candidate_files(basedir): - for curdir, dirs, files in os.walk(basedir): - for i, dirname in enumerate(dirs[:]): - if dirname.startswith('.'): - dirs.pop(i) - - for filename in files: - if not filename.endswith('.go'): - continue - - filepath = os.path.join(curdir, filename) - if not os.access(filepath, os.R_OK | os.W_OK): - continue - - yield filepath - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 new file mode 100755 index 0000000..8743245 --- /dev/null +++ b/cli-v1-to-v2 @@ -0,0 +1,235 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +import argparse +import logging +import os +import re +import sys + + +_DESCRIPTION = """\ +Migrate arbitrary `.go` sources (mostly) from the v1 to v2 API. +""" +_V2_IMPORT = '"gopkg.in/urfave/cli.v2"' +_V1_IMPORT_RE = re.compile( + '"(?:github\\.com|gopkg\\.in)/(?:codegangsta|urfave)/cli(?:\\.v1|)"', +) +_SLICE_TYPE_RE = re.compile( + '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', + flags=re.DOTALL +) +_FLAG_LITERAL_RE = re.compile( + '(?P[^&]?)cli\\.(?P\\w+)Flag{', +) +_COMMAND_SLICE_RE = re.compile( + '(?P\\[\\])cli\\.Command{', +) +_COMMAND_LITERAL_RE = re.compile( + '(?P\\s+)cli\\.Command{', +) + +_MIGRATORS = {} + + +def main(sysargs=sys.argv[:]): + parser = argparse.ArgumentParser( + description=_DESCRIPTION, + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('basedir', nargs='?', metavar='BASEDIR', + 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('--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 + + logging.basicConfig( + level=(logging.FATAL if args.quiet else logging.INFO), + format='%(message)s' + ) + + for filepath in _find_candidate_files(args.basedir): + updated_source = _update_filepath(filepath) + if args.write: + logging.info('Updating %s', filepath) + + with open(filepath, 'w') as outfile: + outfile.write(updated_source) + else: + logging.info('// Updated %s:', filepath) + print(updated_source) + + return 0 + + +def _find_candidate_files(basedir): + for curdir, dirs, files in os.walk(basedir): + for i, dirname in enumerate(dirs[:]): + if dirname.startswith('.'): + dirs.pop(i) + + for filename in files: + if not filename.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 open(filepath) as infile: + return _update_source(infile.read()) + + +def _update_source(source): + for migrator, func in _MIGRATORS.items(): + logging.info('Running %s migrator', migrator) + source = func(source) + return source + + +def _migrator(func): + _MIGRATORS[func.__name__.replace('_migrate_', '')] = func + return func + + +@_migrator +def _migrate_command_slice(source): + return _COMMAND_SLICE_RE.sub(_command_slice_repl, source) + + +def _command_slice_repl(match): + return '{}*cli.Command{{'.format(match.groupdict()['prefix']) + + +@_migrator +def _migrate_command_literal(source): + return _COMMAND_LITERAL_RE.sub(_command_literal_repl, source) + + +def _command_literal_repl(match): + return '{}&cli.Command{{'.format(match.groupdict()['prefix']) + + +@_migrator +def _migrate_slice_types(source): + return _SLICE_TYPE_RE.sub(_slice_type_repl, source) + + +def _slice_type_repl(match): + return 'cli.New{}({})'.format( + match.groupdict()['type'], match.groupdict()['args'] + ) + + +@_migrator +def _migrate_flag_literals(source): + return _FLAG_LITERAL_RE.sub(_flag_literal_repl, source) + + +def _flag_literal_repl(match): + return '{}&cli.{}Flag{{'.format( + match.groupdict()['prefix'], match.groupdict()['type'] + ) + + +@_migrator +def _migrate_v1_imports(source): + return _V1_IMPORT_RE.sub(_V2_IMPORT, source) + + +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" +""" + ) +) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/runtests b/runtests index 72c1f0d..d077055 100755 --- a/runtests +++ b/runtests @@ -9,18 +9,13 @@ import tempfile from subprocess import check_call, check_output -PACKAGE_NAME = os.environ.get( +_PACKAGE_NAME = os.environ.get( 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' ) +_TARGETS = {} def main(sysargs=sys.argv[:]): - targets = { - 'vet': _vet, - 'test': _test, - 'gfmxr': _gfmxr - } - parser = argparse.ArgumentParser() parser.add_argument( 'target', nargs='?', choices=tuple(targets.keys()), default='test' @@ -31,6 +26,12 @@ def main(sysargs=sys.argv[:]): return 0 +def _target(func): + _TARGETS[func.__name__.strip('_')] = func + return func + + +@_target def _test(): if check_output('go version'.split()).split()[2] < 'go1.2': _run('go test -v .'.split()) @@ -46,7 +47,7 @@ def _test(): _run('go test -v'.split() + [ '-coverprofile={}'.format(coverprofile), - ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') + ('{}/{}'.format(_PACKAGE_NAME, subpackage)).rstrip('/') ]) combined_name = _combine_coverprofiles(coverprofiles) @@ -54,14 +55,24 @@ def _test(): os.remove(combined_name) +@_target def _gfmxr(): _run(['gfmxr', '-c', str(_gfmxr_count()), '-s', 'README.md']) +@_target def _vet(): _run('go vet ./...'.split()) +@_target +def _migrator(): + exe = os.path.join( + os.path.abspath(os.path.dirname(__file__)), 'cli-v1-to-v2' + ) + run(['py.test', exe]) + + def _run(command): print('runtests: {}'.format(' '.join(command)), file=sys.stderr) check_call(command) From eadd3918f10b686858154a3e3a659b412a9cb45a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 09:53:47 -0400 Subject: [PATCH 029/158] Correct some flake8 bits --- cli-v1-to-v2 | 3 +-- runtests | 12 ++---------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 8743245..74eac50 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -226,8 +226,7 @@ _MIGRATOR_TESTS = ( \t"gopkg.in/urfave/cli.v2" \t"gopkg.in/urfave/cli.v2" \t"gopkg.in/urfave/cli.v2" -""" - ) +""") ) diff --git a/runtests b/runtests index d077055..c9cbf5e 100755 --- a/runtests +++ b/runtests @@ -18,11 +18,11 @@ _TARGETS = {} def main(sysargs=sys.argv[:]): parser = argparse.ArgumentParser() parser.add_argument( - 'target', nargs='?', choices=tuple(targets.keys()), default='test' + 'target', nargs='?', choices=tuple(_TARGETS.keys()), default='test' ) args = parser.parse_args(sysargs[1:]) - targets[args.target]() + _TARGETS[args.target]() return 0 @@ -65,14 +65,6 @@ def _vet(): _run('go vet ./...'.split()) -@_target -def _migrator(): - exe = os.path.join( - os.path.abspath(os.path.dirname(__file__)), 'cli-v1-to-v2' - ) - run(['py.test', exe]) - - def _run(command): print('runtests: {}'.format(' '.join(command)), file=sys.stderr) check_call(command) From b47e1523955b3ddf5949e97b784ac72e3943d527 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 10:21:13 -0400 Subject: [PATCH 030/158] Handle migration of both files and dirs --- cli-v1-to-v2 | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 74eac50..1ff4892 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -36,7 +36,7 @@ def main(sysargs=sys.argv[:]): parser = argparse.ArgumentParser( description=_DESCRIPTION, formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('basedir', nargs='?', metavar='BASEDIR', + 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) @@ -60,7 +60,7 @@ def main(sysargs=sys.argv[:]): format='%(message)s' ) - for filepath in _find_candidate_files(args.basedir): + for filepath in _find_candidate_files(args.path): updated_source = _update_filepath(filepath) if args.write: logging.info('Updating %s', filepath) @@ -74,21 +74,26 @@ def main(sysargs=sys.argv[:]): return 0 -def _find_candidate_files(basedir): - for curdir, dirs, files in os.walk(basedir): - for i, dirname in enumerate(dirs[:]): - if dirname.startswith('.'): - dirs.pop(i) +def _find_candidate_files(paths): + for path in paths: + if not os.path.isdir(path): + yield path + continue - for filename in files: - if not filename.endswith('.go'): - continue + for curdir, dirs, files in os.walk(path): + for i, dirname in enumerate(dirs[:]): + if dirname.startswith('.'): + dirs.pop(i) - filepath = os.path.join(curdir, filename) - if not os.access(filepath, os.R_OK | os.W_OK): - continue + for filename in files: + if not filename.endswith('.go'): + continue - yield filepath + filepath = os.path.join(curdir, filename) + if not os.access(filepath, os.R_OK | os.W_OK): + continue + + yield filepath def _update_filepath(filepath): From e9027a59efbacbba3ad6eab638aa2995478f6b10 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 10:23:21 -0400 Subject: [PATCH 031/158] Do a user-level install with pip --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 66643e7..744ef8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... -- pip install flake8 +- pip install --user flake8 script: - flake8 runtests cli-v1-to-v2 diff --git a/appveyor.yml b/appveyor.yml index 5714f8b..7ed2b65 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,7 +18,7 @@ install: - go env - go get github.com/urfave/gfmxr/... - go get -v -t ./... -- pip install flake8 +- pip install --user flake8 build_script: - flake8 runtests cli-v1-to-v2 From 6d59b40b685583b878111df4254aac3eb45c896f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 10:27:44 -0400 Subject: [PATCH 032/158] Skip flake8 bits on Windows since pip isn't already there, installing it is a bit of a pain, and the results *should* be the same on all platforms --- appveyor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 7ed2b65..173086e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,10 +18,8 @@ install: - go env - go get github.com/urfave/gfmxr/... - go get -v -t ./... -- pip install --user flake8 build_script: -- flake8 runtests cli-v1-to-v2 - python runtests vet - python runtests test - python runtests gfmxr From 462217f9fca36771bd1da9c65db8b2888ba71a5e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 10:46:00 -0400 Subject: [PATCH 033/158] Allow zero+ paths in migration script and run the self-tests on Windows, too. --- appveyor.yml | 1 + cli-v1-to-v2 | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 173086e..e93734d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,3 +23,4 @@ build_script: - python runtests vet - python runtests test - python runtests gfmxr +- python cli-v1-to-v2 --selftest diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 1ff4892..175afa7 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -36,7 +36,7 @@ def main(sysargs=sys.argv[:]): parser = argparse.ArgumentParser( description=_DESCRIPTION, formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('path', nargs='+', + 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) @@ -60,7 +60,11 @@ def main(sysargs=sys.argv[:]): format='%(message)s' ) - for filepath in _find_candidate_files(args.path): + 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) @@ -86,7 +90,7 @@ def _find_candidate_files(paths): dirs.pop(i) for filename in files: - if not filename.endswith('.go'): + if not filename.decode('utf-8').endswith('.go'): continue filepath = os.path.join(curdir, filename) From 3914ca9d5f43b15dfa8f9f644bcde8cd803ae5d1 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 10:54:06 -0400 Subject: [PATCH 034/158] Update docs around migration script and ensure ~/.local/bin is in PATH on OS X --- .gitignore | 1 + .travis.yml | 1 + CHANGELOG.md | 4 ++-- README.md | 3 +++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7823778..2a7262d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.coverprofile +.extracted-examples diff --git a/.travis.yml b/.travis.yml index 744ef8d..2667e57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ matrix: include: - go: 1.6.2 os: osx + env: PATH=$HOME/.local/bin:$PATH - go: 1.1.2 install: go get -v . before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION diff --git a/CHANGELOG.md b/CHANGELOG.md index 248181e..c9e2c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,8 @@ ### Removed - the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`. - To migrate to the new API, you may choose to run [this helper - (python) script](./cli-migrate-slice-types). + To migrate to the new API, you may choose to run [the migrator + (python) script](./cli-v1-to-v2). - The optimistic reordering of arguments and flags introduced by https://github.com/codegangsta/cli/pull/36. This behavior only worked when all arguments appeared before all flags, but caused [weird issues with boolean diff --git a/README.md b/README.md index 11a3ea4..4cfb562 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,9 @@ import ( ... ``` +**NOTE**: There is a [migrator (python) script](./cli-v1-to-v2) available to aid +with the transition from the v1 to v2 API. + ### Pinning to the `v1` branch Similarly to the section above describing use of the `v2` branch, if one wants From 5565a037054f9030be63bfa2b47f6adb63facaa4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 11:02:49 -0400 Subject: [PATCH 035/158] Migrate and build extracted v1 examples --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2667e57..b014f0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,10 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... - pip install --user flake8 +- mkdir -p .extracted-examples +- curl -sSL -o .extracted-examples/README.md + 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' +- cd .extracted-examples && gfmxr extract -o . script: - flake8 runtests cli-v1-to-v2 @@ -36,3 +40,9 @@ script: - ./runtests test - ./runtests gfmxr - ./cli-v1-to-v2 --selftest +- for f in .extracted-examples/*.go ; do + ./cli-v1-to-v2 -w $f ; + go build -o .extracted-examples/example $f ; + ./cli-v1-to-v2 -w $f ; + go build -o .extracted-examples/example $f ; + done From e5ba2f41c7886b1d53d5cb438ed761bf26851226 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 11:09:37 -0400 Subject: [PATCH 036/158] Ensure we're in the right dir before continuing --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b014f0f..d7f72ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ before_script: - mkdir -p .extracted-examples - curl -sSL -o .extracted-examples/README.md 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' -- cd .extracted-examples && gfmxr extract -o . +- pushd .extracted-examples && gfmxr extract -o . ; popd script: - flake8 runtests cli-v1-to-v2 From 0ecae535a85d072ab1ae6ac05c6bff51ae0d43ad Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 12:24:05 -0400 Subject: [PATCH 037/158] Where is flake8 on OS X? --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d7f72ee..60d8eb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... - pip install --user flake8 +- find ~/.local -name flake8 - mkdir -p .extracted-examples - curl -sSL -o .extracted-examples/README.md 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' From 27f4b751e42b4ece1835c47126e2029d84cc20a0 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 12:29:35 -0400 Subject: [PATCH 038/158] Try a system pip install on OS X --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 60d8eb2..4f143a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,17 @@ go: - 1.6.2 - master +env: + global: + - PIP_FLAGS="--user" + matrix: allow_failures: - go: master include: - go: 1.6.2 os: osx - env: PATH=$HOME/.local/bin:$PATH + env: PIP_FLAGS="" - go: 1.1.2 install: go get -v . before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION @@ -28,8 +32,7 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... -- pip install --user flake8 -- find ~/.local -name flake8 +- pip install $PIP_FLAGS flake8 - mkdir -p .extracted-examples - curl -sSL -o .extracted-examples/README.md 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' From b4db7c2b27cb8b5284cbbfdc41e2f1c3e9e68d69 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 12:34:33 -0400 Subject: [PATCH 039/158] Do sudo-tastic system install os OS X --- .travis.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f143a7..8f43203 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,7 @@ go: - 1.6.2 - master -env: - global: - - PIP_FLAGS="--user" +env: PIP_INSTALL="pip install --user" matrix: allow_failures: @@ -22,7 +20,7 @@ matrix: include: - go: 1.6.2 os: osx - env: PIP_FLAGS="" + env: PIP_INSTALL="sudo pip install" - go: 1.1.2 install: go get -v . before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION @@ -32,7 +30,7 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... -- pip install $PIP_FLAGS flake8 +- $PIP_INSTALL flake8 - mkdir -p .extracted-examples - curl -sSL -o .extracted-examples/README.md 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' From 9158c1189d3ccca28e8266050bbbe7f9476c40cd Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 12:40:13 -0400 Subject: [PATCH 040/158] Add dir with flake8 to path better-er --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8f43203..a2d3b83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,15 +12,12 @@ go: - 1.6.2 - master -env: PIP_INSTALL="pip install --user" - matrix: allow_failures: - go: master include: - go: 1.6.2 os: osx - env: PIP_INSTALL="sudo pip install" - go: 1.1.2 install: go get -v . before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION @@ -30,7 +27,10 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... -- $PIP_INSTALL flake8 +- pip install --user flake8 +- if [ -d ~/Library/Python ] ; then + export PATH=$PATH:$(dirname $(find ~/Library/Python -name flake8)) ; + fi - mkdir -p .extracted-examples - curl -sSL -o .extracted-examples/README.md 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' From 586b49ded8a3ea52b841732b6c91f0f5b2ef4f4e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 12:44:41 -0400 Subject: [PATCH 041/158] Give up and use sudo on OS X again --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index a2d3b83..b6a805e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,12 +12,15 @@ go: - 1.6.2 - master +env: pip_install="pip install --user" + matrix: allow_failures: - go: master include: - go: 1.6.2 os: osx + env: pip_install="sudo pip install" - go: 1.1.2 install: go get -v . before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION @@ -27,10 +30,7 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... -- pip install --user flake8 -- if [ -d ~/Library/Python ] ; then - export PATH=$PATH:$(dirname $(find ~/Library/Python -name flake8)) ; - fi +- $pip_install flake8 - mkdir -p .extracted-examples - curl -sSL -o .extracted-examples/README.md 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' From 2cfb957033dacc14d07867af9fb92f194ecf9441 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 13:59:45 -0400 Subject: [PATCH 042/158] Run migration tests via `./runtests` --- .travis.yml | 11 +---------- appveyor.yml | 1 + runtests | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index b6a805e..550bccf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,10 +31,6 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... - $pip_install flake8 -- mkdir -p .extracted-examples -- curl -sSL -o .extracted-examples/README.md - 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' -- pushd .extracted-examples && gfmxr extract -o . ; popd script: - flake8 runtests cli-v1-to-v2 @@ -42,9 +38,4 @@ script: - ./runtests test - ./runtests gfmxr - ./cli-v1-to-v2 --selftest -- for f in .extracted-examples/*.go ; do - ./cli-v1-to-v2 -w $f ; - go build -o .extracted-examples/example $f ; - ./cli-v1-to-v2 -w $f ; - go build -o .extracted-examples/example $f ; - done +- ./runtests migrations diff --git a/appveyor.yml b/appveyor.yml index e93734d..ae80b2f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,3 +24,4 @@ build_script: - python runtests test - python runtests gfmxr - python cli-v1-to-v2 --selftest +- python runtests migrations diff --git a/runtests b/runtests index c9cbf5e..5dffd7d 100755 --- a/runtests +++ b/runtests @@ -2,7 +2,9 @@ from __future__ import print_function import argparse +import glob import os +import shutil import sys import tempfile @@ -65,6 +67,30 @@ def _vet(): _run('go vet ./...'.split()) +@_target +def _migrations(): + migration_script = os.path.abspath( + os.environ.get('V1TOV2', './cli-v1-to-v2') + ) + v1_readme_url = os.environ.get( + 'V1README', + 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' + ) + + tmpdir = tempfile.mkdtemp() + try: + os.chdir(tmpdir) + _run('curl -sSL -o README.md {}'.format(v1_readme_url).split()) + _run('gfmxr extract -o .'.split()) + + for gofile in glob.glob('*.go'): + for i in (0, 1): + _run(['python', migration_script, '-w', gofile]) + _run('go build -o tmp.out {}'.format(gofile).split()) + finally: + shutil.rmtree(tmpdir, ignore_errors=True) + + def _run(command): print('runtests: {}'.format(' '.join(command)), file=sys.stderr) check_call(command) From 659e1c1e0bf53b6d1d55ade8393eaabe9cd637e9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 15:15:49 -0400 Subject: [PATCH 043/158] Fill in migrations for cli.Exit and BoolFlag with Value --- cli-v1-to-v2 | 46 +++++++++++++++++++++++++++++++++++++++++++++- runtests | 3 ++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 175afa7..60a19b6 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -20,7 +20,7 @@ _SLICE_TYPE_RE = re.compile( flags=re.DOTALL ) _FLAG_LITERAL_RE = re.compile( - '(?P[^&]?)cli\\.(?P\\w+)Flag{', + '(?P\\s+)cli\\.(?P\\w+)Flag{', ) _COMMAND_SLICE_RE = re.compile( '(?P\\[\\])cli\\.Command{', @@ -28,6 +28,11 @@ _COMMAND_SLICE_RE = re.compile( _COMMAND_LITERAL_RE = re.compile( '(?P\\s+)cli\\.Command{', ) +_EXIT_ERROR_RE = re.compile('cli\\.NewExitError') +_BOOL_T_FLAG_RE = re.compile( + 'cli\\.BoolTFlag{(?P[^}]*)}', + flags=re.DOTALL +) _MIGRATORS = {} @@ -162,6 +167,21 @@ def _migrate_v1_imports(source): return _V1_IMPORT_RE.sub(_V2_IMPORT, source) +@_migrator +def _migrate_new_exit_error(source): + return _EXIT_ERROR_RE.sub('cli.Exit', source) + +@_migrator +def _migrate_bool_t_flag(source): + return _BOOL_T_FLAG_RE.sub(_bool_t_flag_repl, source) + + +def _bool_t_flag_repl(match): + return 'cli.BoolFlag{{Value: true, {}}}'.format( + match.groupdict()['args'] + ) + + def test_migrators(): import difflib @@ -235,6 +255,30 @@ _MIGRATOR_TESTS = ( \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} """) ) diff --git a/runtests b/runtests index 5dffd7d..4975f96 100755 --- a/runtests +++ b/runtests @@ -88,7 +88,8 @@ def _migrations(): _run(['python', migration_script, '-w', gofile]) _run('go build -o tmp.out {}'.format(gofile).split()) finally: - shutil.rmtree(tmpdir, ignore_errors=True) + if os.environ.get('NOCLEAN', '') == '': + shutil.rmtree(tmpdir, ignore_errors=True) def _run(command): From e3a099f7c54fbeaecdf2dc4c774665ac6e33bd0d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 30 May 2016 15:24:16 -0400 Subject: [PATCH 044/158] Cleanups per flake8 and code review --- .gitignore | 1 - .travis.yml | 2 -- cli-v1-to-v2 | 5 +++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2a7262d..7823778 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ *.coverprofile -.extracted-examples diff --git a/.travis.yml b/.travis.yml index 550bccf..e7475b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: go sudo: false -cache: pip - go: - 1.2.2 - 1.3.3 diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 60a19b6..90f127a 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -171,13 +171,14 @@ def _migrate_v1_imports(source): def _migrate_new_exit_error(source): return _EXIT_ERROR_RE.sub('cli.Exit', source) + @_migrator def _migrate_bool_t_flag(source): return _BOOL_T_FLAG_RE.sub(_bool_t_flag_repl, source) def _bool_t_flag_repl(match): - return 'cli.BoolFlag{{Value: true, {}}}'.format( + return 'cli.BoolFlag{{Value: true,{}}}'.format( match.groupdict()['args'] ) @@ -275,7 +276,7 @@ _MIGRATOR_TESTS = ( \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&cli.BoolFlag{Value: true, \t\t\t\t\tName: "blurp", \t\t\t\t}, \t\t\t} From c9011d835167b3e148a585661c596ee2891d56ba Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 31 May 2016 16:39:56 -0400 Subject: [PATCH 045/158] Add migrators for context.Args() indexing and EnvVars --- cli-v1-to-v2 | 68 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 90f127a..1d64e72 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -19,16 +19,12 @@ _SLICE_TYPE_RE = re.compile( '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', flags=re.DOTALL ) -_FLAG_LITERAL_RE = re.compile( - '(?P\\s+)cli\\.(?P\\w+)Flag{', -) -_COMMAND_SLICE_RE = re.compile( - '(?P\\[\\])cli\\.Command{', -) -_COMMAND_LITERAL_RE = re.compile( - '(?P\\s+)cli\\.Command{', -) +_FLAG_LITERAL_RE = re.compile('(?P\\s+)cli\\.(?P\\w+)Flag{') +_COMMAND_SLICE_RE = re.compile('(?P\\[\\])cli\\.Command{') +_COMMAND_LITERAL_RE = re.compile('(?P\\s+)cli\\.Command{') _EXIT_ERROR_RE = re.compile('cli\\.NewExitError') +_CONTEXT_ARGS_INDEX_RE = re.compile('\\.Args\\(\\)\\[(?P\\d+)\\]') +_ENVVAR_STRING_RE = re.compile('EnvVar:(?P\\s+)"(?P[^"]+)"') _BOOL_T_FLAG_RE = re.compile( 'cli\\.BoolTFlag{(?P[^}]*)}', flags=re.DOTALL @@ -183,6 +179,30 @@ def _bool_t_flag_repl(match): ) +@_migrator +def _migrate_context_args_index(source): + return _CONTEXT_ARGS_INDEX_RE.sub(_context_args_index_repl, source) + + +def _context_args_index_repl(match): + return '.Args().Get({})'.format(match.groupdict()['index']) + + +@_migrator +def _migrate_envvar_string(source): + return _ENVVAR_STRING_RE.sub(_envvar_string_repl, source) + + +def _envvar_string_repl(match): + return 'EnvVars:{}[]string{{{}}}'.format( + match.groupdict()['ws'], + ', '.join([ + '"{}"'.format(s) for s in + re.split('\\s*,\\s*', match.groupdict()['string']) + ]) + ) + + def test_migrators(): import difflib @@ -280,6 +300,36 @@ _MIGRATOR_TESTS = ( \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} """) ) From cbd0e8231babff69e6cb2149503ee6005d0ab44f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 31 May 2016 20:58:12 -0400 Subject: [PATCH 046/158] Add migrator for stringly values of flag "Name" and reorganize things a bit for less jumpy editing --- cli-v1-to-v2 | 150 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 53 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 1d64e72..ce7ad3c 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -12,24 +12,6 @@ _DESCRIPTION = """\ Migrate arbitrary `.go` sources (mostly) from the v1 to v2 API. """ _V2_IMPORT = '"gopkg.in/urfave/cli.v2"' -_V1_IMPORT_RE = re.compile( - '"(?:github\\.com|gopkg\\.in)/(?:codegangsta|urfave)/cli(?:\\.v1|)"', -) -_SLICE_TYPE_RE = re.compile( - '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', - flags=re.DOTALL -) -_FLAG_LITERAL_RE = re.compile('(?P\\s+)cli\\.(?P\\w+)Flag{') -_COMMAND_SLICE_RE = re.compile('(?P\\[\\])cli\\.Command{') -_COMMAND_LITERAL_RE = re.compile('(?P\\s+)cli\\.Command{') -_EXIT_ERROR_RE = re.compile('cli\\.NewExitError') -_CONTEXT_ARGS_INDEX_RE = re.compile('\\.Args\\(\\)\\[(?P\\d+)\\]') -_ENVVAR_STRING_RE = re.compile('EnvVar:(?P\\s+)"(?P[^"]+)"') -_BOOL_T_FLAG_RE = re.compile( - 'cli\\.BoolTFlag{(?P[^}]*)}', - flags=re.DOTALL -) - _MIGRATORS = {} @@ -120,86 +102,135 @@ def _migrator(func): @_migrator def _migrate_command_slice(source): - return _COMMAND_SLICE_RE.sub(_command_slice_repl, source) + return re.sub( + '(?P\\[\\])cli\\.Command{', + _command_slice_repl, source + ) def _command_slice_repl(match): - return '{}*cli.Command{{'.format(match.groupdict()['prefix']) + return '{prefix}*cli.Command{{'.format(**match.groupdict()) @_migrator def _migrate_command_literal(source): - return _COMMAND_LITERAL_RE.sub(_command_literal_repl, source) + return re.sub( + '(?P\\s+)cli\\.Command{', + _command_literal_repl, source + ) def _command_literal_repl(match): - return '{}&cli.Command{{'.format(match.groupdict()['prefix']) + return '{prefix}&cli.Command{{'.format(**match.groupdict()) @_migrator def _migrate_slice_types(source): - return _SLICE_TYPE_RE.sub(_slice_type_repl, source) + return re.sub( + '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', + _slice_type_repl, source, flags=re.DOTALL + ) def _slice_type_repl(match): - return 'cli.New{}({})'.format( - match.groupdict()['type'], match.groupdict()['args'] - ) + return 'cli.New{type}({args})'.format(**match.groupdict()) @_migrator def _migrate_flag_literals(source): - return _FLAG_LITERAL_RE.sub(_flag_literal_repl, source) + return re.sub( + '(?P\\s+)cli\\.(?P\\w+)Flag{', + _flag_literal_repl, source + ) def _flag_literal_repl(match): - return '{}&cli.{}Flag{{'.format( - match.groupdict()['prefix'], match.groupdict()['type'] - ) + return '{prefix}&cli.{type}Flag{{'.format(**match.groupdict()) @_migrator def _migrate_v1_imports(source): - return _V1_IMPORT_RE.sub(_V2_IMPORT, source) - - -@_migrator -def _migrate_new_exit_error(source): - return _EXIT_ERROR_RE.sub('cli.Exit', source) - - -@_migrator -def _migrate_bool_t_flag(source): - return _BOOL_T_FLAG_RE.sub(_bool_t_flag_repl, source) - - -def _bool_t_flag_repl(match): - return 'cli.BoolFlag{{Value: true,{}}}'.format( - match.groupdict()['args'] + return re.sub( + '"(?:github\\.com|gopkg\\.in)/(?:codegangsta|urfave)/cli(?:\\.v1|)"', + _V2_IMPORT, source ) +@_migrator +def _migrate_new_exit_error(source): + return re.sub('cli\\.NewExitError', 'cli.Exit', source) + + +@_migrator +def _migrate_bool_t_flag(source): + return re.sub( + 'cli\\.BoolTFlag{(?P[^}]*)}', + _bool_t_flag_repl, source, flags=re.DOTALL + ) + + +def _bool_t_flag_repl(match): + return 'cli.BoolFlag{{Value: true,{args}}}'.format(**match.groupdict()) + + @_migrator def _migrate_context_args_index(source): - return _CONTEXT_ARGS_INDEX_RE.sub(_context_args_index_repl, source) + return re.sub( + '\\.Args\\(\\)\\[(?P\\d+)\\]', + _context_args_index_repl, source + ) def _context_args_index_repl(match): - return '.Args().Get({})'.format(match.groupdict()['index']) + return '.Args().Get({index})'.format(**match.groupdict()) @_migrator def _migrate_envvar_string(source): - return _ENVVAR_STRING_RE.sub(_envvar_string_repl, source) + return re.sub('EnvVar:(?P\\s+)"(?P[^"]+)"', _envvar_string_repl, source) def _envvar_string_repl(match): - return 'EnvVars:{}[]string{{{}}}'.format( - match.groupdict()['ws'], - ', '.join([ + return 'EnvVars:{ws}[]string{{{value}}}'.format( + value=', '.join([ '"{}"'.format(s) for s in re.split('\\s*,\\s*', match.groupdict()['string']) - ]) + ]), + **match.groupdict() + ) + + +@_migrator +def _migrate_flag_name_stringly(source): + return re.sub( + '(?P\\s+)Name:(?P\\s+)"(?P[^"]+)"', + _flag_name_stringly_repl, source + ) + + +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 ) @@ -330,6 +361,19 @@ _MIGRATOR_TESTS = ( \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} """) ) From 733431d5c24b958f1184db84c3d172ed6931609e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 31 May 2016 20:59:09 -0400 Subject: [PATCH 047/158] Wrap a long line --- cli-v1-to-v2 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index ce7ad3c..08435f7 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -187,7 +187,10 @@ def _context_args_index_repl(match): @_migrator def _migrate_envvar_string(source): - return re.sub('EnvVar:(?P\\s+)"(?P[^"]+)"', _envvar_string_repl, source) + return re.sub( + 'EnvVar:(?P\\s+)"(?P[^"]+)"', + _envvar_string_repl, source + ) def _envvar_string_repl(match): From 92b70c22ac79283a22b2ce5b47fe0cca214444c2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 1 Jun 2016 19:11:05 -0400 Subject: [PATCH 048/158] Add more v1 -> v2 migrators --- cli-v1-to-v2 | 144 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 30 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 08435f7..e6b0e9a 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -2,6 +2,7 @@ from __future__ import print_function, unicode_literals import argparse +import io import logging import os import re @@ -52,7 +53,7 @@ def main(sysargs=sys.argv[:]): if args.write: logging.info('Updating %s', filepath) - with open(filepath, 'w') as outfile: + with io.open(filepath, 'w', encoding='utf-8') as outfile: outfile.write(updated_source) else: logging.info('// Updated %s:', filepath) @@ -84,7 +85,7 @@ def _find_candidate_files(paths): def _update_filepath(filepath): - with open(filepath) as infile: + with io.open(filepath, encoding='utf-8') as infile: return _update_source(infile.read()) @@ -96,39 +97,39 @@ def _update_source(source): def _migrator(func): - _MIGRATORS[func.__name__.replace('_migrate_', '')] = func + _MIGRATORS[func.__name__.strip('_')] = func return func @_migrator -def _migrate_command_slice(source): +def _slice_pointer_types(source): return re.sub( - '(?P\\[\\])cli\\.Command{', - _command_slice_repl, source + '(?P\\[\\])cli\\.(?PCommand|Author){', + _slice_pointer_types_repl, source, flags=re.UNICODE ) -def _command_slice_repl(match): - return '{prefix}*cli.Command{{'.format(**match.groupdict()) +def _slice_pointer_types_repl(match): + return '{prefix}*cli.{type}{{'.format(**match.groupdict()) @_migrator -def _migrate_command_literal(source): +def _pointer_type_literal(source): return re.sub( - '(?P\\s+)cli\\.Command{', - _command_literal_repl, source + '(?P\\s+)cli\\.(?PCommand|Author){', + _pointer_type_literal_repl, source, flags=re.UNICODE ) -def _command_literal_repl(match): - return '{prefix}&cli.Command{{'.format(**match.groupdict()) +def _pointer_type_literal_repl(match): + return '{prefix}&cli.{type}{{'.format(**match.groupdict()) @_migrator -def _migrate_slice_types(source): +def _slice_types(source): return re.sub( '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', - _slice_type_repl, source, flags=re.DOTALL + _slice_type_repl, source, flags=re.DOTALL|re.UNICODE ) @@ -137,10 +138,10 @@ def _slice_type_repl(match): @_migrator -def _migrate_flag_literals(source): +def _flag_literals(source): return re.sub( '(?P\\s+)cli\\.(?P\\w+)Flag{', - _flag_literal_repl, source + _flag_literal_repl, source, flags=re.UNICODE ) @@ -149,23 +150,23 @@ def _flag_literal_repl(match): @_migrator -def _migrate_v1_imports(source): +def _v1_imports(source): return re.sub( '"(?:github\\.com|gopkg\\.in)/(?:codegangsta|urfave)/cli(?:\\.v1|)"', - _V2_IMPORT, source + _V2_IMPORT, source, flags=re.UNICODE ) @_migrator -def _migrate_new_exit_error(source): - return re.sub('cli\\.NewExitError', 'cli.Exit', source) +def _new_exit_error(source): + return re.sub('cli\\.NewExitError', 'cli.Exit', source, flags=re.UNICODE) @_migrator -def _migrate_bool_t_flag(source): +def _bool_t_flag(source): return re.sub( 'cli\\.BoolTFlag{(?P[^}]*)}', - _bool_t_flag_repl, source, flags=re.DOTALL + _bool_t_flag_repl, source, flags=re.DOTALL|re.UNICODE ) @@ -174,10 +175,10 @@ def _bool_t_flag_repl(match): @_migrator -def _migrate_context_args_index(source): +def _context_args_index(source): return re.sub( '\\.Args\\(\\)\\[(?P\\d+)\\]', - _context_args_index_repl, source + _context_args_index_repl, source, flags=re.UNICODE ) @@ -186,10 +187,10 @@ def _context_args_index_repl(match): @_migrator -def _migrate_envvar_string(source): +def _envvar_string(source): return re.sub( 'EnvVar:(?P\\s+)"(?P[^"]+)"', - _envvar_string_repl, source + _envvar_string_repl, source, flags=re.UNICODE ) @@ -197,17 +198,20 @@ 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']) + re.split( + '\\s*,\\s*', match.groupdict()['string'], + flags=re.UNICODE + ) ]), **match.groupdict() ) @_migrator -def _migrate_flag_name_stringly(source): +def _flag_name_stringly(source): return re.sub( '(?P\\s+)Name:(?P\\s+)"(?P[^"]+)"', - _flag_name_stringly_repl, source + _flag_name_stringly_repl, source, flags=re.UNICODE ) @@ -237,6 +241,86 @@ def _flag_name_stringly_repl(match): ) +@_migrator +def _commands_opaque_type(source): + return re.sub('cli\\.Commands', '[]*cli.Command', source, flags=re.UNICODE) + + +@_migrator +def _flag_names(source): + return re.sub('\\.GetName\\(\\)', '.Names()[0]', source, flags=re.UNICODE) + + +@_migrator +def _app_categories(source): + return re.sub( + '\\.App\\.Categories\\(\\)', '.App.Categories', + source, flags=re.UNICODE + ) + + +@_migrator +def _range_categories(source): + # XXX: brittle + return re.sub( + '(?P:=\\s+range\\s+)categories(?P[^\\.])', + _range_categories_repl, source, flags=re.UNICODE|re.IGNORECASE + ) + + +def _range_categories_repl(match): + return '{prefix}categories.Categories(){suffix}'.format( + **match.groupdict() + ) + + +@_migrator +def _command_category_commands(source): + # XXX: brittle + return re.sub( + '(?P\\s+category\\.)Commands(?P[^(])', + _command_category_commands_repl, source, flags=re.UNICODE + ) + + +def _command_category_commands_repl(match): + return '{prefix}VisibleCommands(){suffix}'.format(**match.groupdict()) + + +@_migrator +def _context_bool_t(source): + # XXX: probably brittle + return re.sub( + '(?P\\S+)(?:Global|)BoolT\\(', + _context_bool_t_repl, source, flags=re.UNICODE + ) + + +def _context_bool_t_repl(match): + return '!{prefix}Bool('.format(**match.groupdict()) + + +@_migrator +def _context_global_methods(source): + return re.sub( + '\\.Global(?P' \ + 'Bool|Duration|Float64|Generic|Int|IntSlice|String|StringSlice|' \ + 'FlagNames|IsSet|Set' \ + ')\\(', + _context_global_methods_repl, source, flags=re.UNICODE + ) + + +def _context_global_methods_repl(match): + return '.{method}('.format(**match.groupdict()) + + +@_migrator +def _context_parent(source): + # XXX: brittle + return re.sub('\\.Parent\\(\\)', '.Lineage()[1]', source, flags=re.UNICODE) + + def test_migrators(): import difflib From 73acde8e690f035024688f293a7400359dc77eb1 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 2 Jun 2016 04:37:36 -0400 Subject: [PATCH 049/158] Make migrators ordered, clean up some duplication --- cli-v1-to-v2 | 112 ++++++++++++++++++--------------------------------- 1 file changed, 40 insertions(+), 72 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index e6b0e9a..b3fb7ca 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -12,8 +12,7 @@ import sys _DESCRIPTION = """\ Migrate arbitrary `.go` sources (mostly) from the v1 to v2 API. """ -_V2_IMPORT = '"gopkg.in/urfave/cli.v2"' -_MIGRATORS = {} +_MIGRATORS = [] def main(sysargs=sys.argv[:]): @@ -90,70 +89,60 @@ def _update_filepath(filepath): def _update_source(source): - for migrator, func in _MIGRATORS.items(): + for migrator, func in _MIGRATORS: logging.info('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[func.__name__.strip('_')] = func + _MIGRATORS.append((func.__name__.strip('_'), func)) return func @_migrator def _slice_pointer_types(source): - return re.sub( + return _subfmt( '(?P\\[\\])cli\\.(?PCommand|Author){', - _slice_pointer_types_repl, source, flags=re.UNICODE + '{prefix}*cli.{type}{{', source ) -def _slice_pointer_types_repl(match): - return '{prefix}*cli.{type}{{'.format(**match.groupdict()) - - @_migrator def _pointer_type_literal(source): - return re.sub( + return _subfmt( '(?P\\s+)cli\\.(?PCommand|Author){', - _pointer_type_literal_repl, source, flags=re.UNICODE + '{prefix}&cli.{type}{{', source ) -def _pointer_type_literal_repl(match): - return '{prefix}&cli.{type}{{'.format(**match.groupdict()) - - @_migrator def _slice_types(source): - return re.sub( + return _subfmt( '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', - _slice_type_repl, source, flags=re.DOTALL|re.UNICODE + 'cli.New{type}({args})', source, flags=re.DOTALL|re.UNICODE ) -def _slice_type_repl(match): - return 'cli.New{type}({args})'.format(**match.groupdict()) - - @_migrator def _flag_literals(source): - return re.sub( + return _subfmt( '(?P\\s+)cli\\.(?P\\w+)Flag{', - _flag_literal_repl, source, flags=re.UNICODE + '{prefix}&cli.{type}Flag{{', source ) -def _flag_literal_repl(match): - return '{prefix}&cli.{type}Flag{{'.format(**match.groupdict()) - - @_migrator def _v1_imports(source): return re.sub( '"(?:github\\.com|gopkg\\.in)/(?:codegangsta|urfave)/cli(?:\\.v1|)"', - _V2_IMPORT, source, flags=re.UNICODE + '"gopkg.in/urfave/cli.v2"', source, flags=re.UNICODE ) @@ -164,28 +153,29 @@ def _new_exit_error(source): @_migrator def _bool_t_flag(source): - return re.sub( + return _subfmt( 'cli\\.BoolTFlag{(?P[^}]*)}', - _bool_t_flag_repl, source, flags=re.DOTALL|re.UNICODE + 'cli.BoolFlag{{Value: true,{args}}}', + source, flags=re.DOTALL|re.UNICODE ) -def _bool_t_flag_repl(match): - return 'cli.BoolFlag{{Value: true,{args}}}'.format(**match.groupdict()) +@_migrator +def _context_args_len(source): + return _subfmt( + 'len\\((?P\\S+)\\.Args\\(\\)\\)', + '{prefix}.Args().Len()', source + ) @_migrator def _context_args_index(source): - return re.sub( + return _subfmt( '\\.Args\\(\\)\\[(?P\\d+)\\]', - _context_args_index_repl, source, flags=re.UNICODE + '.Args().Get({index})', source ) -def _context_args_index_repl(match): - return '.Args().Get({index})'.format(**match.groupdict()) - - @_migrator def _envvar_string(source): return re.sub( @@ -253,68 +243,46 @@ def _flag_names(source): @_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 _range_categories(source): - # XXX: brittle - return re.sub( - '(?P:=\\s+range\\s+)categories(?P[^\\.])', - _range_categories_repl, source, flags=re.UNICODE|re.IGNORECASE - ) - - -def _range_categories_repl(match): - return '{prefix}categories.Categories(){suffix}'.format( - **match.groupdict() - ) - - @_migrator def _command_category_commands(source): # XXX: brittle - return re.sub( + return _subfmt( '(?P\\s+category\\.)Commands(?P[^(])', - _command_category_commands_repl, source, flags=re.UNICODE + '{prefix}VisibleCommands(){suffix}', source ) -def _command_category_commands_repl(match): - return '{prefix}VisibleCommands(){suffix}'.format(**match.groupdict()) - - @_migrator def _context_bool_t(source): # XXX: probably brittle - return re.sub( + return _subfmt( '(?P\\S+)(?:Global|)BoolT\\(', - _context_bool_t_repl, source, flags=re.UNICODE + '!{prefix}Bool(', source ) -def _context_bool_t_repl(match): - return '!{prefix}Bool('.format(**match.groupdict()) - - @_migrator def _context_global_methods(source): - return re.sub( + return _subfmt( '\\.Global(?P' \ 'Bool|Duration|Float64|Generic|Int|IntSlice|String|StringSlice|' \ 'FlagNames|IsSet|Set' \ ')\\(', - _context_global_methods_repl, source, flags=re.UNICODE + '.{method}(', source ) -def _context_global_methods_repl(match): - return '.{method}('.format(**match.groupdict()) - - @_migrator def _context_parent(source): # XXX: brittle From 6dcb4fdfa4a19c33769e1f2ccb04c5e615b05b4d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 2 Jun 2016 04:43:31 -0400 Subject: [PATCH 050/158] Migrator script output tweaks, debug logging --- cli-v1-to-v2 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index b3fb7ca..91f01d5 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -25,6 +25,9 @@ def main(sysargs=sys.argv[:]): 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) @@ -38,10 +41,10 @@ def main(sysargs=sys.argv[:]): test_migrators() return 0 - logging.basicConfig( - level=(logging.FATAL if args.quiet else logging.INFO), - format='%(message)s' - ) + 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: @@ -90,7 +93,7 @@ def _update_filepath(filepath): def _update_source(source): for migrator, func in _MIGRATORS: - logging.info('Running %s migrator', migrator) + logging.debug('Running %s migrator', migrator) source = func(source) return source From c413767d2598dc02bd4f69d1080e6aa40e882268 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 2 Jun 2016 04:45:13 -0400 Subject: [PATCH 051/158] Fixing some flake8 gripes --- cli-v1-to-v2 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 91f01d5..904d50e 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -129,7 +129,7 @@ def _pointer_type_literal(source): def _slice_types(source): return _subfmt( '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', - 'cli.New{type}({args})', source, flags=re.DOTALL|re.UNICODE + 'cli.New{type}({args})', source, flags=re.DOTALL | re.UNICODE ) @@ -159,7 +159,7 @@ def _bool_t_flag(source): return _subfmt( 'cli\\.BoolTFlag{(?P[^}]*)}', 'cli.BoolFlag{{Value: true,{args}}}', - source, flags=re.DOTALL|re.UNICODE + source, flags=re.DOTALL | re.UNICODE ) @@ -278,9 +278,9 @@ def _context_bool_t(source): @_migrator def _context_global_methods(source): return _subfmt( - '\\.Global(?P' \ - 'Bool|Duration|Float64|Generic|Int|IntSlice|String|StringSlice|' \ - 'FlagNames|IsSet|Set' \ + '\\.Global(?P' + 'Bool|Duration|Float64|Generic|Int|IntSlice|String|StringSlice|' + 'FlagNames|IsSet|Set' ')\\(', '.{method}(', source ) From 8ea4b523d2f071d80429130fea9f8e77b1ac4106 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 10 Jun 2016 13:00:33 -0400 Subject: [PATCH 052/158] Style cleanups of full API example in README --- README.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ab502ca..0bd2a5a 100644 --- a/README.md +++ b/README.md @@ -1112,8 +1112,8 @@ func main() { } app.Flags = []cli.Flag{ &cli.BoolFlag{Name: "fancy"}, - &cli.BoolFlag{Value: true,Name: "fancier"}, - &cli.StringFlag{Name: "dance-move, d"}, + &cli.BoolFlag{Value: true, Name: "fancier"}, + &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, } app.EnableBashCompletion = true app.HideHelp = false @@ -1194,17 +1194,7 @@ func main() { fmt.Printf("%#v\n", nc.IntSlice("blups")) fmt.Printf("%#v\n", nc.String("snurt")) fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Bool("global-nope")) - fmt.Printf("%#v\n", !nc.Bool("global-nerp")) - fmt.Printf("%#v\n", nc.Duration("global-howlong")) - fmt.Printf("%#v\n", nc.Float64("global-hay")) - fmt.Printf("%#v\n", nc.Generic("global-bloop")) - fmt.Printf("%#v\n", nc.Int("global-bips")) - fmt.Printf("%#v\n", nc.IntSlice("global-blups")) - fmt.Printf("%#v\n", nc.String("global-snurt")) - fmt.Printf("%#v\n", nc.StringSlice("global-snurkles")) - fmt.Printf("%#v\n", nc.FlagNames()) fmt.Printf("%#v\n", nc.FlagNames()) fmt.Printf("%#v\n", nc.IsSet("wat")) fmt.Printf("%#v\n", nc.Set("wat", "nope")) @@ -1226,8 +1216,8 @@ func main() { } app.Metadata = map[string]interface{}{ - "layers": "many", - "explicable": false, + "layers": "many", + "explicable": false, "whatever-values": 19.99, } From 0c9b549918e1c3be9c927a4ceec0d0cc3f1b71ba Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 09:54:54 -0400 Subject: [PATCH 053/158] Ensure remaining v1 additions are v2 compatible --- .travis.yml | 3 + flag.go | 160 ++++++++++++++++++++++++++++++--------------------- flag_test.go | 45 +++++++++------ 3 files changed, 126 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3225cc1..84c3828 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,6 +36,9 @@ before_script: - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi +- mkdir -p ~/gopath/src/gopkg.in/urfave +- rm -rf ~/gopath/src/gopkg.in/urfave/cli.v2 +- ln -sv ~/gopath/src/github.com/urfave/cli ~/gopath/src/gopkg.in/urfave/cli.v2 script: - flake8 runtests cli-v1-to-v2 diff --git a/flag.go b/flag.go index 2e6caf4..3ff53ce 100644 --- a/flag.go +++ b/flag.go @@ -219,6 +219,11 @@ func NewIntSlice(defaults ...int) *IntSlice { return &IntSlice{slice: append([]int{}, defaults...)} } +// NewInt64Slice makes an *Int64Slice with default values +func NewInt64Slice(defaults ...int64) *Int64Slice { + return &Int64Slice{slice: append([]int64{}, defaults...)} +} + // SetInt directly adds an integer to the list of values func (i *IntSlice) SetInt(value int) { if !i.hasBeenSet { @@ -243,12 +248,12 @@ func (i *IntSlice) Set(value string) error { return nil } - tmp, err := strconv.Atoi(value) + tmp, err := strconv.ParseInt(value, 0, 64) if err != nil { return err } - i.slice = append(i.slice, tmp) + i.slice = append(i.slice, int(tmp)) return nil } @@ -318,50 +323,72 @@ func (f *IntSliceFlag) Names() []string { } // Int64Slice is an opaque type for []int to satisfy flag.Value -type Int64Slice []int64 +type Int64Slice struct { + slice []int64 + hasBeenSet bool +} // Set parses the value into an integer and appends it to the list of values func (f *Int64Slice) Set(value string) error { - tmp, err := strconv.ParseInt(value, 10, 64) + if !f.hasBeenSet { + f.slice = []int64{} + f.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) + f.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseInt(value, 0, 64) if err != nil { return err } - *f = append(*f, tmp) + + f.slice = append(f.slice, tmp) return nil } // String returns a readable representation of this value (for usage defaults) func (f *Int64Slice) String() string { - return fmt.Sprintf("%#v", *f) + return fmt.Sprintf("%#v", f.slice) +} + +// Serialized allows Int64Slice to fulfill Serializeder +func (f *Int64Slice) Serialized() string { + jsonBytes, _ := json.Marshal(f.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) } // Value returns the slice of ints set by this flag func (f *Int64Slice) Value() []int64 { - return *f + return f.slice } // Int64SliceFlag is an int flag that can be specified multiple times on the // command-line type Int64SliceFlag struct { - Name string - Value *Int64Slice - Usage string - EnvVar string - Hidden bool + Name string + Aliases []string + Value *Int64Slice + Usage string + EnvVars []string + Hidden bool } // String returns the usage -func (f Int64SliceFlag) String() string { +func (f *Int64SliceFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f Int64SliceFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { - newVal := &Int64Slice{} + newVal := NewInt64Slice() for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) err := newVal.Set(s) @@ -375,17 +402,18 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { - if f.Value == nil { - f.Value = &Int64Slice{} - } + if f.Value == nil { + f.Value = NewInt64Slice() + } + + for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f Int64SliceFlag) GetName() string { - return f.Name +// Names returns the names of the flag. +func (f *Int64SliceFlag) Names() []string { + return flagNames(f) } // BoolFlag is a switch that defaults to false @@ -520,23 +548,23 @@ func (f *IntFlag) Names() []string { // Int64Flag is a flag that takes a 64-bit integer type Int64Flag struct { Name string + Aliases []string Value int64 Usage string - EnvVar string + EnvVars []string Destination *int64 Hidden bool } // String returns the usage -func (f Int64Flag) String() string { +func (f *Int64Flag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f Int64Flag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) +func (f *Int64Flag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err == nil { @@ -547,40 +575,40 @@ func (f Int64Flag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.Int64Var(f.Destination, name, f.Value, f.Usage) return } set.Int64(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f Int64Flag) GetName() string { - return f.Name +// Names returns the names of the flag. +func (f *Int64Flag) Names() []string { + return flagNames(f) } // UintFlag is a flag that takes an unsigned integer type UintFlag struct { Name string + Aliases []string Value uint Usage string - EnvVar string + EnvVars []string Destination *uint Hidden bool } // String returns the usage -func (f UintFlag) String() string { +func (f *UintFlag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f UintFlag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) +func (f *UintFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { envValInt, err := strconv.ParseUint(envVal, 0, 64) if err == nil { @@ -591,40 +619,40 @@ func (f UintFlag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.UintVar(f.Destination, name, f.Value, f.Usage) return } set.Uint(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f UintFlag) GetName() string { - return f.Name +// Names returns the names of the flag. +func (f *UintFlag) Names() []string { + return flagNames(f) } // Uint64Flag is a flag that takes an unsigned 64-bit integer type Uint64Flag struct { Name string + Aliases []string Value uint64 Usage string - EnvVar string + EnvVars []string Destination *uint64 Hidden bool } // String returns the usage -func (f Uint64Flag) String() string { +func (f *Uint64Flag) String() string { return FlagStringer(f) } // Apply populates the flag given the flag set and environment -func (f Uint64Flag) Apply(set *flag.FlagSet) { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) +func (f *Uint64Flag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { if envVal := os.Getenv(envVar); envVal != "" { envValInt, err := strconv.ParseUint(envVal, 0, 64) if err == nil { @@ -635,18 +663,18 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) { } } - eachName(f.Name, func(name string) { + for _, name := range f.Names() { if f.Destination != nil { set.Uint64Var(f.Destination, name, f.Value, f.Usage) return } set.Uint64(name, f.Value, f.Usage) - }) + } } -// GetName returns the name of the flag. -func (f Uint64Flag) GetName() string { - return f.Name +// Names returns the names of the flag. +func (f *Uint64Flag) Names() []string { + return flagNames(f) } // DurationFlag is a flag that takes a duration specified in Go's duration @@ -860,13 +888,13 @@ func stringifyFlag(f Flag) string { switch f.(type) { case *IntSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(*IntSliceFlag))) case *Int64SliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyInt64SliceFlag(f.(*Int64SliceFlag))) case *StringSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(*StringSliceFlag))) } @@ -907,10 +935,10 @@ func stringifyIntSliceFlag(f *IntSliceFlag) string { } } - return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } -func stringifyInt64SliceFlag(f Int64SliceFlag) string { +func stringifyInt64SliceFlag(f *Int64SliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { @@ -918,7 +946,7 @@ func stringifyInt64SliceFlag(f Int64SliceFlag) string { } } - return stringifySliceFlag(f.Usage, f.Name, defaultVals) + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } func stringifyStringSliceFlag(f *StringSliceFlag) string { @@ -931,7 +959,7 @@ func stringifyStringSliceFlag(f *StringSliceFlag) string { } } - return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } func stringifySliceFlag(usage string, names, defaultVals []string) string { diff --git a/flag_test.go b/flag_test.go index c82f4d3..1b69f51 100644 --- a/flag_test.go +++ b/flag_test.go @@ -215,7 +215,7 @@ func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range int64FlagTests { - flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -251,7 +251,7 @@ func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range uintFlagTests { - flag := UintFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -287,7 +287,7 @@ func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_BAR", "2") for _, test := range uint64FlagTests { - flag := UintFlag{Name: test.name, EnvVar: "APP_BAR"} + flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() expectedSuffix := " [$APP_BAR]" @@ -397,22 +397,19 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { var int64SliceFlagTests = []struct { name string + aliases []string value *Int64Slice expected string }{ - {"heads", &Int64Slice{}, "--heads value\t"}, - {"H", &Int64Slice{}, "-H value\t"}, - {"H, heads", func() *Int64Slice { - i := &Int64Slice{} - i.Set("2") - i.Set("17179869184") - return i - }(), "-H value, --heads value\t(default: 2, 17179869184)"}, + {"heads", nil, NewInt64Slice(), "--heads value\t"}, + {"H", nil, NewInt64Slice(), "-H value\t"}, + {"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)), + "--heads value, -H value\t(default: 2, 17179869184)"}, } func TestInt64SliceFlagHelpOutput(t *testing.T) { for _, test := range int64SliceFlagTests { - flag := Int64SliceFlag{Name: test.name, Value: test.value} + flag := Int64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} output := flag.String() if output != test.expected { @@ -425,7 +422,7 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_SMURF", "42,17179869184") for _, test := range int64SliceFlagTests { - flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} + flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() expectedSuffix := " [$APP_SMURF]" @@ -929,7 +926,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { func TestParseMultiInt64Slice(t *testing.T) { (&App{ Flags: []Flag{ - Int64SliceFlag{Name: "serve, s", Value: &Int64Slice{}}, + &Int64SliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewInt64Slice()}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Int64Slice("serve"), []int64{10, 17179869184}) { @@ -949,7 +946,7 @@ func TestParseMultiInt64SliceFromEnv(t *testing.T) { (&App{ Flags: []Flag{ - Int64SliceFlag{Name: "intervals, i", Value: &Int64Slice{}, EnvVar: "APP_INTERVALS"}, + &Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewInt64Slice(), EnvVars: []string{"APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { @@ -969,7 +966,7 @@ func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { (&App{ Flags: []Flag{ - Int64SliceFlag{Name: "intervals, i", Value: &Int64Slice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + &Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewInt64Slice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { @@ -1339,3 +1336,19 @@ func TestIntSlice_Serialized_Set(t *testing.T) { t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) } } + +func TestInt64Slice_Serialized_Set(t *testing.T) { + sl0 := NewInt64Slice(int64(1), int64(2)) + ser0 := sl0.Serialized() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewInt64Slice(int64(3), int64(4)) + sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} From 9e21e0e342fc012f6e980d2d847ae979ab821a36 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 10:00:12 -0400 Subject: [PATCH 054/158] Fix up the full API example in the README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 15097d4..4e80447 100644 --- a/README.md +++ b/README.md @@ -1047,7 +1047,7 @@ func init() { cli.HelpFlag = &cli.BoolFlag{Name: "halp"} cli.BashCompletionFlag = &cli.BoolFlag{Name: "compgen", Hidden: true} - cli.VersionFlag = &cli.BoolFlag{Name: "print-version, V"} + cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { fmt.Fprintf(w, "best of luck to you\n") @@ -1114,7 +1114,7 @@ func main() { Description: "no really, there is a lot of dooing to be done", ArgsUsage: "[arrgh]", Flags: []cli.Flag{ - &cli.BoolFlag{Name: "forever, forevvarr"}, + &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, }, Subcommands: []*cli.Command{ &cli.Command{ @@ -1157,15 +1157,15 @@ func main() { app.Flags = []cli.Flag{ &cli.BoolFlag{Name: "fancy"}, &cli.BoolFlag{Value: true, Name: "fancier"}, - &cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, + &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, &cli.Float64Flag{Name: "howmuch"}, &cli.GenericFlag{Name: "wat", Value: &genericType{}}, &cli.Int64Flag{Name: "longdistance"}, &cli.Int64SliceFlag{Name: "intervals"}, &cli.IntFlag{Name: "distance"}, &cli.IntSliceFlag{Name: "times"}, - &cli.StringFlag{Name: "dance-move, d"}, - &cli.StringSliceFlag{Name: "names, N"}, + &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, + &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, &cli.UintFlag{Name: "age"}, &cli.Uint64Flag{Name: "bigage"}, } From dc55e12942ecba68ede4f07b5f3f2995acd395ed Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 10:01:34 -0400 Subject: [PATCH 055/158] Pull the full API example up a level --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e80447..7268140 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ applications in an expressive way. + [Customization](#customization-1) * [Version Flag](#version-flag) + [Customization](#customization-2) - + [Full API Example](#full-api-example) + * [Full API Example](#full-api-example) - [Contribution Guidelines](#contribution-guidelines) @@ -1017,7 +1017,7 @@ func main() { } ``` -#### Full API Example +### Full API Example **NOTE**: This is a contrived (functioning) example meant strictly for API demonstration purposes. Use of one's imagination is encouraged. From 6e6a80c4d3176bc9f2eb46d83ce89aaa04b0c375 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 10:19:23 -0400 Subject: [PATCH 056/158] Try symlinking into gopkg.in/urfave/cli.v2 better --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 84c3828..5584de2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ matrix: env: pip_install="sudo pip install" - go: 1.1.2 install: go get -v . - before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION + before_script: echo skipping gfmxr on ${TRAVIS_GO_VERSION} script: - ./runtests vet - ./runtests test @@ -36,9 +36,9 @@ before_script: - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi -- mkdir -p ~/gopath/src/gopkg.in/urfave -- rm -rf ~/gopath/src/gopkg.in/urfave/cli.v2 -- ln -sv ~/gopath/src/github.com/urfave/cli ~/gopath/src/gopkg.in/urfave/cli.v2 +- mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave +- rm -rf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 script: - flake8 runtests cli-v1-to-v2 From 3357fcd631e85e8018a1d3397d563711a661405a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 10:31:05 -0400 Subject: [PATCH 057/158] Try linking to gopkg.in/urfave/cli.v2 on Appveyor, too --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index ae80b2f..3ec36e6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,6 +18,8 @@ install: - go env - go get github.com/urfave/gfmxr/... - go get -v -t ./... +- mkdir c:\gopath\src\gopkg.in\urfave +- mklink c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 build_script: - python runtests vet @@ -25,3 +27,4 @@ build_script: - python runtests gfmxr - python cli-v1-to-v2 --selftest - python runtests migrations +- python runtests toc From cafccda7b9dd061b2371a13d35b89f01ea458a42 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 10:42:44 -0400 Subject: [PATCH 058/158] Install gfmxr after symlinking into gopkg.in/urfave/cli.v2 --- .travis.yml | 4 ++-- appveyor.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5584de2..c879139 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,14 +31,14 @@ matrix: - ./runtests test before_script: -- go get github.com/urfave/gfmxr/... - $pip_install flake8 - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi - mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave -- rm -rf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 - ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- go get github.com/urfave/gfmxr/... script: - flake8 runtests cli-v1-to-v2 diff --git a/appveyor.yml b/appveyor.yml index 3ec36e6..720f4d3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,10 +16,10 @@ install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env -- go get github.com/urfave/gfmxr/... -- go get -v -t ./... - mkdir c:\gopath\src\gopkg.in\urfave - mklink c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 +- go get github.com/urfave/gfmxr/... +- go get -v -t ./... build_script: - python runtests vet From 5b14460f4e52085dec55b38cf9356d8405da651f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 10:48:56 -0400 Subject: [PATCH 059/158] Use xcopy instead of mklink on Appveyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 720f4d3..8f9010d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,7 +17,7 @@ install: - go version - go env - mkdir c:\gopath\src\gopkg.in\urfave -- mklink c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 +- xcopy /E c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... From c07b18df3a43e4fedbe0e746293868933f71720c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 11:13:55 -0400 Subject: [PATCH 060/158] Try out robocopy maybe? --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 8f9010d..a3a5df3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,7 +17,7 @@ install: - go version - go env - mkdir c:\gopath\src\gopkg.in\urfave -- xcopy /E c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 +- robocopy c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 /E - go get github.com/urfave/gfmxr/... - go get -v -t ./... From 54df4b6c6dc52f4c25edf88403484e38a7532962 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 11:20:49 -0400 Subject: [PATCH 061/158] Try a more fancy robocopy command --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index a3a5df3..13147a5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,7 +17,8 @@ install: - go version - go env - mkdir c:\gopath\src\gopkg.in\urfave -- robocopy c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 /E +- robocopy c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 + /DCOPY:DA /MIR /FFT /Z /XA:SH /R:0 /TEE /XJD - go get github.com/urfave/gfmxr/... - go get -v -t ./... From 98218064c0bd1e8f7576461dcf4fe43eaf2fec2a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 11:31:18 -0400 Subject: [PATCH 062/158] Straighten out robocopy exit code --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 13147a5..b2aa9e3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,8 +17,8 @@ install: - go version - go env - mkdir c:\gopath\src\gopkg.in\urfave -- robocopy c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 - /DCOPY:DA /MIR /FFT /Z /XA:SH /R:0 /TEE /XJD +- (robocopy c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 + /DCOPY:DA /MIR /FFT /Z /XA:SH /R:0 /TEE /XJD) ^& IF %ERRORLEVEL% LEQ 4 exit 0 - go get github.com/urfave/gfmxr/... - go get -v -t ./... From 7cb40bd5762c3042e92a6f0ea42516ba4ee48d81 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 17 Jun 2016 11:36:23 -0400 Subject: [PATCH 063/158] Fighting more with cmd --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b2aa9e3..f51a9e2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,8 +17,8 @@ install: - go version - go env - mkdir c:\gopath\src\gopkg.in\urfave -- (robocopy c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 - /DCOPY:DA /MIR /FFT /Z /XA:SH /R:0 /TEE /XJD) ^& IF %ERRORLEVEL% LEQ 4 exit 0 +- robocopy c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 + /DCOPY:DA /MIR /FFT /Z /XA:SH /R:0 /TEE /XJD ^& IF %ERRORLEVEL% LEQ 4 exit 0 - go get github.com/urfave/gfmxr/... - go get -v -t ./... From a8027b8491ff7c5bb684b70a1e1041348ccc1447 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 15:20:35 -0400 Subject: [PATCH 064/158] Use git to make a copy into urfave/cli.v2 --- .travis.yml | 4 +--- appveyor.yml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index c879139..46bd7ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,9 +35,7 @@ before_script: - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi -- mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave -- rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 -- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- git clone . ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 - go get github.com/urfave/gfmxr/... script: diff --git a/appveyor.yml b/appveyor.yml index f51a9e2..ae91cd4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,9 +16,7 @@ install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env -- mkdir c:\gopath\src\gopkg.in\urfave -- robocopy c:\gopath\src\github.com\urfave\cli c:\gopath\src\gopkg.in\urfave\cli.v2 - /DCOPY:DA /MIR /FFT /Z /XA:SH /R:0 /TEE /XJD ^& IF %ERRORLEVEL% LEQ 4 exit 0 +- git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... From 11311185a0592038140a3ef50b439a59d4ade145 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 15:47:23 -0400 Subject: [PATCH 065/158] Switch back to symlinking on Linux and Mac and fix existence of markdown-toc on Windows --- .travis.yml | 4 +++- appveyor.yml | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 46bd7ef..c879139 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,9 @@ before_script: - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi -- git clone . ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave +- rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 - go get github.com/urfave/gfmxr/... script: diff --git a/appveyor.yml b/appveyor.yml index ae91cd4..a56f1db 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,6 +4,9 @@ os: Windows Server 2012 R2 clone_folder: c:\gopath\src\github.com\urfave\cli +cache: +- node_modules + environment: GOPATH: C:\gopath GOVERSION: 1.6 @@ -19,6 +22,9 @@ install: - git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... +- ps: Install-Product node 6 +- ps: $MarkdownTocExists = Test-Path node_modules\.bin\markdown-toc +- ps: if ($MarkdownTocExists -eq $False) { npm install markdown-toc } build_script: - python runtests vet From 29dc3f0c7c82125bfaa53140b4a067eef2a1d9b8 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 15:59:00 -0400 Subject: [PATCH 066/158] More tweaks to npm usage on Windows --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a56f1db..d278a06 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,9 +22,9 @@ install: - git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... -- ps: Install-Product node 6 -- ps: $MarkdownTocExists = Test-Path node_modules\.bin\markdown-toc -- ps: if ($MarkdownTocExists -eq $False) { npm install markdown-toc } +- ps: if (Test-Path node_modules\.bin\markdown-toc -eq $False) { + npm install markdown-toc + } build_script: - python runtests vet From c24cac3f932215d99ec5e20ad6c98b7b2a2cdf81 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 15:59:54 -0400 Subject: [PATCH 067/158] Try to YAML betterer --- appveyor.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index d278a06..59cfa9b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,9 +22,7 @@ install: - git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... -- ps: if (Test-Path node_modules\.bin\markdown-toc -eq $False) { - npm install markdown-toc - } +- ps: if (Test-Path node_modules\.bin\markdown-toc -eq $False) { npm install markdown-toc } build_script: - python runtests vet From 8eaf6be8fae1df4825d3de1d8edc15e137cdd50a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 16:04:39 -0400 Subject: [PATCH 068/158] Trying to powershell better --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 59cfa9b..610f5c0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,8 @@ install: - git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... -- ps: if (Test-Path node_modules\.bin\markdown-toc -eq $False) { npm install markdown-toc } +- ps: $MarkdownTocExists = Test-Path node_modules\.bin\markdown-toc +- ps: if ($MarkdownTocExists -eq $False) { npm install markdown-toc } build_script: - python runtests vet From dec06d28379085c1b74a5017489f6d5693bc72f9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 16:20:29 -0400 Subject: [PATCH 069/158] Dynamically find path of markdown-toc executable --- runtests | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtests b/runtests index 85656c7..40c7809 100755 --- a/runtests +++ b/runtests @@ -94,7 +94,8 @@ def _migrations(): @_target def _toc(): - _run(['node_modules/.bin/markdown-toc', '-i', 'README.md']) + npm_bindir = check_output(['npm', 'bin']).strip() + _run(['node', os.path.join(npm_bindir, 'markdown-toc'), '-i', 'README.md']) _run(['git', 'diff', '--quiet']) From 0ef116788d3e28646e642b2b7af4079ab6e70e0a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 16:24:46 -0400 Subject: [PATCH 070/158] Run npm install in cmd subshell on Windows --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 610f5c0..004949d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,7 @@ install: - go get github.com/urfave/gfmxr/... - go get -v -t ./... - ps: $MarkdownTocExists = Test-Path node_modules\.bin\markdown-toc -- ps: if ($MarkdownTocExists -eq $False) { npm install markdown-toc } +- ps: if ($MarkdownTocExists -eq $False) { cmd /c npm install markdown-toc } build_script: - python runtests vet From 21219e2b65ee1bbb65a46f1e8edb326cd30054b6 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 16:28:50 -0400 Subject: [PATCH 071/158] Flush stderr after every print --- runtests | 1 + 1 file changed, 1 insertion(+) diff --git a/runtests b/runtests index 40c7809..e762074 100755 --- a/runtests +++ b/runtests @@ -101,6 +101,7 @@ def _toc(): def _run(command): print('runtests: {}'.format(' '.join(command)), file=sys.stderr) + sys.stderr.flush() check_call(command) From 2569db1dc9ba68eed7606a32cd55a27b89ef230a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 16:29:33 -0400 Subject: [PATCH 072/158] Where is npm bindir? --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 004949d..2a5c07d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,6 +22,7 @@ install: - git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... +- npm bin - ps: $MarkdownTocExists = Test-Path node_modules\.bin\markdown-toc - ps: if ($MarkdownTocExists -eq $False) { cmd /c npm install markdown-toc } From d2596f4c86fa4517d2826fa61996d01f6228e922 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 16:31:05 -0400 Subject: [PATCH 073/158] Disable gfmxr debugging on Windows --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2a5c07d..353a28e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,6 @@ environment: PYTHON: C:\Python27-x64 PYTHON_VERSION: 2.7.x PYTHON_ARCH: 64 - GFMXR_DEBUG: 1 install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% From 8cfdccbf0fbec9c3badb8745f2e0cf17d27e5ec5 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 16:37:11 -0400 Subject: [PATCH 074/158] More tweaks to how `./runtests toc` runs on Windows --- appveyor.yml | 4 +--- runtests | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 353a28e..90de4d5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,9 +21,7 @@ install: - git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... -- npm bin -- ps: $MarkdownTocExists = Test-Path node_modules\.bin\markdown-toc -- ps: if ($MarkdownTocExists -eq $False) { cmd /c npm install markdown-toc } +- if not exist node_modules\.bin\markdown-toc npm install markdown-toc build_script: - python runtests vet diff --git a/runtests b/runtests index e762074..d531588 100755 --- a/runtests +++ b/runtests @@ -94,8 +94,10 @@ def _migrations(): @_target def _toc(): - npm_bindir = check_output(['npm', 'bin']).strip() - _run(['node', os.path.join(npm_bindir, 'markdown-toc'), '-i', 'README.md']) + _run([ + 'node', os.path.join('node_modules', '.bin', 'markdown-toc'), + '-i', 'README.md' + ]) _run(['git', 'diff', '--quiet']) From 77722a46e115c4f00a78a1105cde74aa7f0d3b13 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2016 16:46:41 -0400 Subject: [PATCH 075/158] Explicitly run markdown-toc with bash on Windows --- runtests | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/runtests b/runtests index d531588..5b903e6 100755 --- a/runtests +++ b/runtests @@ -4,6 +4,7 @@ from __future__ import print_function import argparse import glob import os +import platform import shutil import sys import tempfile @@ -11,6 +12,7 @@ import tempfile from subprocess import check_call, check_output +_WINDOWS = platform.system().lower() == 'windows' _PACKAGE_NAME = os.environ.get( 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' ) @@ -94,8 +96,9 @@ def _migrations(): @_target def _toc(): - _run([ - 'node', os.path.join('node_modules', '.bin', 'markdown-toc'), + exe = ['bash'] if _WINDOWS else [] + _run(exe + [ + os.path.join('node_modules', '.bin', 'markdown-toc'), '-i', 'README.md' ]) _run(['git', 'diff', '--quiet']) From df685fbacce5932609474568d4ae352b9e996355 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 22 Jun 2016 12:47:57 -0400 Subject: [PATCH 076/158] Remove `NewApp` initializer and move defaulting to `App.Setup` --- README.md | 732 ++++++++++++++++++------------------ altsrc/yaml_command_test.go | 18 +- app.go | 51 ++- app_test.go | 672 +++++++++++++++++---------------- cli-v1-to-v2 | 12 + cli.go | 15 +- command.go | 9 +- command_test.go | 47 +-- help_test.go | 13 +- 9 files changed, 808 insertions(+), 761 deletions(-) diff --git a/README.md b/README.md index 52a050a..812268d 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ import ( ) func main() { - cli.NewApp().Run(os.Args) + (&cli.App{}).Run(os.Args) } ``` @@ -167,12 +167,13 @@ import ( ) func main() { - app := cli.NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Action = func(c *cli.Context) error { - fmt.Println("boom! I say!") - return nil + app := &cli.App{ + Name: "boom", + Usage: "make an explosive entrance", + Action: func(c *cli.Context) error { + fmt.Println("boom! I say!") + return nil + }, } app.Run(os.Args) @@ -205,12 +206,13 @@ import ( ) func main() { - app := cli.NewApp() - app.Name = "greet" - app.Usage = "fight the loneliness!" - app.Action = func(c *cli.Context) error { - fmt.Println("Hello friend!") - return nil + app := &cli.App{ + Name: "greet", + Usage: "fight the loneliness!", + Action: func(c *cli.Context) error { + fmt.Println("Hello friend!") + return nil + }, } app.Run(os.Args) @@ -268,11 +270,11 @@ import ( ) func main() { - app := cli.NewApp() - - app.Action = func(c *cli.Context) error { - fmt.Printf("Hello %q", c.Args().Get(0)) - return nil + app := &cli.App{ + Action: func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + }, } app.Run(os.Args) @@ -297,27 +299,26 @@ import ( ) func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Action: func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil }, - } - - app.Action = func(c *cli.Context) error { - name := "Nefertiti" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil } app.Run(os.Args) @@ -343,28 +344,27 @@ import ( func main() { var language string - app := cli.NewApp() - - app.Flags = []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + }, + Action: func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil }, - } - - app.Action = func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil } app.Run(os.Args) @@ -394,13 +394,13 @@ import ( ) func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, }, } @@ -436,14 +436,14 @@ import ( ) func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + }, }, } @@ -473,15 +473,15 @@ import ( ) func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"APP_LANG"}, + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, + }, }, } @@ -506,15 +506,15 @@ import ( ) func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, + }, }, } @@ -569,21 +569,20 @@ import ( ) func main() { - app := cli.NewApp() - flags := []cli.Flag{ altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), &cli.StringFlag{Name: "load"}, } - app.Action = func(c *cli.Context) error { - fmt.Println("yaml ist rad") - return nil + app := &cli.App{ + Action: func(c *cli.Context) error { + fmt.Println("yaml ist rad") + return nil + }, + Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), + Flags: flags, } - app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) - app.Flags = flags - app.Run(os.Args) } ``` @@ -607,46 +606,46 @@ import ( ) func main() { - app := cli.NewApp() - - app.Commands = []*cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []*cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, }, }, }, @@ -675,19 +674,19 @@ import ( ) func main() { - app := cli.NewApp() - - app.Commands = []*cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "template", - }, - { - Name: "remove", - Category: "template", + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, }, } @@ -723,19 +722,20 @@ import ( ) func main() { - app := cli.NewApp() - app.Flags = []cli.Flag{ - &cli.BoolFlag{ - Name: "ginger-crouton", - Value: true, - Usage: "is it in the soup?", + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "ginger-crouton", + Value: true, + Usage: "is it in the soup?", + }, + }, + Action: func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.Exit("it is not in the soup", 86) + } + return nil }, - } - app.Action = func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.Exit("it is not in the soup", 86) - } - return nil } app.Run(os.Args) @@ -766,25 +766,26 @@ import ( func main() { tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []*cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - BashComplete: func(c *cli.Context) { - // This will complete if no args are passed - if c.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, }, }, } @@ -840,11 +841,12 @@ func main() { Hidden: true, } - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []*cli.Command{ - { - Name: "wat", + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "wat", + }, }, } app.Run(os.Args) @@ -919,7 +921,7 @@ VERSION: fmt.Println("Ha HA. I pwnd the help!!1") } - cli.NewApp().Run(os.Args) + (&cli.App{}).Run(os.Args) } ``` @@ -946,7 +948,7 @@ func main() { EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, } - cli.NewApp().Run(os.Args) + (&cli.App{}).Run(os.Args) } ``` @@ -980,9 +982,10 @@ func main() { Usage: "print only the version", } - app := cli.NewApp() - app.Name = "partay" - app.Version = "v19.99.0" + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } app.Run(os.Args) } ``` @@ -1012,9 +1015,10 @@ func main() { fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) } - app := cli.NewApp() - app.Name = "partay" - app.Version = "v19.99.0" + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } app.Run(os.Args) } ``` @@ -1091,183 +1095,189 @@ func (g *genericType) String() string { } func main() { - app := cli.NewApp() - app.Name = "kənˈtrīv" - app.Version = "v19.99.0" - app.Compiled = time.Now() - app.Authors = []*cli.Author{ - &cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - } - app.Copyright = "(c) 1999 Serious Enterprise" - app.HelpName = "contrive" - app.Usage = "demonstrate available API" - app.UsageText = "contrive - demonstrating the available API" - app.ArgsUsage = "[args and such]" - app.Commands = []*cli.Command{ - &cli.Command{ - Name: "doo", - Aliases: []string{"do"}, - Category: "motion", - Usage: "do the doo", - UsageText: "doo - does the dooing", - Description: "no really, there is a lot of dooing to be done", - ArgsUsage: "[arrgh]", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, + app := cli.App{ + Name: "kənˈtrīv", + Version: "v19.99.0", + Compiled: time.Now(), + Authors: []*cli.Author{ + &cli.Author{ + Name: "Example Human", + Email: "human@example.com", }, - Subcommands: []*cli.Command{ - &cli.Command{ - Name: "wop", - Action: wopAction, + }, + Copyright: "(c) 1999 Serious Enterprise", + HelpName: "contrive", + Usage: "demonstrate available API", + UsageText: "contrive - demonstrating the available API", + ArgsUsage: "[args and such]", + Commands: []*cli.Command{ + &cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, + }, + Subcommands: []*cli.Command{ + &cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err }, }, - SkipFlagParsing: false, - HideHelp: false, - Hidden: false, - HelpName: "doo!", - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "--better\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "brace for impact\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") - return nil - }, - Action: func(c *cli.Context) error { - c.Command.FullName() - c.Command.HasName("wop") - c.Command.Names() - c.Command.VisibleFlags() - fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") - if c.Bool("forever") { - c.Command.Run(c) - } - return nil - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - fmt.Fprintf(c.App.Writer, "for shame\n") - return err - }, }, - } - app.Flags = []cli.Flag{ - &cli.BoolFlag{Name: "fancy"}, - &cli.BoolFlag{Value: true, Name: "fancier"}, - &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, - &cli.Float64Flag{Name: "howmuch"}, - &cli.GenericFlag{Name: "wat", Value: &genericType{}}, - &cli.Int64Flag{Name: "longdistance"}, - &cli.Int64SliceFlag{Name: "intervals"}, - &cli.IntFlag{Name: "distance"}, - &cli.IntSliceFlag{Name: "times"}, - &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, - &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, - &cli.UintFlag{Name: "age"}, - &cli.Uint64Flag{Name: "bigage"}, - } - app.EnableBashCompletion = true - app.HideHelp = false - app.HideVersion = false - app.BashComplete = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - } - app.Before = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") - return nil - } - app.After = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "Phew!\n") - return nil - } - app.CommandNotFound = func(c *cli.Context, command string) { - fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) - } - app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { - if isSubcommand { - return err - } + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "fancy"}, + &cli.BoolFlag{Value: true, Name: "fancier"}, + &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, + &cli.Float64Flag{Name: "howmuch"}, + &cli.GenericFlag{Name: "wat", Value: &genericType{}}, + &cli.Int64Flag{Name: "longdistance"}, + &cli.Int64SliceFlag{Name: "intervals"}, + &cli.IntFlag{Name: "distance"}, + &cli.IntSliceFlag{Name: "times"}, + &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, + &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, + &cli.UintFlag{Name: "age"}, + &cli.Uint64Flag{Name: "bigage"}, + }, + EnableBashCompletion: true, + HideHelp: false, + HideVersion: false, + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + }, + CommandNotFound: func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } - fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) - return nil - } - app.Action = func(c *cli.Context) error { - cli.DefaultAppComplete(c) - cli.HandleExitCoder(errors.New("not an exit coder, though")) - cli.ShowAppHelp(c) - cli.ShowCommandCompletions(c, "nope") - cli.ShowCommandHelp(c, "also-nope") - cli.ShowCompletions(c) - cli.ShowSubcommandHelp(c) - cli.ShowVersion(c) + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + }, + Action: func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) - categories := c.App.Categories - categories.AddCommand("sounds", &cli.Command{ - Name: "bloop", - }) + categories := c.App.Categories + categories.AddCommand("sounds", &cli.Command{ + Name: "bloop", + }) - for _, category := range c.App.Categories.Categories() { - fmt.Fprintf(c.App.Writer, "%s\n", category.Name) - fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) - fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) - } + for _, category := range c.App.Categories.Categories() { + fmt.Fprintf(c.App.Writer, "%s\n", category.Name) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + } - fmt.Printf("%#v\n", c.App.Command("doo")) - if c.Bool("infinite") { - c.App.Run([]string{"app", "doo", "wop"}) - } + fmt.Printf("%#v\n", c.App.Command("doo")) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } - if c.Bool("forevar") { - c.App.RunAsSubcommand(c) - } - c.App.Setup() - fmt.Printf("%#v\n", c.App.VisibleCategories()) - fmt.Printf("%#v\n", c.App.VisibleCommands()) - fmt.Printf("%#v\n", c.App.VisibleFlags()) + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } + c.App.Setup() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) - fmt.Printf("%#v\n", c.Args().First()) - if c.Args().Len() > 0 { - fmt.Printf("%#v\n", c.Args().Get(1)) - } - fmt.Printf("%#v\n", c.Args().Present()) - fmt.Printf("%#v\n", c.Args().Tail()) + fmt.Printf("%#v\n", c.Args().First()) + if c.Args().Len() > 0 { + fmt.Printf("%#v\n", c.Args().Get(1)) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) - set := flag.NewFlagSet("contrive", 0) - nc := cli.NewContext(c.App, set, c) + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) - fmt.Printf("%#v\n", nc.Args()) - fmt.Printf("%#v\n", nc.Bool("nope")) - fmt.Printf("%#v\n", !nc.Bool("nerp")) - fmt.Printf("%#v\n", nc.Duration("howlong")) - fmt.Printf("%#v\n", nc.Float64("hay")) - fmt.Printf("%#v\n", nc.Generic("bloop")) - fmt.Printf("%#v\n", nc.Int64("bonk")) - fmt.Printf("%#v\n", nc.Int64Slice("burnks")) - fmt.Printf("%#v\n", nc.Int("bips")) - fmt.Printf("%#v\n", nc.IntSlice("blups")) - fmt.Printf("%#v\n", nc.String("snurt")) - fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Uint("flub")) - fmt.Printf("%#v\n", nc.Uint64("florb")) + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", !nc.Bool("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) - fmt.Printf("%#v\n", nc.FlagNames()) - fmt.Printf("%#v\n", nc.IsSet("wat")) - fmt.Printf("%#v\n", nc.Set("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Lineage()[1]) + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.IsSet("wat")) + fmt.Printf("%#v\n", nc.Set("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Lineage()[1]) - nc.Set("wat", "also-nope") + nc.Set("wat", "also-nope") - ec := cli.Exit("ohwell", 86) - fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return ec + ec := cli.Exit("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return ec + }, + Metadata: map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + }, } if os.Getenv("HEXY") != "" { @@ -1275,12 +1285,6 @@ func main() { app.ErrWriter = &hexWriter{} } - app.Metadata = map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - } - app.Run(os.Args) } diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index d5082af..2bcea40 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -15,7 +15,7 @@ import ( ) func TestCommandYamlFileTest(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") @@ -45,7 +45,7 @@ func TestCommandYamlFileTest(t *testing.T) { } func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") @@ -79,7 +79,7 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { } func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) @@ -114,7 +114,7 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { } func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") @@ -146,7 +146,7 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { } func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) @@ -179,7 +179,7 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { } func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") @@ -211,7 +211,7 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { } func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) @@ -244,7 +244,7 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { } func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") @@ -278,7 +278,7 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T } func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) { - app := cli.NewApp() + app := &cli.App{} set := flag.NewFlagSet("test", 0) ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) diff --git a/app.go b/app.go index b16f991..d2825d5 100644 --- a/app.go +++ b/app.go @@ -11,8 +11,7 @@ import ( "time" ) -// App is the main structure of a cli application. It is recommended that -// an app be created with the cli.NewApp() function +// App is the main structure of a cli application. type App struct { // The name of the program. Defaults to path.Base(os.Args[0]) Name string @@ -78,22 +77,6 @@ func compileTime() time.Time { return info.ModTime() } -// NewApp creates a new cli Application with some reasonable defaults for Name, -// Usage, Version and Action. -func NewApp() *App { - return &App{ - Name: filepath.Base(os.Args[0]), - HelpName: filepath.Base(os.Args[0]), - Usage: "A new cli application", - UsageText: "", - Version: "0.0.0", - BashComplete: DefaultAppComplete, - Action: helpCommand.Action, - Compiled: compileTime(), - Writer: os.Stdout, - } -} - // Setup runs initialization code to ensure all data structures are ready for // `Run` or inspection prior to `Run`. It is internally called by `Run`, but // will return early if setup has already happened. @@ -104,6 +87,38 @@ func (a *App) Setup() { a.didSetup = true + if a.Name == "" { + a.Name = filepath.Base(os.Args[0]) + } + + if a.HelpName == "" { + a.HelpName = filepath.Base(os.Args[0]) + } + + if a.Usage == "" { + a.Usage = "A new cli application" + } + + if a.Version == "" { + a.Version = "0.0.0" + } + + if a.BashComplete == nil { + a.BashComplete = DefaultAppComplete + } + + if a.Action == nil { + a.Action = helpCommand.Action + } + + if a.Compiled == (time.Time{}) { + a.Compiled = compileTime() + } + + if a.Writer == nil { + a.Writer = os.Stdout + } + newCmds := []*Command{} for _, c := range a.Commands { if c.HelpName == "" { diff --git a/app_test.go b/app_test.go index 99bd6a3..0efb2e6 100644 --- a/app_test.go +++ b/app_test.go @@ -21,17 +21,19 @@ func ExampleApp_Run() { // set args for examples sake os.Args = []string{"greet", "--name", "Jeremy"} - app := NewApp() - app.Name = "greet" - app.Flags = []Flag{ - &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + app := &App{ + Name: "greet", + Flags: []Flag{ + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + }, + Action: func(c *Context) error { + fmt.Printf("Hello %v\n", c.String("name")) + return nil + }, + UsageText: "app [first_arg] [second_arg]", + Authors: []*Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}}, } - app.Action = func(c *Context) error { - fmt.Printf("Hello %v\n", c.String("name")) - return nil - } - app.UsageText = "app [first_arg] [second_arg]" - app.Authors = []*Author{{Name: "Oliver Allen", Email: "oliver@toyshop.example.com"}} + app.Run(os.Args) // Output: // Hello Jeremy @@ -40,30 +42,31 @@ func ExampleApp_Run() { func ExampleApp_Run_subcommand() { // set args for examples sake os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} - app := NewApp() - app.Name = "say" - app.Commands = []*Command{ - { - Name: "hello", - Aliases: []string{"hi"}, - Usage: "use it to see a description", - Description: "This is how we describe hello the function", - Subcommands: []*Command{ - { - Name: "english", - Aliases: []string{"en"}, - Usage: "sends a greeting in english", - Description: "greets someone in english", - Flags: []Flag{ - &StringFlag{ - Name: "name", - Value: "Bob", - Usage: "Name of the person to greet", + app := &App{ + Name: "say", + Commands: []*Command{ + { + Name: "hello", + Aliases: []string{"hi"}, + Usage: "use it to see a description", + Description: "This is how we describe hello the function", + Subcommands: []*Command{ + { + Name: "english", + Aliases: []string{"en"}, + Usage: "sends a greeting in english", + Description: "greets someone in english", + Flags: []Flag{ + &StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, + }, + Action: func(c *Context) error { + fmt.Println("Hello,", c.String("name")) + return nil }, - }, - Action: func(c *Context) error { - fmt.Println("Hello,", c.String("name")) - return nil }, }, }, @@ -79,20 +82,21 @@ func ExampleApp_Run_help() { // set args for examples sake os.Args = []string{"greet", "h", "describeit"} - app := NewApp() - app.Name = "greet" - app.Flags = []Flag{ - &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, - } - app.Commands = []*Command{ - { - Name: "describeit", - Aliases: []string{"d"}, - Usage: "use it to see a description", - Description: "This is how we describe describeit the function", - Action: func(c *Context) error { - fmt.Printf("i like to describe things") - return nil + app := &App{ + Name: "greet", + Flags: []Flag{ + &StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + }, + Commands: []*Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, }, }, } @@ -112,26 +116,27 @@ func ExampleApp_Run_bashComplete() { // set args for examples sake os.Args = []string{"greet", "--generate-bash-completion"} - app := NewApp() - app.Name = "greet" - app.EnableBashCompletion = true - app.Commands = []*Command{ - { - Name: "describeit", - Aliases: []string{"d"}, - Usage: "use it to see a description", - Description: "This is how we describe describeit the function", - Action: func(c *Context) error { - fmt.Printf("i like to describe things") - return nil - }, - }, { - Name: "next", - Usage: "next example", - Description: "more stuff to see when generating bash completion", - Action: func(c *Context) error { - fmt.Printf("the next example") - return nil + app := &App{ + Name: "greet", + EnableBashCompletion: true, + Commands: []*Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, + }, { + Name: "next", + Usage: "next example", + Description: "more stuff to see when generating bash completion", + Action: func(c *Context) error { + fmt.Printf("the next example") + return nil + }, }, }, } @@ -148,10 +153,11 @@ func ExampleApp_Run_bashComplete() { func TestApp_Run(t *testing.T) { s := "" - app := NewApp() - app.Action = func(c *Context) error { - s = s + c.Args().First() - return nil + app := &App{ + Action: func(c *Context) error { + s = s + c.Args().First() + return nil + }, } err := app.Run([]string{"command", "foo"}) @@ -174,7 +180,7 @@ var commandAppTests = []struct { } func TestApp_Command(t *testing.T) { - app := NewApp() + app := &App{} fooCommand := &Command{Name: "foobar", Aliases: []string{"f"}} batCommand := &Command{Name: "batbaz", Aliases: []string{"b"}} app.Commands = []*Command{ @@ -190,22 +196,23 @@ func TestApp_Command(t *testing.T) { func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context - a := NewApp() - a.Commands = []*Command{ - { - Name: "foo", - Action: func(c *Context) error { - context = c - return nil - }, - Flags: []Flag{ - &StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", + a := &App{ + Commands: []*Command{ + { + Name: "foo", + Action: func(c *Context) error { + context = c + return nil }, + Flags: []Flag{ + &StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Before: func(_ *Context) error { return nil }, }, - Before: func(_ *Context) error { return nil }, }, } a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) @@ -218,7 +225,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string var args Args - app := NewApp() + app := &App{} command := &Command{ Name: "cmd", Flags: []Flag{ @@ -243,7 +250,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { func TestApp_CommandWithDash(t *testing.T) { var args Args - app := NewApp() + app := &App{} command := &Command{ Name: "cmd", Action: func(c *Context) error { @@ -262,7 +269,7 @@ func TestApp_CommandWithDash(t *testing.T) { func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { var args Args - app := NewApp() + app := &App{} command := &Command{ Name: "cmd", Action: func(c *Context) error { @@ -280,18 +287,19 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { } func TestApp_VisibleCommands(t *testing.T) { - app := NewApp() - app.Commands = []*Command{ - { - Name: "frob", - HelpName: "foo frob", - Action: func(_ *Context) error { return nil }, - }, - { - Name: "frib", - HelpName: "foo frib", - Hidden: true, - Action: func(_ *Context) error { return nil }, + app := &App{ + Commands: []*Command{ + { + Name: "frob", + HelpName: "foo frob", + Action: func(_ *Context) error { return nil }, + }, + { + Name: "frib", + HelpName: "foo frib", + Hidden: true, + Action: func(_ *Context) error { return nil }, + }, }, } @@ -332,13 +340,14 @@ func TestApp_VisibleCommands(t *testing.T) { func TestApp_Float64Flag(t *testing.T) { var meters float64 - app := NewApp() - app.Flags = []Flag{ - &Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, - } - app.Action = func(c *Context) error { - meters = c.Float64("height") - return nil + app := &App{ + Flags: []Flag{ + &Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, + }, + Action: func(c *Context) error { + meters = c.Float64("height") + return nil + }, } app.Run([]string{"", "--height", "1.93"}) @@ -350,7 +359,7 @@ func TestApp_ParseSliceFlags(t *testing.T) { var parsedIntSlice []int var parsedStringSlice []string - app := NewApp() + app := &App{} command := &Command{ Name: "cmd", Flags: []Flag{ @@ -408,7 +417,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { var parsedIntSlice []int var parsedStringSlice []string - app := NewApp() + app := &App{} command := &Command{ Name: "cmd", Flags: []Flag{ @@ -438,7 +447,8 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { } func TestApp_DefaultStdout(t *testing.T) { - app := NewApp() + app := &App{} + app.Setup() if app.Writer != os.Stdout { t.Error("Default output writer not set.") @@ -466,9 +476,10 @@ func (fw *mockWriter) GetWritten() (b []byte) { func TestApp_SetStdout(t *testing.T) { w := &mockWriter{} - app := NewApp() - app.Name = "test" - app.Writer = w + app := &App{ + Name: "test", + Writer: w, + } err := app.Run([]string{"help"}) @@ -486,32 +497,30 @@ func TestApp_BeforeFunc(t *testing.T) { beforeError := fmt.Errorf("fail") var err error - app := NewApp() + app := &App{ + Before: func(c *Context) error { + counts.Total++ + counts.Before = counts.Total + s := c.String("opt") + if s == "fail" { + return beforeError + } - app.Before = func(c *Context) error { - counts.Total++ - counts.Before = counts.Total - s := c.String("opt") - if s == "fail" { - return beforeError - } - - return nil - } - - app.Commands = []*Command{ - { - Name: "sub", - Action: func(c *Context) error { - counts.Total++ - counts.SubCommand = counts.Total - return nil + return nil + }, + Commands: []*Command{ + { + Name: "sub", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, }, }, - } - - app.Flags = []Flag{ - &StringFlag{Name: "opt"}, + Flags: []Flag{ + &StringFlag{Name: "opt"}, + }, } // run with the Before() func succeeding @@ -578,32 +587,30 @@ func TestApp_AfterFunc(t *testing.T) { afterError := fmt.Errorf("fail") var err error - app := NewApp() + app := &App{ + After: func(c *Context) error { + counts.Total++ + counts.After = counts.Total + s := c.String("opt") + if s == "fail" { + return afterError + } - app.After = func(c *Context) error { - counts.Total++ - counts.After = counts.Total - s := c.String("opt") - if s == "fail" { - return afterError - } - - return nil - } - - app.Commands = []*Command{ - { - Name: "sub", - Action: func(c *Context) error { - counts.Total++ - counts.SubCommand = counts.Total - return nil + return nil + }, + Commands: []*Command{ + { + Name: "sub", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, }, }, - } - - app.Flags = []Flag{ - &StringFlag{Name: "opt"}, + Flags: []Flag{ + &StringFlag{Name: "opt"}, + }, } // run with the After() func succeeding @@ -649,8 +656,7 @@ func TestAppNoHelpFlag(t *testing.T) { HelpFlag = nil - app := NewApp() - app.Writer = ioutil.Discard + app := &App{Writer: ioutil.Discard} err := app.Run([]string{"test", "-h"}) if err != flag.ErrHelp { @@ -669,7 +675,7 @@ func TestAppHelpPrinter(t *testing.T) { wasCalled = true } - app := NewApp() + app := &App{} app.Run([]string{"-h"}) if wasCalled == false { @@ -688,7 +694,7 @@ func TestApp_VersionPrinter(t *testing.T) { wasCalled = true } - app := NewApp() + app := &App{} ctx := NewContext(app, nil, nil) ShowVersion(ctx) @@ -699,20 +705,19 @@ func TestApp_VersionPrinter(t *testing.T) { func TestApp_CommandNotFound(t *testing.T) { counts := &opCounts{} - app := NewApp() - - app.CommandNotFound = func(c *Context, command string) { - counts.Total++ - counts.CommandNotFound = counts.Total - } - - app.Commands = []*Command{ - { - Name: "bar", - Action: func(c *Context) error { - counts.Total++ - counts.SubCommand = counts.Total - return nil + app := &App{ + CommandNotFound: func(c *Context, command string) { + counts.Total++ + counts.CommandNotFound = counts.Total + }, + Commands: []*Command{ + { + Name: "bar", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, }, }, } @@ -729,17 +734,17 @@ func TestApp_OrderOfOperations(t *testing.T) { resetCounts := func() { counts = &opCounts{} } - app := NewApp() - app.EnableBashCompletion = true - app.BashComplete = func(c *Context) { - counts.Total++ - counts.BashComplete = counts.Total - } - - app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { - counts.Total++ - counts.OnUsageError = counts.Total - return errors.New("hay OnUsageError") + app := &App{ + EnableBashCompletion: true, + BashComplete: func(c *Context) { + counts.Total++ + counts.BashComplete = counts.Total + }, + OnUsageError: func(c *Context, err error, isSubcommand bool) error { + counts.Total++ + counts.OnUsageError = counts.Total + return errors.New("hay OnUsageError") + }, } beforeNoError := func(c *Context) error { @@ -875,7 +880,7 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { for _, flagSet := range subcommandHelpTopics { t.Logf("==> checking with flags %v", flagSet) - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf @@ -920,7 +925,7 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } func TestApp_Run_SubcommandFullPath(t *testing.T) { - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" @@ -953,7 +958,7 @@ func TestApp_Run_SubcommandFullPath(t *testing.T) { } func TestApp_Run_SubcommandHelpName(t *testing.T) { - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" @@ -988,7 +993,7 @@ func TestApp_Run_SubcommandHelpName(t *testing.T) { } func TestApp_Run_CommandHelpName(t *testing.T) { - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf app.Name = "command" @@ -1023,7 +1028,7 @@ func TestApp_Run_CommandHelpName(t *testing.T) { } func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { - app := NewApp() + app := &App{} buf := new(bytes.Buffer) app.Writer = buf app.Name = "base" @@ -1065,13 +1070,14 @@ func TestApp_Run_Help(t *testing.T) { t.Logf("==> checking with arguments %v", args) - app := NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Writer = buf - app.Action = func(c *Context) error { - buf.WriteString("boom I say!") - return nil + app := &App{ + Name: "boom", + Usage: "make an explosive entrance", + Writer: buf, + Action: func(c *Context) error { + buf.WriteString("boom I say!") + return nil + }, } err := app.Run(args) @@ -1096,14 +1102,15 @@ func TestApp_Run_Version(t *testing.T) { t.Logf("==> checking with arguments %v", args) - app := NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Version = "0.1.0" - app.Writer = buf - app.Action = func(c *Context) error { - buf.WriteString("boom I say!") - return nil + app := &App{ + Name: "boom", + Usage: "make an explosive entrance", + Version: "0.1.0", + Writer: buf, + Action: func(c *Context) error { + buf.WriteString("boom I say!") + return nil + }, } err := app.Run(args) @@ -1121,24 +1128,26 @@ func TestApp_Run_Version(t *testing.T) { } func TestApp_Run_Categories(t *testing.T) { - app := NewApp() - app.Name = "categories" - app.Commands = []*Command{ - { - Name: "command1", - Category: "1", - }, - { - Name: "command2", - Category: "1", - }, - { - Name: "command3", - Category: "2", - }, - } buf := new(bytes.Buffer) - app.Writer = buf + + app := &App{ + Name: "categories", + Commands: []*Command{ + { + Name: "command1", + Category: "1", + }, + { + Name: "command2", + Category: "1", + }, + { + Name: "command3", + Category: "2", + }, + }, + Writer: buf, + } app.Run([]string{"categories"}) @@ -1171,24 +1180,25 @@ func TestApp_Run_Categories(t *testing.T) { } func TestApp_VisibleCategories(t *testing.T) { - app := NewApp() - app.Name = "visible-categories" - app.Commands = []*Command{ - { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - }, - { - Name: "command2", - Category: "2", - HelpName: "foo command2", - }, - { - Name: "command3", - Category: "3", - HelpName: "foo command3", + app := &App{ + Name: "visible-categories", + Commands: []*Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + }, }, } @@ -1210,25 +1220,26 @@ func TestApp_VisibleCategories(t *testing.T) { app.Setup() expect(t, expected, app.VisibleCategories()) - app = NewApp() - app.Name = "visible-categories" - app.Commands = []*Command{ - { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - }, - { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, - }, - { - Name: "command3", - Category: "3", - HelpName: "foo command3", + app = &App{ + Name: "visible-categories", + Commands: []*Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + }, }, } @@ -1244,26 +1255,27 @@ func TestApp_VisibleCategories(t *testing.T) { app.Setup() expect(t, expected, app.VisibleCategories()) - app = NewApp() - app.Name = "visible-categories" - app.Commands = []*Command{ - { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - }, - { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, - }, - { - Name: "command3", - Category: "3", - HelpName: "foo command3", - Hidden: true, + app = &App{ + Name: "visible-categories", + Commands: []*Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, + }, }, } @@ -1272,10 +1284,11 @@ func TestApp_VisibleCategories(t *testing.T) { } func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { - app := NewApp() - app.Action = func(c *Context) error { return nil } - app.Before = func(c *Context) error { return fmt.Errorf("before error") } - app.After = func(c *Context) error { return fmt.Errorf("after error") } + app := &App{ + Action: func(c *Context) error { return nil }, + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, + } err := app.Run([]string{"foo"}) if err == nil { @@ -1291,17 +1304,18 @@ func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { } func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { - app := NewApp() - app.Commands = []*Command{ - { - Subcommands: []*Command{ - { - Name: "sub", + app := &App{ + Commands: []*Command{ + { + Subcommands: []*Command{ + { + Name: "sub", + }, }, + Name: "bar", + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, }, - Name: "bar", - Before: func(c *Context) error { return fmt.Errorf("before error") }, - After: func(c *Context) error { return fmt.Errorf("after error") }, }, } @@ -1319,22 +1333,23 @@ func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { } func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { - app := NewApp() - app.Flags = []Flag{ - &IntFlag{Name: "flag"}, - } - app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { - if isSubcommand { - t.Errorf("Expect no subcommand") - } - if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { - t.Errorf("Expect an invalid value error, but got \"%v\"", err) - } - return errors.New("intercepted: " + err.Error()) - } - app.Commands = []*Command{ - { - Name: "bar", + app := &App{ + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect no subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + Commands: []*Command{ + { + Name: "bar", + }, }, } @@ -1349,22 +1364,23 @@ func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { } func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { - app := NewApp() - app.Flags = []Flag{ - &IntFlag{Name: "flag"}, - } - app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { - if isSubcommand { - t.Errorf("Expect subcommand") - } - if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { - t.Errorf("Expect an invalid value error, but got \"%v\"", err) - } - return errors.New("intercepted: " + err.Error()) - } - app.Commands = []*Command{ - { - Name: "bar", + app := &App{ + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + Commands: []*Command{ + { + Name: "bar", + }, }, } diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 904d50e..48085e9 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -292,6 +292,11 @@ def _context_parent(source): 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) + + def test_migrators(): import difflib @@ -432,6 +437,13 @@ _MIGRATOR_TESTS = ( \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 """) ) diff --git a/cli.go b/cli.go index f0440c5..b2b612f 100644 --- a/cli.go +++ b/cli.go @@ -2,17 +2,18 @@ // Go applications. cli is designed to be easy to understand and write, the most simple // cli application can be written as follows: // func main() { -// cli.NewApp().Run(os.Args) +// (&cli.App{}).Run(os.Args) // } // // Of course this application does not do much, so let's make this an actual application: // func main() { -// app := cli.NewApp() -// app.Name = "greet" -// app.Usage = "say a greeting" -// app.Action = func(c *cli.Context) error { -// println("Greetings") -// } +// app := &cli.App{ +// Name: "greet", +// Usage: "say a greeting", +// Action: func(c *cli.Context) error { +// println("Greetings") +// }, +// } // // app.Run(os.Args) // } diff --git a/command.go b/command.go index f05f1e2..121deca 100644 --- a/command.go +++ b/command.go @@ -164,10 +164,11 @@ func (c *Command) HasName(name string) bool { } func (c *Command) startApp(ctx *Context) error { - app := NewApp() - app.Metadata = ctx.App.Metadata - // set the name and usage - app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + app := &App{ + Metadata: ctx.App.Metadata, + Name: fmt.Sprintf("%s %s", ctx.App.Name, c.Name), + } + if c.HelpName == "" { app.HelpName = c.HelpName } else { diff --git a/command_test.go b/command_test.go index 033149f..7c5bdfd 100644 --- a/command_test.go +++ b/command_test.go @@ -22,8 +22,7 @@ func TestCommandFlagParsing(t *testing.T) { } for _, c := range cases { - app := NewApp() - app.Writer = ioutil.Discard + app := &App{Writer: ioutil.Discard} set := flag.NewFlagSet("test", 0) set.Parse(c.testArgs) @@ -47,15 +46,16 @@ func TestCommandFlagParsing(t *testing.T) { } func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { - app := NewApp() - app.Commands = []*Command{ - { - Name: "bar", - Before: func(c *Context) error { - return fmt.Errorf("before error") - }, - After: func(c *Context) error { - return fmt.Errorf("after error") + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Before: func(c *Context) error { + return fmt.Errorf("before error") + }, + After: func(c *Context) error { + return fmt.Errorf("after error") + }, }, }, } @@ -74,18 +74,19 @@ func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { } func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { - app := NewApp() - app.Commands = []*Command{ - { - Name: "bar", - Flags: []Flag{ - &IntFlag{Name: "flag"}, - }, - OnUsageError: func(c *Context, err error, _ bool) error { - if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { - t.Errorf("Expect an invalid value error, but got \"%v\"", err) - } - return errors.New("intercepted: " + err.Error()) + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, }, }, } diff --git a/help_test.go b/help_test.go index b81701c..b8aa132 100644 --- a/help_test.go +++ b/help_test.go @@ -9,8 +9,7 @@ import ( func Test_ShowAppHelp_NoAuthor(t *testing.T) { output := new(bytes.Buffer) - app := NewApp() - app.Writer = output + app := &App{Writer: output} c := NewContext(app, nil, nil) @@ -23,8 +22,7 @@ func Test_ShowAppHelp_NoAuthor(t *testing.T) { func Test_ShowAppHelp_NoVersion(t *testing.T) { output := new(bytes.Buffer) - app := NewApp() - app.Writer = output + app := &App{Writer: output} app.Version = "" @@ -39,8 +37,7 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) { func Test_ShowAppHelp_HideVersion(t *testing.T) { output := new(bytes.Buffer) - app := NewApp() - app.Writer = output + app := &App{Writer: output} app.HideVersion = true @@ -116,7 +113,7 @@ func Test_Version_Custom_Flags(t *testing.T) { } func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { - app := NewApp() + app := &App{} set := flag.NewFlagSet("test", 0) set.Parse([]string{"foo"}) @@ -144,7 +141,7 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { } func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { - app := NewApp() + app := &App{} set := flag.NewFlagSet("test", 0) set.Parse([]string{"foo"}) From 76d44cff068cb5f6bb574eb7c7b4f71b400ff430 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 22 Jun 2016 12:55:38 -0400 Subject: [PATCH 077/158] Ensure gfmxr installs with cli.v2 and *then* make this repo shadow cli.v2 --- .travis.yml | 2 +- appveyor.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c879139..8563b8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,10 +35,10 @@ before_script: - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi +- go get github.com/urfave/gfmxr/... - mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave - rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 - ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 -- go get github.com/urfave/gfmxr/... script: - flake8 runtests cli-v1-to-v2 diff --git a/appveyor.yml b/appveyor.yml index 90de4d5..2abe1b4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,8 +18,9 @@ install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env -- git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... +- rmdir c:\gopath\src\gopkg.in\urfave\cli.v2 /s /q +- git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get -v -t ./... - if not exist node_modules\.bin\markdown-toc npm install markdown-toc From 796679fd8ba6b7d7198d97dfa708ead4492aa3bb Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 22 Jun 2016 16:57:18 -0400 Subject: [PATCH 078/158] Clean out urfave/cli.v2.a, wrap long line --- .travis.yml | 1 + appveyor.yml | 1 + cli-v1-to-v2 | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8563b8d..08c0563 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,6 +38,7 @@ before_script: - go get github.com/urfave/gfmxr/... - mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave - rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- rm -rvf ${GOPATH%%:*}/pkg/*/gopkg.in/urfave/cli.v2.a - ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 script: diff --git a/appveyor.yml b/appveyor.yml index 2abe1b4..2a51748 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,6 +20,7 @@ install: - go env - go get github.com/urfave/gfmxr/... - rmdir c:\gopath\src\gopkg.in\urfave\cli.v2 /s /q +- rmdir c:\gopath\pkg /s /q - git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get -v -t ./... - if not exist node_modules\.bin\markdown-toc npm install markdown-toc diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 48085e9..2c83720 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -294,7 +294,9 @@ def _context_parent(source): @_migrator def _app_init(source): - return re.sub('cli\\.NewApp\\(\\)', '(&cli.App{})', source, flags=re.UNICODE) + return re.sub( + 'cli\\.NewApp\\(\\)', '(&cli.App{})', source, flags=re.UNICODE + ) def test_migrators(): From 028af4bc3503feb6307441b1d2183cde74e850a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Franc=CC=A7a=20dos=20Reis?= Date: Tue, 28 Jun 2016 19:52:25 -0700 Subject: [PATCH 079/158] adding support for Float64SliceFlag --- context.go | 17 ++++++++ flag.go | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ flag_test.go | 85 ++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) diff --git a/context.go b/context.go index 2e07469..21a6b67 100644 --- a/context.go +++ b/context.go @@ -84,6 +84,15 @@ func (c *Context) Int64Slice(name string) []int64 { return nil } +// Float64Slice looks up the value of a local float64 slice flag, returns nil if no float +// slice flag exists +func (c *Context) Float64Slice(name string) []float64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64Slice(name, fs) + } + return nil +} + // Bool looks up the value of a local bool flag, returns false if no bool flag exists func (c *Context) Bool(name string) bool { if fs := lookupFlagSet(name, c); fs != nil { @@ -317,6 +326,14 @@ func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { return nil } +func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*Float64Slice)).Value() + } + return nil +} + func lookupGeneric(name string, set *flag.FlagSet) interface{} { f := set.Lookup(name) if f != nil { diff --git a/flag.go b/flag.go index 3ff53ce..2a587da 100644 --- a/flag.go +++ b/flag.go @@ -765,6 +765,111 @@ func (f *Float64Flag) Names() []string { return flagNames(f) } + + + +// NewFloat64Slice makes a *Float64Slice with default values +func NewFloat64Slice(defaults ...float64) *Float64Slice { + return &Float64Slice{slice: append([]float64{}, defaults...)} +} + + +// Float64Slice is an opaque type for []float64 to satisfy flag.Value +type Float64Slice struct { + slice []float64 + hasBeenSet bool +} + +// Set parses the value into a float64 and appends it to the list of values +func (f *Float64Slice) Set(value string) error { + if !f.hasBeenSet { + f.slice = []float64{} + f.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) + f.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + + f.slice = append(f.slice, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Float64Slice) String() string { + return fmt.Sprintf("%#v", f.slice) +} + +// Serialized allows Float64Slice to fulfill Serializeder +func (f *Float64Slice) Serialized() string { + jsonBytes, _ := json.Marshal(f.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of float64s set by this flag +func (f *Float64Slice) Value() []float64 { + return f.slice +} + +// Float64SliceFlag is a float64 flag that can be specified multiple times on the +// command-line +type Float64SliceFlag struct { + Name string + Aliases []string + Value *Float64Slice + Usage string + EnvVars []string + Hidden bool +} + +// String returns the usage +func (f *Float64SliceFlag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { + if envVal := os.Getenv(envVar); envVal != "" { + newVal := NewFloat64Slice() + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(ErrWriter, err.Error()) + } + } + f.Value = newVal + break + } + } + } + + if f.Value == nil { + f.Value = NewFloat64Slice() + } + + for _, name := range f.Names() { + set.Var(f.Value, name, f.Usage) + } +} + +// Names returns the names of the flag. +func (f *Float64SliceFlag) Names() []string { + return flagNames(f) +} + + + func visibleFlags(fl []Flag) []Flag { visible := []Flag{} for _, flag := range fl { @@ -893,6 +998,9 @@ func stringifyFlag(f Flag) string { case *Int64SliceFlag: return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyInt64SliceFlag(f.(*Int64SliceFlag))) + case *Float64SliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyFloat64SliceFlag(f.(*Float64SliceFlag))) case *StringSliceFlag: return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(*StringSliceFlag))) @@ -949,6 +1057,17 @@ func stringifyInt64SliceFlag(f *Int64SliceFlag) string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +func stringifyFloat64SliceFlag(f *Float64SliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + func stringifyStringSliceFlag(f *StringSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { diff --git a/flag_test.go b/flag_test.go index 1b69f51..e8c8bcd 100644 --- a/flag_test.go +++ b/flag_test.go @@ -482,6 +482,49 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) { expect(t, v, float64(43.33333)) } + +var float64SliceFlagTests = []struct { + name string + aliases []string + value *Float64Slice + expected string +}{ + {"heads", nil, NewFloat64Slice(), "--heads value\t"}, + {"H", nil, NewFloat64Slice(), "-H value\t"}, + {"heads", []string{"H"}, NewFloat64Slice(float64(0.1234), float64(-10.5)), + "--heads value, -H value\t(default: 0.1234, -10.5)"}, +} + +func TestFloat64SliceFlagHelpOutput(t *testing.T) { + for _, test := range float64SliceFlagTests { + flag := Float64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_SMURF", "0.1234,-10.5") + for _, test := range float64SliceFlagTests { + flag := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := flag.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + + + var genericFlagTests = []struct { name string value Generic @@ -1057,6 +1100,48 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { a.Run([]string{"run"}) } + +func TestParseMultiFloat64SliceFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "0.1,-10.5") + + (&App{ + Flags: []Flag{ + &Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewFloat64Slice(), EnvVars: []string{"APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Float64Slice("intervals"), []float64{0.1, -10.5}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Float64Slice("i"), []float64{0.1, -10.5}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "0.1234,-10.5") + + (&App{ + Flags: []Flag{ + &Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewFloat64Slice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Float64Slice("intervals"), []float64{0.1234, -10.5}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Float64Slice("i"), []float64{0.1234, -10.5}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + + func TestParseMultiBool(t *testing.T) { a := App{ Flags: []Flag{ From 91563b8ff0153cbd2a16f2a16df167dbf3d75690 Mon Sep 17 00:00:00 2001 From: John Weldon Date: Sat, 2 Jul 2016 12:35:48 -0700 Subject: [PATCH 080/158] Add JSON InputSource to altsrc package - Implement NewJSONSource* functions for returning an InputSource from various JSON data sources. - Copy and modify YAML tests for the JSON InputSource --- README.md | 6 +- altsrc/json_command_test.go | 324 ++++++++++++++++++++++++++++++++++ altsrc/json_source_context.go | 202 +++++++++++++++++++++ 3 files changed, 529 insertions(+), 3 deletions(-) create mode 100644 altsrc/json_command_test.go create mode 100644 altsrc/json_source_context.go diff --git a/README.md b/README.md index 812268d..0b2a858 100644 --- a/README.md +++ b/README.md @@ -547,9 +547,9 @@ the yaml input source for any flags that are defined on that command. As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. -Currently only YAML files are supported but developers can add support for other -input sources by implementing the altsrc.InputSourceContext for their given -sources. +Currently only YAML and JSON files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. Here is a more complete sample of a command using YAML support: diff --git a/altsrc/json_command_test.go b/altsrc/json_command_test.go new file mode 100644 index 0000000..fcca5fc --- /dev/null +++ b/altsrc/json_command_test.go @@ -0,0 +1,324 @@ +package altsrc + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + "github.com/urfave/cli" +) + +const ( + fileName = "current.json" + simpleJSON = `{"test": 15}` + nestedJSON = `{"top": {"test": 15}}` +) + +func TestCommandJSONFileTest(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestGlobalEnvVarWins(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 10) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestGlobalEnvVarWinsNested(t *testing.T) { + cleanup := writeTempFile(t, fileName, nestedJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 10) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "top.test", EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestSpecifiedFlagWins(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName, "--test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 7) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestSpecifiedFlagWinsNested(t *testing.T) { + cleanup := writeTempFile(t, fileName, nestedJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName, "--top.test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 7) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "top.test"}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestDefaultValueFileWins(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileTestDefaultValueFileWinsNested(t *testing.T) { + cleanup := writeTempFile(t, fileName, nestedJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWins(t *testing.T) { + cleanup := writeTempFile(t, fileName, simpleJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 11) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWinsNested(t *testing.T) { + cleanup := writeTempFile(t, fileName, nestedJSON) + defer cleanup() + + app := &cli.App{} + set := flag.NewFlagSet("test", 0) + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", fileName} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 11) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(&cli.IntFlag{Name: "top.test", Value: 7, EnvVars: []string{"THE_TEST"}}), + &cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func writeTempFile(t *testing.T, name string, content string) func() { + if err := ioutil.WriteFile(name, []byte(content), 0666); err != nil { + t.Fatalf("cannot write %q: %v", name, err) + } + return func() { + if err := os.Remove(name); err != nil { + t.Errorf("cannot remove %q: %v", name, err) + } + } +} diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go new file mode 100644 index 0000000..c027a53 --- /dev/null +++ b/altsrc/json_source_context.go @@ -0,0 +1,202 @@ +package altsrc + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "strings" + "time" + + "github.com/urfave/cli" +) + +// NewJSONSourceFromFlagFunc returns a func that takes a cli.Context +// and returns an InputSourceContext suitable for retrieving config +// variables from a file containing JSON data with the file name defined +// by the given flag. +func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) { + return func(context *cli.Context) (InputSourceContext, error) { + return NewJSONSourceFromFile(context.String(flag)) + } +} + +// NewJSONSourceFromFile returns an InputSourceContext suitable for +// retrieving config variables from a file (or url) containing JSON +// data. +func NewJSONSourceFromFile(f string) (InputSourceContext, error) { + data, err := loadDataFrom(f) + if err != nil { + return nil, err + } + return NewJSONSource(data) +} + +// NewJSONSourceFromReader returns an InputSourceContext suitable for +// retrieving config variables from an io.Reader that returns JSON data. +func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + return NewJSONSource(data) +} + +// NewJSONSource returns an InputSourceContext suitable for retrieving +// config variables from raw JSON data. +func NewJSONSource(data []byte) (InputSourceContext, error) { + var deserialized map[string]interface{} + if err := json.Unmarshal(data, &deserialized); err != nil { + return nil, err + } + return &jsonSource{deserialized: deserialized}, nil +} + +func (x *jsonSource) Int(name string) (int, error) { + i, err := x.getValue(name) + if err != nil { + return 0, err + } + switch v := i.(type) { + default: + return 0, fmt.Errorf("unexpected type %T for %q", i, name) + case int: + return v, nil + case float64: + return int(float64(v)), nil + case float32: + return int(float32(v)), nil + } +} + +func (x *jsonSource) Duration(name string) (time.Duration, error) { + i, err := x.getValue(name) + if err != nil { + return 0, err + } + v, ok := (time.Duration)(0), false + if v, ok = i.(time.Duration); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) Float64(name string) (float64, error) { + i, err := x.getValue(name) + if err != nil { + return 0, err + } + v, ok := (float64)(0), false + if v, ok = i.(float64); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) String(name string) (string, error) { + i, err := x.getValue(name) + if err != nil { + return "", err + } + v, ok := "", false + if v, ok = i.(string); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) StringSlice(name string) ([]string, error) { + i, err := x.getValue(name) + if err != nil { + return nil, err + } + switch v := i.(type) { + default: + return nil, fmt.Errorf("unexpected type %T for %q", i, name) + case []string: + return v, nil + case []interface{}: + c := []string{} + for _, s := range v { + if str, ok := s.(string); ok { + c = append(c, str) + } else { + return c, fmt.Errorf("unexpected item type %T in %T for %q", s, c, name) + } + } + return c, nil + } +} + +func (x *jsonSource) IntSlice(name string) ([]int, error) { + i, err := x.getValue(name) + if err != nil { + return nil, err + } + switch v := i.(type) { + default: + return nil, fmt.Errorf("unexpected type %T for %q", i, name) + case []int: + return v, nil + case []interface{}: + c := []int{} + for _, s := range v { + if i2, ok := s.(int); ok { + c = append(c, i2) + } else { + return c, fmt.Errorf("unexpected item type %T in %T for %q", s, c, name) + } + } + return c, nil + } +} + +func (x *jsonSource) Generic(name string) (cli.Generic, error) { + i, err := x.getValue(name) + if err != nil { + return nil, err + } + v, ok := (cli.Generic)(nil), false + if v, ok = i.(cli.Generic); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) Bool(name string) (bool, error) { + i, err := x.getValue(name) + if err != nil { + return false, err + } + v, ok := false, false + if v, ok = i.(bool); !ok { + return v, fmt.Errorf("unexpected type %T for %q", i, name) + } + return v, nil +} + +func (x *jsonSource) getValue(key string) (interface{}, error) { + return jsonGetValue(key, x.deserialized) +} + +func jsonGetValue(key string, m map[string]interface{}) (interface{}, error) { + var ret interface{} + var ok bool + working := m + keys := strings.Split(key, ".") + for ix, k := range keys { + if ret, ok = working[k]; !ok { + return ret, fmt.Errorf("missing key %q", key) + } + if working, ok = ret.(map[string]interface{}); !ok { + if ix < len(keys)-1 { + return ret, fmt.Errorf("unexpected intermediate value at %q segment of %q: %T", k, key, ret) + } + } + } + return ret, nil +} + +type jsonSource struct { + deserialized map[string]interface{} +} From 3c9856d0fde1c56929cc40599c9a1ee6fac258a8 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 2 Jul 2016 21:44:48 -0700 Subject: [PATCH 081/158] Add Float64SliceFlag to the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 808a08c..0d3424d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## 2.0.0 - (unreleased 2.x series) ### Added - `NewStringSlice` and `NewIntSlice` for creating their related types +- `Float64SliceFlag` for unmarshaling a list of floats from the user - `Context.Lineage` to get all contexts from current up to global - `Context.LocalFlagNames` to get the flag names from *only* the current context - `BoolFlag.Value` to handle both default-false and default-true From fcaccb600b76d189de0a373f954567ec7babc2d9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 15 Jul 2016 08:45:46 -0400 Subject: [PATCH 082/158] Run flake8 checks on generate-flag-types --- .travis.yml | 2 +- generate-flag-types | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d12ddbb..13e791c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ before_script: - ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 script: -- flake8 runtests cli-v1-to-v2 +- flake8 runtests cli-v1-to-v2 generate-flag-types - ./runtests gen - ./runtests vet - ./runtests test diff --git a/generate-flag-types b/generate-flag-types index 28069b3..2ee0485 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -37,8 +37,8 @@ The meaning of each field is as follows: name (string) - The type "name", which will be suffixed with `Flag` when generating the type definition for `cli` and the wrapper type for `altsrc` - type (string) - The type that the generated `Flag` type for `cli` - is expected to "contain" as its `.Value` member + type (string) - The type that the generated `Flag` type for + `cli` is expected to "contain" as its `.Value` member value (bool) - Should the generated `cli` type have a `Value` member? dest (bool) - Should the generated `cli` type support a From a51cb870fbf3c34897dc080a848e60da3ee50ab0 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 15 Jul 2016 21:49:55 -0400 Subject: [PATCH 083/158] Wrap a long line for flake8 --- generate-flag-types | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generate-flag-types b/generate-flag-types index 2ee0485..4ac7c04 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -38,7 +38,8 @@ The meaning of each field is as follows: `Flag` when generating the type definition for `cli` and the wrapper type for `altsrc` type (string) - The type that the generated `Flag` type for - `cli` is expected to "contain" as its `.Value` member + `cli` is expected to "contain" as its `.Value` + member value (bool) - Should the generated `cli` type have a `Value` member? dest (bool) - Should the generated `cli` type support a From 67cf9194196e72f3a4a4bd44762bbde5f95f625b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 16 Jul 2016 12:24:16 -0400 Subject: [PATCH 084/158] Fix some trailing gfmxr refs --- .travis.yml | 1 - runtests | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 82c058b..72d5bd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,6 @@ before_script: - if [ ! -f node_modules/.bin/markdown-toc ] ; then npm install markdown-toc ; fi -- go get github.com/urfave/gfmxr/... - mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave - rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 - rm -rvf ${GOPATH%%:*}/pkg/*/gopkg.in/urfave/cli.v2.a diff --git a/runtests b/runtests index d82fd44..4af1b7d 100755 --- a/runtests +++ b/runtests @@ -83,7 +83,7 @@ def _migrations(): try: os.chdir(tmpdir) _run('curl -sSL -o README.md {}'.format(v1_readme_url).split()) - _run('gfmxr extract -o .'.split()) + _run('gfmrun extract -o .'.split()) for gofile in glob.glob('*.go'): for i in (0, 1): From ca2a0f72bfc23378d7b6fcfa06939a89b1fa9909 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 23 Jul 2016 21:53:55 -0400 Subject: [PATCH 085/158] Ensure context lookups traverse lineage Closes #487 --- context_test.go | 52 +++++++++++++++++++++++++++++------- flag_generated.go | 65 ++++++++++++++++++++++++++++++++++++--------- generate-flag-types | 5 +++- 3 files changed, 98 insertions(+), 24 deletions(-) diff --git a/context_test.go b/context_test.go index ac61dba..1083eeb 100644 --- a/context_test.go +++ b/context_test.go @@ -35,57 +35,89 @@ func TestNewContext(t *testing.T) { func TestContext_Int(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Int("top-flag", 13, "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Int("myflag"), 12) + expect(t, c.Int("top-flag"), 13) } func TestContext_Int64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int64("myflagInt64", 12, "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Int64("top-flag", 13, "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Int64("myflagInt64"), int64(12)) + expect(t, c.Int64("top-flag"), int64(13)) } func TestContext_Uint(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Uint("myflagUint", uint(13), "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Uint("top-flag", uint(14), "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Uint("myflagUint"), uint(13)) + expect(t, c.Uint("top-flag"), uint(14)) } func TestContext_Uint64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Uint64("myflagUint64", uint64(9), "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Uint64("top-flag", uint64(10), "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Uint64("myflagUint64"), uint64(9)) + expect(t, c.Uint64("top-flag"), uint64(10)) } func TestContext_Float64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Float64("myflag", float64(17), "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Float64("top-flag", float64(18), "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Float64("myflag"), float64(17)) + expect(t, c.Float64("top-flag"), float64(18)) } func TestContext_Duration(t *testing.T) { set := flag.NewFlagSet("test", 0) - set.Duration("myflag", time.Duration(12*time.Second), "doc") - c := NewContext(nil, set, nil) - expect(t, c.Duration("myflag"), time.Duration(12*time.Second)) + set.Duration("myflag", 12*time.Second, "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Duration("top-flag", 13*time.Second, "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) + expect(t, c.Duration("myflag"), 12*time.Second) + expect(t, c.Duration("top-flag"), 13*time.Second) } func TestContext_String(t *testing.T) { set := flag.NewFlagSet("test", 0) set.String("myflag", "hello world", "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.String("top-flag", "hai veld", "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.String("myflag"), "hello world") + expect(t, c.String("top-flag"), "hai veld") } func TestContext_Bool(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") - c := NewContext(nil, set, nil) + parentSet := flag.NewFlagSet("test", 0) + parentSet.Bool("top-flag", true, "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Bool("myflag"), false) + expect(t, c.Bool("top-flag"), true) } func TestContext_Args(t *testing.T) { diff --git a/flag_generated.go b/flag_generated.go index 626ec78..e224fb8 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -33,7 +33,10 @@ func (f *BoolFlag) Names() []string { // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { - return lookupBool(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupBool(name, fs) + } + return false } func lookupBool(name string, set *flag.FlagSet) bool { @@ -73,7 +76,10 @@ func (f *DurationFlag) Names() []string { // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { - return lookupDuration(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupDuration(name, fs) + } + return 0 } func lookupDuration(name string, set *flag.FlagSet) time.Duration { @@ -113,7 +119,10 @@ func (f *Float64Flag) Names() []string { // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { - return lookupFloat64(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64(name, fs) + } + return 0 } func lookupFloat64(name string, set *flag.FlagSet) float64 { @@ -152,7 +161,10 @@ func (f *GenericFlag) Names() []string { // 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 } func lookupGeneric(name string, set *flag.FlagSet) interface{} { @@ -192,7 +204,10 @@ func (f *Int64Flag) Names() []string { // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { - return lookupInt64(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt64(name, fs) + } + return 0 } func lookupInt64(name string, set *flag.FlagSet) int64 { @@ -232,7 +247,10 @@ func (f *IntFlag) Names() []string { // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { - return lookupInt(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt(name, fs) + } + return 0 } func lookupInt(name string, set *flag.FlagSet) int { @@ -271,7 +289,10 @@ func (f *IntSliceFlag) Names() []string { // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { - return lookupIntSlice(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupIntSlice(name, fs) + } + return nil } func lookupIntSlice(name string, set *flag.FlagSet) []int { @@ -310,7 +331,10 @@ func (f *Int64SliceFlag) Names() []string { // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { - return lookupInt64Slice(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt64Slice(name, fs) + } + return nil } func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { @@ -349,7 +373,10 @@ func (f *Float64SliceFlag) Names() []string { // Float64Slice looks up the value of a local Float64SliceFlag, returns // nil if not found func (c *Context) Float64Slice(name string) []float64 { - return lookupFloat64Slice(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64Slice(name, fs) + } + return nil } func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 { @@ -389,7 +416,10 @@ func (f *StringFlag) Names() []string { // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { - return lookupString(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupString(name, fs) + } + return "" } func lookupString(name string, set *flag.FlagSet) string { @@ -428,7 +458,10 @@ func (f *StringSliceFlag) Names() []string { // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { - return lookupStringSlice(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupStringSlice(name, fs) + } + return nil } func lookupStringSlice(name string, set *flag.FlagSet) []string { @@ -468,7 +501,10 @@ func (f *Uint64Flag) Names() []string { // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { - return lookupUint64(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupUint64(name, fs) + } + return 0 } func lookupUint64(name string, set *flag.FlagSet) uint64 { @@ -508,7 +544,10 @@ func (f *UintFlag) Names() []string { // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { - return lookupUint(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupUint(name, fs) + } + return 0 } func lookupUint(name string, set *flag.FlagSet) uint { diff --git a/generate-flag-types b/generate-flag-types index 4ac7c04..6244ff9 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -169,7 +169,10 @@ def _write_cli_flag_types(outfile, types): // {name} looks up the value of a local {name}Flag, returns // {context_default} if not found func (c *Context) {name}(name string) {context_type} {{ - return lookup{name}(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil {{ + return lookup{name}(name, fs) + }} + return {context_default} }} func lookup{name}(name string, set *flag.FlagSet) {context_type} {{ From 7640bef0eb856d050706a55148bedd7529b8b5b6 Mon Sep 17 00:00:00 2001 From: Antoine Eiche Date: Thu, 21 Jul 2016 21:01:59 +0200 Subject: [PATCH 086/158] Add --init-completion flag to print completion code This flag takes as input 'bash' or 'zsh' and generates the completion code for the specified shell. --- app.go | 6 ++++++ flag.go | 6 ++++++ help.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/app.go b/app.go index 290d3bb..6087d9e 100644 --- a/app.go +++ b/app.go @@ -138,6 +138,7 @@ func (a *App) Setup() { if a.EnableBashCompletion { a.appendFlag(BashCompletionFlag) + a.appendFlag(InitCompletionFlag) } if !a.HideVersion { @@ -176,6 +177,11 @@ func (a *App) Run(arguments []string) (err error) { return nil } + var done bool + if done, err = checkInitCompletion(context); done { + return nil + } + if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) diff --git a/flag.go b/flag.go index 807c95f..ee7629e 100644 --- a/flag.go +++ b/flag.go @@ -27,6 +27,12 @@ var BashCompletionFlag = &BoolFlag{ Hidden: true, } +// InitCompletionFlag generates completion code +var InitCompletionFlag = &StringFlag{ + Name: "init-completion", + Usage: "generate completion code. Value must be 'bash' or 'zsh'", +} + // VersionFlag prints the version for the application var VersionFlag = &BoolFlag{ Name: "version", diff --git a/help.go b/help.go index 1dca46f..9a4c334 100644 --- a/help.go +++ b/help.go @@ -286,3 +286,41 @@ func checkCommandCompletions(c *Context, name string) bool { return false } + +func checkInitCompletion(c *Context) (bool, error) { + if c.IsSet(InitCompletionFlag.Name) { + shell := c.String(InitCompletionFlag.Name) + progName := os.Args[0] + switch shell { + case "bash": + fmt.Print(bashCompletionCode(progName)) + return true, nil + case "zsh": + fmt.Print(zshCompletionCode(progName)) + return true, nil + default: + return false, fmt.Errorf("--init-completion value cannot be '%s'", shell) + } + } + return false, nil +} + +func bashCompletionCode(progName string) string { + var template = `_cli_bash_autocomplete() { + local cur opts base; + COMPREPLY=(); + cur="${COMP_WORDS[COMP_CWORD]}"; + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ); + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ); + return 0; +}; +complete -F _cli_bash_autocomplete %s` + return fmt.Sprintf(template, progName) +} + +func zshCompletionCode(progName string) string { + var template = `autoload -U compinit && compinit; +autoload -U bashcompinit && bashcompinit;` + + return template + "\n" + bashCompletionCode(progName) +} From 34a8b004d2b73ac94b7ed2ac2a9b1d5d8d93675b Mon Sep 17 00:00:00 2001 From: Antoine Eiche Date: Thu, 21 Jul 2016 21:11:47 +0200 Subject: [PATCH 087/158] Documentation regarding the init-completion flag --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1b29c86..a564c22 100644 --- a/README.md +++ b/README.md @@ -794,10 +794,18 @@ func main() { #### Enabling -Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while -setting the `PROG` variable to the name of your program: +You can generate bash or zsh completion code by using the flag `--init-completion bash` or `--init-completion bash`. -`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` +To setup for bash: + +``` +eval "`myprogram --init-completion bash`" +``` + +Alternatively, you can put the completion code in your `.bashrc` file: +``` +myprogram --init-completion bash >> ~/.bashrc +``` #### Distribution From 94bc26fd1cd5684da524420fa3db3c4e68aa193a Mon Sep 17 00:00:00 2001 From: Antoine Eiche Date: Fri, 22 Jul 2016 10:19:29 +0200 Subject: [PATCH 088/158] Refactoring names from bash to shell The refactoring is required since completion is also supported for zsh shell. Refactoring details: - flag generate-bash-completion -> generate-completion - var EnableBashCompletion -> EnableShellCompletion - var BashComplete -> ShellComplete - var BashCompletionFlag -> GenerateCompletionFlag - type BashCompleteFunc -> ShellCompleteFunc --- README.md | 30 +++++++++++++++--------------- app.go | 29 ++++++++++++++++------------- app_test.go | 20 ++++++++++---------- command.go | 14 +++++++------- flag.go | 6 +++--- funcs.go | 4 ++-- help.go | 14 +++++++------- 7 files changed, 60 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index a564c22..e63b9f5 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ applications in an expressive way. * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) * [Exit code](#exit-code) - * [Bash Completion](#bash-completion) + * [Shell Completion](#shell-completion) + [Enabling](#enabling) + [Distribution](#distribution) + [Customization](#customization) @@ -740,15 +740,15 @@ func main() { } ``` -### Bash Completion +### Shell Completion -You can enable completion commands by setting the `EnableBashCompletion` +You can enable completion commands by setting the `EnableShellCompletion` flag on the `App` object. By default, this setting will only auto-complete to show an app's subcommands, but you can write your own completion methods for the App or its subcommands. ``` go @@ -765,7 +765,7 @@ func main() { tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} app := &cli.App{ - EnableBashCompletion: true, + EnableShellCompletion: true, Commands: []*cli.Command{ { Name: "complete", @@ -775,7 +775,7 @@ func main() { fmt.Println("completed task: ", c.Args().First()) return nil }, - BashComplete: func(c *cli.Context) { + ShellComplete: func(c *cli.Context) { // This will complete if no args are passed if c.NArg() > 0 { return @@ -794,7 +794,7 @@ func main() { #### Enabling -You can generate bash or zsh completion code by using the flag `--init-completion bash` or `--init-completion bash`. +You can generate bash or zsh completion code by using the flag `--init-completion bash` or `--init-completion zsh`. To setup for bash: @@ -825,8 +825,8 @@ to the name of their program (as above). #### Customization -The default bash completion flag (`--generate-bash-completion`) is defined as -`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: +The default shell completion flag (`--generate-completion`) is defined as +`cli.GenerateCompletionFlag`, and may be redefined if desired, e.g.: ``` go package main @@ -996,7 +996,7 @@ Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e. ``` go package main From 812fa64d8e8a5e70a2d23ccd7293ac196ec42f43 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 22 Aug 2016 17:54:51 -0400 Subject: [PATCH 091/158] Skip migrations on go < 1.3 because there won't be a `gfmrun` around to extract examples --- runtests | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runtests b/runtests index 8a2d3cf..bcd8f4c 100755 --- a/runtests +++ b/runtests @@ -75,6 +75,11 @@ def _vet(): @_target def _migrations(): + go_version = check_output('go version'.split()).split()[2] + if go_version < 'go1.3': + print('runtests: skip on {}'.format(go_version), file=sys.stderr) + return + migration_script = os.path.abspath( os.environ.get('V1TOV2', './cli-v1-to-v2') ) From 3a3228c0e40e35d7ebe5ce1e226bc5541552c8d0 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Wed, 24 Aug 2016 14:57:01 -0400 Subject: [PATCH 092/158] Abstract dependency and test execution with Makefile Abstract the `runtests` script with a makefile, and update travis tests to use makefile abstraction. --- .travis.yml | 13 +------------ GNUmakefile | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 GNUmakefile diff --git a/.travis.yml b/.travis.yml index 5a45f06..41697ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,11 +26,6 @@ matrix: before_script: - $pip_install flake8 -- go get github.com/urfave/gfmrun/... || true -- go get golang.org/x/tools/... || true -- if [ ! -f node_modules/.bin/markdown-toc ] ; then - npm install markdown-toc ; - fi - mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave - rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 - rm -rvf ${GOPATH%%:*}/pkg/*/gopkg.in/urfave/cli.v2.a @@ -38,10 +33,4 @@ before_script: script: - flake8 runtests cli-v1-to-v2 generate-flag-types -- ./runtests gen -- ./runtests vet -- ./runtests test -- ./runtests gfmrun -- ./cli-v1-to-v2 --selftest -- ./runtests migrations -- ./runtests toc +- make all diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..4543b19 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,37 @@ +default: test + +deps: + go get golang.org/x/tools/cmd/goimports || true + go get github.com/urfave/gfmrun/... || true + go list ./... \ + | xargs go list -f '{{ join .Deps "\n" }}{{ printf "\n" }}{{ join .TestImports "\n" }}' \ + | grep -v github.com/urfave/cli \ + | xargs go get + @if [ ! -f node_modules/.bin/markdown-toc ]; then \ + npm install markdown-toc ; \ + fi + +gen: deps + ./runtests gen + +vet: + ./runtests vet + +gfmrun: + ./runtests gfmrun + +v1-to-v2: + ./cli-v1-to-v2 --selftest + +migrations: + ./runtests migrations + +toc: + ./runtests toc + +test: deps + ./runtests test + +all: gen vet test gfmrun v1-to-v2 migrations toc + +.PHONY: default gen vet test gfmrun migrations toc v1-to-v2 deps all From b93207160fd4263fd12b9ee6d7f0454fde74e246 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Sat, 27 Aug 2016 19:14:15 -0400 Subject: [PATCH 093/158] Update altsrc imports to gopkg.in Updates altsrc imports to use `gopkg.in/urfave/cli.v2` Fixes: #505 --- altsrc/flag.go | 2 +- altsrc/flag_generated.go | 2 +- altsrc/flag_test.go | 2 +- altsrc/input_source_context.go | 2 +- altsrc/json_command_test.go | 2 +- altsrc/json_source_context.go | 2 +- altsrc/map_input_source.go | 2 +- altsrc/toml_command_test.go | 2 +- altsrc/toml_file_loader.go | 2 +- altsrc/yaml_command_test.go | 2 +- altsrc/yaml_file_loader.go | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index c378ac4..879708c 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -5,7 +5,7 @@ import ( "os" "strconv" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) // FlagInputSourceExtension is an extension interface of cli.Flag that diff --git a/altsrc/flag_generated.go b/altsrc/flag_generated.go index 4edb0a7..7ca8dac 100644 --- a/altsrc/flag_generated.go +++ b/altsrc/flag_generated.go @@ -3,7 +3,7 @@ package altsrc import ( "flag" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) // WARNING: This file is generated! diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index e5b4c05..f077487 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) type testApplyInputSource struct { diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 56603cf..c45ba5c 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -3,7 +3,7 @@ package altsrc import ( "time" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) // InputSourceContext is an interface used to allow diff --git a/altsrc/json_command_test.go b/altsrc/json_command_test.go index fcca5fc..ccb5041 100644 --- a/altsrc/json_command_test.go +++ b/altsrc/json_command_test.go @@ -6,7 +6,7 @@ import ( "os" "testing" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) const ( diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index c027a53..a197d87 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) // NewJSONSourceFromFlagFunc returns a func that takes a cli.Context diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 68a749c..26d6e81 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) // MapInputSource implements InputSourceContext to return diff --git a/altsrc/toml_command_test.go b/altsrc/toml_command_test.go index 139aa37..2f28d22 100644 --- a/altsrc/toml_command_test.go +++ b/altsrc/toml_command_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) func TestCommandTomFileTest(t *testing.T) { diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index bc2c11d..0fa2dbb 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -10,7 +10,7 @@ import ( "reflect" "github.com/BurntSushi/toml" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) type tomlMap struct { diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 2bcea40..5290e84 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -11,7 +11,7 @@ import ( "os" "testing" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" ) func TestCommandYamlFileTest(t *testing.T) { diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index b4e3365..ada90a8 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -12,7 +12,7 @@ import ( "net/url" "os" - "github.com/urfave/cli" + "gopkg.in/urfave/cli.v2" "gopkg.in/yaml.v2" ) From 2ab83fab2a96a383ee71cb04c2a9cb3c6d033a09 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Sun, 28 Aug 2016 01:36:49 -0400 Subject: [PATCH 094/158] Force generation of flag types to use gopkg import --- generate-flag-types | 2 ++ 1 file changed, 2 insertions(+) diff --git a/generate-flag-types b/generate-flag-types index 6244ff9..aa51154 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -193,6 +193,8 @@ def _write_altsrc_flag_types(outfile, types): _fwrite(outfile, """\ package altsrc + import "gopkg.in/urfave/cli.v2" + // WARNING: This file is generated! """) From e0556cf9e8c79d43c2ba13679be64b2cb7b2d6c7 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Sun, 28 Aug 2016 03:14:39 -0400 Subject: [PATCH 095/158] Add DefaultValue text for flags Allows a user to override the default value of a flag in the displayed help output. Ex: ``` cli.IntFlag{ Name: "foo, f", DefaultText: "random foo", Value: "bar", } ``` Running `(app.name) -h` will now yield: ``` --foo value (default: "random foo") ``` Fixes: #504 --- flag.go | 7 ++++- flag_generated.go | 73 ++++++++++++++++++++++++++------------------- flag_test.go | 15 ++++++---- generate-flag-types | 1 + 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/flag.go b/flag.go index 807c95f..29010f2 100644 --- a/flag.go +++ b/flag.go @@ -730,7 +730,6 @@ func stringifyFlag(f Flag) string { needsPlaceholder := false defaultValueString := "" val := fv.FieldByName("Value") - if val.IsValid() { needsPlaceholder = val.Kind() != reflect.Bool defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) @@ -740,6 +739,12 @@ func stringifyFlag(f Flag) string { } } + helpText := fv.FieldByName("DefaultText") + if helpText.IsValid() && helpText.String() != "" { + needsPlaceholder = val.Kind() != reflect.Bool + defaultValueString = fmt.Sprintf(" (default: %q)", helpText.String()) + } + if defaultValueString == " (default: )" { defaultValueString = "" } diff --git a/flag_generated.go b/flag_generated.go index e224fb8..1f48d9f 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -16,6 +16,7 @@ type BoolFlag struct { EnvVars []string Hidden bool Value bool + DefaultText string Destination *bool } @@ -59,6 +60,7 @@ type DurationFlag struct { EnvVars []string Hidden bool Value time.Duration + DefaultText string Destination *time.Duration } @@ -102,6 +104,7 @@ type Float64Flag struct { EnvVars []string Hidden bool Value float64 + DefaultText string Destination *float64 } @@ -139,12 +142,13 @@ func lookupFloat64(name string, set *flag.FlagSet) float64 { // GenericFlag is a flag with type Generic type GenericFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value Generic + Name string + Aliases []string + Usage string + EnvVars []string + Hidden bool + Value Generic + DefaultText string } // String returns a readable representation of this value @@ -187,6 +191,7 @@ type Int64Flag struct { EnvVars []string Hidden bool Value int64 + DefaultText string Destination *int64 } @@ -230,6 +235,7 @@ type IntFlag struct { EnvVars []string Hidden bool Value int + DefaultText string Destination *int } @@ -267,12 +273,13 @@ func lookupInt(name string, set *flag.FlagSet) int { // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value *IntSlice + Name string + Aliases []string + Usage string + EnvVars []string + Hidden bool + Value *IntSlice + DefaultText string } // String returns a readable representation of this value @@ -309,12 +316,13 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int { // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value *Int64Slice + Name string + Aliases []string + Usage string + EnvVars []string + Hidden bool + Value *Int64Slice + DefaultText string } // String returns a readable representation of this value @@ -351,12 +359,13 @@ func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { // Float64SliceFlag is a flag with type *Float64Slice type Float64SliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value *Float64Slice + Name string + Aliases []string + Usage string + EnvVars []string + Hidden bool + Value *Float64Slice + DefaultText string } // String returns a readable representation of this value @@ -399,6 +408,7 @@ type StringFlag struct { EnvVars []string Hidden bool Value string + DefaultText string Destination *string } @@ -436,12 +446,13 @@ func lookupString(name string, set *flag.FlagSet) string { // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value *StringSlice + Name string + Aliases []string + Usage string + EnvVars []string + Hidden bool + Value *StringSlice + DefaultText string } // String returns a readable representation of this value @@ -484,6 +495,7 @@ type Uint64Flag struct { EnvVars []string Hidden bool Value uint64 + DefaultText string Destination *uint64 } @@ -527,6 +539,7 @@ type UintFlag struct { EnvVars []string Hidden bool Value uint + DefaultText string Destination *uint } diff --git a/flag_test.go b/flag_test.go index e8c8bcd..be126f9 100644 --- a/flag_test.go +++ b/flag_test.go @@ -67,6 +67,16 @@ func TestStringFlagHelpOutput(t *testing.T) { } } +func TestStringFlagDefaultText(t *testing.T) { + flag := &StringFlag{Name: "foo", Aliases: nil, Usage: "amount of `foo` requested", Value: "none", DefaultText: "all of it"} + expected := "--foo foo\tamount of foo requested (default: \"all of it\")" + output := flag.String() + + if output != expected { + t.Errorf("%q does not match %q", output, expected) + } +} + func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() os.Setenv("APP_FOO", "derp") @@ -482,7 +492,6 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) { expect(t, v, float64(43.33333)) } - var float64SliceFlagTests = []struct { name string aliases []string @@ -523,8 +532,6 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } - - var genericFlagTests = []struct { name string value Generic @@ -1100,7 +1107,6 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { a.Run([]string{"run"}) } - func TestParseMultiFloat64SliceFromEnv(t *testing.T) { os.Clearenv() os.Setenv("APP_INTERVALS", "0.1,-10.5") @@ -1141,7 +1147,6 @@ func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { }).Run([]string{"run"}) } - func TestParseMultiBool(t *testing.T) { a := App{ Flags: []Flag{ diff --git a/generate-flag-types b/generate-flag-types index 6244ff9..276cfc9 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -145,6 +145,7 @@ def _write_cli_flag_types(outfile, types): EnvVars []string Hidden bool Value {type} + DefaultText string """.format(**typedef)) if typedef['dest']: From 7d56512ecc02936aab92a89dc8784c44fc614133 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Tue, 30 Aug 2016 10:52:24 -0400 Subject: [PATCH 096/158] Add documentation, remove quotes by default --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ flag.go | 2 +- flag_test.go | 2 +- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f0980e..43f2247 100644 --- a/README.md +++ b/README.md @@ -589,6 +589,48 @@ func main() { } ``` +#### Default Values for help output + +Sometimes it's useful to specify a flag's default help-text value within the flag declaration. This can be useful if the default value for a flag is a computed value. The default value can be set via the `DefaultText` struct field. + +For example this: + + +```go +package main + +import ( + "os" + + "gopkg.in/urfave/cli.v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "port", + Usage: "Use a randomized port", + Value: 0, + DefaultText: "random", + }, + }, + } + + app.Run(os.Args) +} +``` + +Will result in help output like: + +``` +--port value Use a randomized port (default: random) +``` + + ### Subcommands Subcommands can be defined for a more git-like command line app. diff --git a/flag.go b/flag.go index 29010f2..10adca8 100644 --- a/flag.go +++ b/flag.go @@ -742,7 +742,7 @@ func stringifyFlag(f Flag) string { helpText := fv.FieldByName("DefaultText") if helpText.IsValid() && helpText.String() != "" { needsPlaceholder = val.Kind() != reflect.Bool - defaultValueString = fmt.Sprintf(" (default: %q)", helpText.String()) + defaultValueString = fmt.Sprintf(" (default: %s)", helpText.String()) } if defaultValueString == " (default: )" { diff --git a/flag_test.go b/flag_test.go index be126f9..ccb1d45 100644 --- a/flag_test.go +++ b/flag_test.go @@ -69,7 +69,7 @@ func TestStringFlagHelpOutput(t *testing.T) { func TestStringFlagDefaultText(t *testing.T) { flag := &StringFlag{Name: "foo", Aliases: nil, Usage: "amount of `foo` requested", Value: "none", DefaultText: "all of it"} - expected := "--foo foo\tamount of foo requested (default: \"all of it\")" + expected := "--foo foo\tamount of foo requested (default: all of it)" output := flag.String() if output != expected { From 51cebd042ae6c88d8e3083ed0c2980ea94624fc3 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Tue, 30 Aug 2016 10:56:28 -0400 Subject: [PATCH 097/158] Update TOC in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 43f2247..09f4a57 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ applications in an expressive way. + [Alternate Names](#alternate-names) + [Values from the Environment](#values-from-the-environment) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + + [Default Values for Help Output](#default-values-for-help-output) * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) * [Exit code](#exit-code) From c8d66d7edab7375cabe5522889d318738c8345b1 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Tue, 30 Aug 2016 11:12:22 -0400 Subject: [PATCH 098/158] Lowercase TOC to pass tests --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09f4a57..f72b7cc 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ applications in an expressive way. + [Alternate Names](#alternate-names) + [Values from the Environment](#values-from-the-environment) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) - + [Default Values for Help Output](#default-values-for-help-output) + + [Default Values for help output](#default-values-for-help-output) * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) * [Exit code](#exit-code) From 03f77c6e740501d14f0596a9a0beadffb23a89a0 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 29 Oct 2016 14:33:41 -0700 Subject: [PATCH 099/158] Fix new example for v2 branch --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d82a8cd..d46de8d 100644 --- a/README.md +++ b/README.md @@ -480,17 +480,17 @@ import ( ) func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "Language for the greeting", - }, - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "Language for the greeting", + }, + &cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, }, } From 230bac34b72f6980b8a155ffaec040840189126a Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Tue, 14 Feb 2017 21:17:05 -0800 Subject: [PATCH 100/158] Allow slightly longer lines in Python scripts --- .flake8 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6deafc2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 From 04b2f4ff79cf1fd71e138bafc67df8bbdb5b81c2 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Tue, 14 Feb 2017 21:18:00 -0800 Subject: [PATCH 101/158] Adjust spacing in cli-v1-to-v2 to satisfy flake8 --- cli-v1-to-v2 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index 36e1cd5..aa83461 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -298,24 +298,28 @@ def _app_init(source): '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 From 9073334ecdb2120c75dafab1afba075d56366162 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Tue, 14 Feb 2017 20:30:04 -0800 Subject: [PATCH 102/158] Fix context.IsSet() Accidentally mangled the implementation when merging master into the `v2` branch resulting in a dropped test. --- context.go | 2 +- context_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 429172c..e6e86b0 100644 --- a/context.go +++ b/context.go @@ -78,7 +78,7 @@ func (c *Context) IsSet(name string) bool { for _, envVar := range envVarValues.Interface().([]string) { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { - continue + return true } } diff --git a/context_test.go b/context_test.go index 1083eeb..870d4cd 100644 --- a/context_test.go +++ b/context_test.go @@ -2,6 +2,7 @@ package cli import ( "flag" + "os" "sort" "testing" "time" @@ -157,6 +158,68 @@ func TestContext_IsSet(t *testing.T) { expect(t, ctx.IsSet("bogus"), false) } +// XXX Corresponds to hack in context.IsSet for flags with EnvVar field +// Should be moved to `flag_test` in v2 +func TestContext_IsSet_fromEnv(t *testing.T) { + var ( + timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet bool + globalTimeoutIsSet, TIsSet, globalNoEnvVarIsSet, NIsSet bool + ) + + os.Clearenv() + os.Setenv("GLOBAL_APP_TIMEOUT_SECONDS", "15.5") + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := App{ + Flags: []Flag{ + &Float64Flag{ + Name: "global-timeout", + Aliases: []string{"T"}, + EnvVars: []string{"GLOBAL_APP_TIMEOUT_SECONDS"}, + }, + &Float64Flag{ + Name: "global-no-env-var", + Aliases: []string{"N"}, + }, + }, + Commands: []*Command{ + { + Name: "hello", + Flags: []Flag{ + &Float64Flag{ + Name: "timeout", + Aliases: []string{"t"}, + EnvVars: []string{"APP_TIMEOUT_SECONDS"}, + }, + &Float64Flag{ + Name: "no-env-var", + Aliases: []string{"n"}, + }, + }, + Action: func(ctx *Context) error { + globalTimeoutIsSet = ctx.IsSet("global-timeout") + TIsSet = ctx.IsSet("T") + globalNoEnvVarIsSet = ctx.IsSet("global-no-env-var") + NIsSet = ctx.IsSet("N") + timeoutIsSet = ctx.IsSet("timeout") + tIsSet = ctx.IsSet("t") + noEnvVarIsSet = ctx.IsSet("no-env-var") + nIsSet = ctx.IsSet("n") + return nil + }, + }, + }, + } + a.Run([]string{"run", "hello"}) + expect(t, globalTimeoutIsSet, true) + expect(t, TIsSet, true) + expect(t, globalNoEnvVarIsSet, false) + expect(t, NIsSet, false) + expect(t, timeoutIsSet, true) + expect(t, tIsSet, true) + expect(t, noEnvVarIsSet, false) + expect(t, nIsSet, false) +} + func TestContext_NumFlags(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") From 8b9345ec9dd6947ba776af88dff3f1b250c3725f Mon Sep 17 00:00:00 2001 From: Nuruddin Ashr Date: Tue, 4 Apr 2017 13:53:15 +0700 Subject: [PATCH 103/158] Remove the error return signature --- help.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/help.go b/help.go index f16e6fa..188366f 100644 --- a/help.go +++ b/help.go @@ -121,9 +121,8 @@ var HelpPrinter helpPrinter = printHelp var VersionPrinter = printVersion // ShowAppHelp is an action that displays the help. -func ShowAppHelp(c *Context) error { +func ShowAppHelp(c *Context) { HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) - return nil } // DefaultAppComplete prints the list of subcommands as the default app completion method From 47a412375fd57dad5f612a210e46db23e735eb76 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 4 Aug 2017 12:00:22 -0400 Subject: [PATCH 104/158] Resolved compile-time errors since merging from v1 --- app_test.go | 380 ++++++++++++++++++------------------------------ command_test.go | 83 ++++++----- context.go | 5 + context_test.go | 73 ---------- errors.go | 6 +- errors_test.go | 10 +- flag.go | 113 +++++++++++++- flag_test.go | 66 ++++----- help.go | 24 ++- help_test.go | 10 +- runtests | 25 ++-- 11 files changed, 364 insertions(+), 431 deletions(-) diff --git a/app_test.go b/app_test.go index 15d6a74..88c66ac 100644 --- a/app_test.go +++ b/app_test.go @@ -11,6 +11,8 @@ import ( "reflect" "strings" "testing" + + "github.com/stretchr/testify/assert" ) var ( @@ -201,14 +203,15 @@ func ExampleApp_Run_noAction() { } func ExampleApp_Run_subcommandNoAction() { - app := App{} - app.Name = "greet" - app.Commands = []Command{ - { - Name: "describeit", - Aliases: []string{"d"}, - Usage: "use it to see a description", - Description: "This is how we describe describeit the function", + app := &App{ + Name: "greet", + Commands: []*Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + }, }, } app.Run([]string{"greet", "describeit"}) @@ -273,10 +276,10 @@ func TestApp_Run(t *testing.T) { } err := app.Run([]string{"command", "foo"}) - expect(t, err, nil) + assert.Nil(t, err) err = app.Run([]string{"command", "bar"}) - expect(t, err, nil) - expect(t, s, "foobar") + assert.Nil(t, err) + assert.Equal(t, "foobar", s) } var commandAppTests = []struct { @@ -292,12 +295,11 @@ var commandAppTests = []struct { } func TestApp_Command(t *testing.T) { - app := &App{} - fooCommand := &Command{Name: "foobar", Aliases: []string{"f"}} - batCommand := &Command{Name: "batbaz", Aliases: []string{"b"}} - app.Commands = []*Command{ - fooCommand, - batCommand, + app := &App{ + Commands: []*Command{ + {Name: "foobar", Aliases: []string{"f"}}, + {Name: "batbaz", Aliases: []string{"b"}}, + }, } for _, test := range commandAppTests { @@ -314,24 +316,25 @@ func TestApp_Setup_defaultsWriter(t *testing.T) { func TestApp_CommandWithArgBeforeFlags(t *testing.T) { var parsedOption, firstArg string - app := NewApp() - command := Command{ - Name: "cmd", - Flags: []Flag{ - StringFlag{Name: "option", Value: "", Usage: "some option"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - firstArg = c.Args().First() - return nil + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Flags: []Flag{ + &StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *Context) error { + parsedOption = c.String("option") + firstArg = c.Args().First() + return nil + }, + }, }, } - app.Commands = []Command{command} - app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) - expect(t, parsedOption, "my-option") - expect(t, firstArg, "my-arg") + assert.Equal(t, parsedOption, "my-option") + assert.Equal(t, firstArg, "my-arg") } func TestApp_RunAsSubcommandParseFlags(t *testing.T) { @@ -384,19 +387,21 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { var parsedOption string var args Args - app := &App{} - command := &Command{ - Name: "cmd", - Flags: []Flag{ - &StringFlag{Name: "option", Value: "", Usage: "some option"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - args = c.Args() - return nil + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Flags: []Flag{ + &StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *Context) error { + parsedOption = c.String("option") + args = c.Args() + return nil + }, + }, }, } - app.Commands = []*Command{command} app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}) @@ -409,15 +414,17 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { func TestApp_CommandWithDash(t *testing.T) { var args Args - app := &App{} - command := &Command{ - Name: "cmd", - Action: func(c *Context) error { - args = c.Args() - return nil + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + }, }, } - app.Commands = []*Command{command} app.Run([]string{"", "cmd", "my-arg", "-"}) @@ -428,15 +435,17 @@ func TestApp_CommandWithDash(t *testing.T) { func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { var args Args - app := &App{} - command := &Command{ - Name: "cmd", - Action: func(c *Context) error { - args = c.Args() - return nil + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + }, }, } - app.Commands = []*Command{command} app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) @@ -518,22 +527,24 @@ func TestApp_ParseSliceFlags(t *testing.T) { var parsedIntSlice []int var parsedStringSlice []string - app := &App{} - command := &Command{ - Name: "cmd", - Flags: []Flag{ - &IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"}, - &StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"}, - }, - Action: func(c *Context) error { - parsedIntSlice = c.IntSlice("p") - parsedStringSlice = c.StringSlice("ip") - parsedOption = c.String("option") - firstArg = c.Args().First() - return nil + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Flags: []Flag{ + &IntSliceFlag{Name: "p", Value: NewIntSlice(), Usage: "set one or more ip addr"}, + &StringSliceFlag{Name: "ip", Value: NewStringSlice(), Usage: "set one or more ports to open"}, + }, + Action: func(c *Context) error { + parsedIntSlice = c.IntSlice("p") + parsedStringSlice = c.StringSlice("ip") + parsedOption = c.String("option") + firstArg = c.Args().First() + return nil + }, + }, }, } - app.Commands = []*Command{command} app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"}) @@ -576,20 +587,22 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { var parsedIntSlice []int var parsedStringSlice []string - app := &App{} - command := &Command{ - Name: "cmd", - Flags: []Flag{ - &IntSliceFlag{Name: "a", Usage: "set numbers"}, - &StringSliceFlag{Name: "str", Usage: "set strings"}, - }, - Action: func(c *Context) error { - parsedIntSlice = c.IntSlice("a") - parsedStringSlice = c.StringSlice("str") - return nil + app := &App{ + Commands: []*Command{ + { + Name: "cmd", + Flags: []Flag{ + &IntSliceFlag{Name: "a", Usage: "set numbers"}, + &StringSliceFlag{Name: "str", Usage: "set strings"}, + }, + Action: func(c *Context) error { + parsedIntSlice = c.IntSlice("a") + parsedStringSlice = c.StringSlice("str") + return nil + }, + }, }, } - app.Commands = []*Command{command} app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"}) @@ -1569,8 +1582,8 @@ func (c *customBoolFlag) String() string { return "***" + c.Nombre + "***" } -func (c *customBoolFlag) GetName() string { - return c.Nombre +func (c *customBoolFlag) Names() []string { + return []string{c.Nombre} } func (c *customBoolFlag) Apply(set *flag.FlagSet) { @@ -1578,8 +1591,9 @@ func (c *customBoolFlag) Apply(set *flag.FlagSet) { } func TestCustomFlagsUnused(t *testing.T) { - app := NewApp() - app.Flags = []Flag{&customBoolFlag{"custom"}} + app := &App{ + Flags: []Flag{&customBoolFlag{"custom"}}, + } err := app.Run([]string{"foo"}) if err != nil { @@ -1588,8 +1602,9 @@ func TestCustomFlagsUnused(t *testing.T) { } func TestCustomFlagsUsed(t *testing.T) { - app := NewApp() - app.Flags = []Flag{&customBoolFlag{"custom"}} + app := &App{ + Flags: []Flag{&customBoolFlag{"custom"}}, + } err := app.Run([]string{"foo", "--custom=bar"}) if err != nil { @@ -1598,12 +1613,12 @@ func TestCustomFlagsUsed(t *testing.T) { } func TestCustomHelpVersionFlags(t *testing.T) { - app := NewApp() + app := &App{} // Be sure to reset the global flags defer func(helpFlag Flag, versionFlag Flag) { - HelpFlag = helpFlag - VersionFlag = versionFlag + HelpFlag = helpFlag.(*BoolFlag) + VersionFlag = versionFlag.(*BoolFlag) }(HelpFlag, VersionFlag) HelpFlag = &customBoolFlag{"help-custom"} @@ -1615,166 +1630,47 @@ func TestCustomHelpVersionFlags(t *testing.T) { } } -func TestHandleAction_WithNonFuncAction(t *testing.T) { - app := NewApp() - app.Action = 42 - fs, err := flagSet(app.Name, app.Flags) - if err != nil { - t.Errorf("error creating FlagSet: %s", err) - } - err = HandleAction(app.Action, NewContext(app, fs, nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") { - t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { - app := NewApp() - app.Action = func() string { return "" } - fs, err := flagSet(app.Name, app.Flags) - if err != nil { - t.Errorf("error creating FlagSet: %s", err) - } - err = HandleAction(app.Action, NewContext(app, fs, nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { - t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { - app := NewApp() - app.Action = func(_ *Context) (int, error) { return 0, nil } - fs, err := flagSet(app.Name, app.Flags) - if err != nil { - t.Errorf("error creating FlagSet: %s", err) - } - err = HandleAction(app.Action, NewContext(app, fs, nil)) - - if err == nil { - t.Fatalf("expected to receive error from Run, got none") - } - - exitErr, ok := err.(*ExitError) - - if !ok { - t.Fatalf("expected to receive a *ExitError") - } - - if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { - t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) - } - - if exitErr.ExitCode() != 2 { - t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) - } -} - -func TestHandleAction_WithUnknownPanic(t *testing.T) { - defer func() { refute(t, recover(), nil) }() - - var fn ActionFunc - - app := NewApp() - app.Action = func(ctx *Context) error { - fn(ctx) - return nil - } - fs, err := flagSet(app.Name, app.Flags) - if err != nil { - t.Errorf("error creating FlagSet: %s", err) - } - HandleAction(app.Action, NewContext(app, fs, nil)) -} - func TestShellCompletionForIncompleteFlags(t *testing.T) { - app := NewApp() - app.Flags = []Flag{ - IntFlag{ - Name: "test-completion", + app := &App{ + Flags: []Flag{ + &IntFlag{ + Name: "test-completion", + }, }, - } - app.EnableBashCompletion = true - app.BashComplete = func(ctx *Context) { - for _, command := range ctx.App.Commands { - if command.Hidden { - continue - } - - for _, name := range command.Names() { - fmt.Fprintln(ctx.App.Writer, name) - } - } - - for _, flag := range ctx.App.Flags { - for _, name := range strings.Split(flag.GetName(), ",") { - if name == BashCompletionFlag.GetName() { + EnableShellCompletion: true, + ShellComplete: func(ctx *Context) { + for _, command := range ctx.App.Commands { + if command.Hidden { continue } - switch name = strings.TrimSpace(name); len(name) { - case 0: - case 1: - fmt.Fprintln(ctx.App.Writer, "-"+name) - default: - fmt.Fprintln(ctx.App.Writer, "--"+name) + for _, name := range command.Names() { + fmt.Fprintln(ctx.App.Writer, name) } } - } + + for _, flag := range ctx.App.Flags { + for _, name := range flag.Names() { + if name == genCompName() { + continue + } + + switch name = strings.TrimSpace(name); len(name) { + case 0: + case 1: + fmt.Fprintln(ctx.App.Writer, "-"+name) + default: + fmt.Fprintln(ctx.App.Writer, "--"+name) + } + } + } + }, + Action: func(ctx *Context) error { + return fmt.Errorf("should not get here") + }, } - app.Action = func(ctx *Context) error { - return fmt.Errorf("should not get here") - } - err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.GetName()}) + err := app.Run([]string{"", "--test-completion", "--" + genCompName()}) if err != nil { t.Errorf("app should not return an error: %s", err) } } - -func TestHandleActionActuallyWorksWithActions(t *testing.T) { - var f ActionFunc - called := false - f = func(c *Context) error { - called = true - return nil - } - - err := HandleAction(f, nil) - - if err != nil { - t.Errorf("Should not have errored: %v", err) - } - - if !called { - t.Errorf("Function was not called") - } -} diff --git a/command_test.go b/command_test.go index 6a9f3bd..7fb3980 100644 --- a/command_test.go +++ b/command_test.go @@ -124,15 +124,16 @@ func TestCommand_Run_BeforeSavesMetadata(t *testing.T) { } func TestCommand_OnUsageError_hasCommandContext(t *testing.T) { - app := NewApp() - app.Commands = []Command{ - { - Name: "bar", - Flags: []Flag{ - IntFlag{Name: "flag"}, - }, - OnUsageError: func(c *Context, err error, _ bool) error { - return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error()) + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error()) + }, }, }, } @@ -176,23 +177,24 @@ func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { } func TestCommand_OnUsageError_WithSubcommand(t *testing.T) { - app := NewApp() - app.Commands = []Command{ - { - Name: "bar", - Subcommands: []Command{ - { - Name: "baz", + app := &App{ + Commands: []*Command{ + { + Name: "bar", + Subcommands: []*Command{ + { + Name: "baz", + }, + }, + Flags: []Flag{ + &IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) }, - }, - Flags: []Flag{ - IntFlag{Name: "flag"}, - }, - OnUsageError: func(c *Context, err error, _ bool) error { - if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { - t.Errorf("Expect an invalid value error, but got \"%v\"", err) - } - return errors.New("intercepted: " + err.Error()) }, }, } @@ -208,22 +210,23 @@ func TestCommand_OnUsageError_WithSubcommand(t *testing.T) { } func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { - app := NewApp() - app.ErrWriter = ioutil.Discard - app.Commands = []Command{ - { - Name: "bar", - Usage: "this is for testing", - Subcommands: []Command{ - { - Name: "baz", - Usage: "this is for testing", - Action: func(c *Context) error { - if c.App.ErrWriter != ioutil.Discard { - return fmt.Errorf("ErrWriter not passed") - } + app := &App{ + ErrWriter: ioutil.Discard, + Commands: []*Command{ + { + Name: "bar", + Usage: "this is for testing", + Subcommands: []*Command{ + { + Name: "baz", + Usage: "this is for testing", + Action: func(c *Context) error { + if c.App.ErrWriter != ioutil.Discard { + return fmt.Errorf("ErrWriter not passed") + } - return nil + return nil + }, }, }, }, diff --git a/context.go b/context.go index 2b80442..aa015b9 100644 --- a/context.go +++ b/context.go @@ -121,6 +121,11 @@ func (c *Context) Lineage() []*Context { return lineage } +// value returns the value of the flag coressponding to `name` +func (c *Context) value(name string) interface{} { + return c.flagSet.Lookup(name).Value.(flag.Getter).Get() +} + // Args returns the command line arguments associated with the context. func (c *Context) Args() Args { ret := args(c.flagSet.Args()) diff --git a/context_test.go b/context_test.go index 688bda1..edfbaee 100644 --- a/context_test.go +++ b/context_test.go @@ -2,7 +2,6 @@ package cli import ( "flag" - "os" "sort" "testing" "time" @@ -158,78 +157,6 @@ func TestContext_IsSet(t *testing.T) { expect(t, ctx.IsSet("bogus"), false) } -// XXX Corresponds to hack in context.IsSet for flags with EnvVar field -// Should be moved to `flag_test` in v2 -func TestContext_IsSet_fromEnv(t *testing.T) { - var ( - timeoutIsSet, tIsSet, noEnvVarIsSet, nIsSet bool - globalTimeoutIsSet, TIsSet, globalNoEnvVarIsSet, NIsSet bool - ) - - clearenv() - os.Setenv("GLOBAL_APP_TIMEOUT_SECONDS", "15.5") - os.Setenv("APP_TIMEOUT_SECONDS", "15.5") - os.Setenv("APP_PASSWORD", "") - a := App{ - Flags: []Flag{ - &Float64Flag{ - Name: "global-timeout", - Aliases: []string{"T"}, - EnvVars: []string{"GLOBAL_APP_TIMEOUT_SECONDS"}, - }, - &Float64Flag{ - Name: "global-no-env-var", - Aliases: []string{"N"}, - }, - }, - Commands: []*Command{ - { - Name: "hello", - Flags: []Flag{ - &Float64Flag{ - Name: "timeout", - Aliases: []string{"t"}, - EnvVars: []string{"APP_TIMEOUT_SECONDS"}, - }, - &Float64Flag{ - Name: "no-env-var", - Aliases: []string{"n"}, - }, - }, - Action: func(ctx *Context) error { - globalTimeoutIsSet = ctx.IsSet("global-timeout") - TIsSet = ctx.IsSet("T") - globalNoEnvVarIsSet = ctx.IsSet("global-no-env-var") - NIsSet = ctx.IsSet("N") - timeoutIsSet = ctx.IsSet("timeout") - tIsSet = ctx.IsSet("t") - noEnvVarIsSet = ctx.IsSet("no-env-var") - nIsSet = ctx.IsSet("n") - return nil - }, - }, - }, - } - a.Run([]string{"run", "hello"}) - expect(t, globalTimeoutIsSet, true) - expect(t, TIsSet, true) - expect(t, globalNoEnvVarIsSet, false) - expect(t, NIsSet, false) - expect(t, timeoutIsSet, true) - expect(t, tIsSet, true) - expect(t, passwordIsSet, true) - expect(t, pIsSet, true) - expect(t, noEnvVarIsSet, false) - expect(t, nIsSet, false) - - 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") diff --git a/errors.go b/errors.go index 963eb05..236ef02 100644 --- a/errors.go +++ b/errors.go @@ -66,7 +66,7 @@ type exitError struct { // Exit wraps a message and exit code into an ExitCoder suitable for handling by // HandleExitCoder -func Exit(message string, exitCode int) ExitCoder { +func Exit(message interface{}, exitCode int) ExitCoder { return &exitError{ exitCode: exitCode, message: message, @@ -74,7 +74,7 @@ func Exit(message string, exitCode int) ExitCoder { } func (ee *exitError) Error() string { - return ee.message + return fmt.Sprintf("%v", ee.message) } func (ee *exitError) ExitCode() int { @@ -112,7 +112,7 @@ func HandleExitCoder(err error) { func handleMultiError(multiErr MultiError) int { code := 1 - for _, merr := range multiErr.Errors { + for _, merr := range multiErr.Errors() { if multiErr2, ok := merr.(MultiError); ok { code = handleMultiError(multiErr2) } else { diff --git a/errors_test.go b/errors_test.go index 54f5348..7c85d7b 100644 --- a/errors_test.go +++ b/errors_test.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "testing" + + "github.com/stretchr/testify/assert" ) func TestHandleExitCoder_nil(t *testing.T) { @@ -95,7 +97,7 @@ func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { ErrWriter = fakeErrWriter }() - err := NewExitError(NewErrorWithFormat("I am formatted"), 1) + err := Exit(NewErrorWithFormat("I am formatted"), 1) HandleExitCoder(err) expect(t, called, true) @@ -114,9 +116,9 @@ func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { defer func() { OsExiter = fakeOsExiter }() - err := NewMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2")) + err := newMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2")) HandleExitCoder(err) - expect(t, called, true) - expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: err1\nThis the format: err2\n") + assert.True(t, called) + assert.Equal(t, "This the format: err1\nThis the format: err2\n", ErrWriter.(*bytes.Buffer).String()) } diff --git a/flag.go b/flag.go index ce768f0..8e6920a 100644 --- a/flag.go +++ b/flag.go @@ -22,11 +22,19 @@ var ( ) // GenerateCompletionFlag enables completion for all commands and subcommands -var GenerateCompletionFlag = &BoolFlag{ +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", @@ -34,7 +42,7 @@ var InitCompletionFlag = &StringFlag{ } // VersionFlag prints the version for the application -var VersionFlag = &BoolFlag{ +var VersionFlag Flag = &BoolFlag{ Name: "version", Aliases: []string{"v"}, Usage: "print the version", @@ -43,7 +51,7 @@ var VersionFlag = &BoolFlag{ // 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 = &BoolFlag{ +var HelpFlag Flag = &BoolFlag{ Name: "help", Aliases: []string{"h"}, Usage: "show help", @@ -121,7 +129,14 @@ type Generic interface { // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag +// Ignores parsing errors func (f *GenericFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError takes the flagset and calls Set on the generic flag with the +// value provided by the user for parsing by the flag +func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error { val := f.Value if f.EnvVars != nil { for _, envVar := range f.EnvVars { @@ -135,6 +150,7 @@ func (f *GenericFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { set.Var(val, name, f.Usage) } + return nil } // StringSlice wraps a []string to satisfy flag.Value @@ -188,7 +204,13 @@ func (f *StringSlice) Get() interface{} { } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *StringSliceFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -212,6 +234,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) } + return nil } // IntSlice wraps an []int to satisfy flag.Value @@ -285,7 +308,13 @@ func (f *IntSlice) Get() interface{} { } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *IntSliceFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -309,6 +338,7 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) } + return nil } // Int64Slice is an opaque type for []int to satisfy flag.Value @@ -362,7 +392,13 @@ func (f *Int64Slice) Get() interface{} { } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -386,10 +422,17 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) } + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *BoolFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -398,7 +441,6 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) { f.Value = envValBool } - val = envValBool break } } @@ -411,10 +453,17 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) { } set.Bool(name, f.Value, f.Usage) } + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *StringFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -431,10 +480,17 @@ func (f *StringFlag) Apply(set *flag.FlagSet) { } set.String(name, f.Value, f.Usage) } + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *IntFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -455,10 +511,17 @@ func (f *IntFlag) Apply(set *flag.FlagSet) { } set.Int(name, f.Value, f.Usage) } + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *Int64Flag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -476,14 +539,21 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.Int64Var(f.Destination, name, f.Value, f.Usage) - return + return nil } set.Int64(name, f.Value, f.Usage) } + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *UintFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -501,14 +571,21 @@ func (f *UintFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.UintVar(f.Destination, name, f.Value, f.Usage) - return + return nil } set.Uint(name, f.Value, f.Usage) } + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *Uint64Flag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -526,14 +603,21 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { if f.Destination != nil { set.Uint64Var(f.Destination, name, f.Value, f.Usage) - return + return nil } set.Uint64(name, f.Value, f.Usage) } + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *DurationFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -555,10 +639,17 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) { } set.Duration(name, f.Value, f.Usage) } + return nil } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *Float64Flag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -580,6 +671,7 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) { } set.Float64(name, f.Value, f.Usage) } + return nil } // NewFloat64Slice makes a *Float64Slice with default values @@ -633,7 +725,13 @@ func (f *Float64Slice) Value() []float64 { } // Apply populates the flag given the flag set and environment +// Ignores errors func (f *Float64SliceFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *Float64SliceFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { @@ -658,6 +756,7 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) { for _, name := range f.Names() { set.Var(f.Value, name, f.Usage) } + return nil } func visibleFlags(fl []Flag) []Flag { diff --git a/flag_test.go b/flag_test.go index 9f31f27..1e44db6 100644 --- a/flag_test.go +++ b/flag_test.go @@ -49,57 +49,57 @@ func TestFlagsFromEnv(t *testing.T) { flag Flag errRegexp string }{ - {"", false, &BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, - {"1", true, &BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, - {"false", false, &BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, - {"foobar", true, &BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, + {"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, + {"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, + {"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, + {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, - {"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVar: "TIME"}, ""}, - {"foobar", false, &DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)}, + {"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""}, + {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)}, - {"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, - {"1", 1.0, &Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, - {"foobar", 0, &Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)}, + {"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)}, - {"1", int64(1), &Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, - {"1.2", 0, &Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, - {"foobar", 0, &Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, + {"1", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, + {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, - {"1", 1, &IntFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, - {"1.2", 0, &IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, - {"foobar", 0, &IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, + {"1", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, + {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, - {"1,2", NewIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, - {"1.2,2", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)}, - {"foobar", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)}, + {"1,2", NewIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)}, + {"foobar", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)}, - {"1,2", NewInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, - {"1.2,2", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)}, - {"foobar", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)}, + {"1,2", NewInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)}, + {"foobar", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)}, - {"foo", "foo", &StringFlag{Name: "name", EnvVar: "NAME"}, ""}, + {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""}, - {"foo,bar", NewStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVar: "NAMES"}, ""}, + {"foo,bar", NewStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, - {"1", uint(1), &UintFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, - {"1.2", 0, &UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)}, - {"foobar", 0, &UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)}, + {"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)}, + {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)}, - {"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, - {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)}, - {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)}, + {"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)}, + {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)}, - {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, ""}, + {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } for _, test := range flagTests { clearenv() - os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input) + os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVars").Slice(0, 1).String(), test.input) a := App{ Flags: []Flag{test.flag}, Action: func(ctx *Context) error { - if !reflect.DeepEqual(ctx.value(test.flag.GetName()), test.output) { - t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.value(test.flag.GetName())) + if !reflect.DeepEqual(ctx.value(test.flag.Names()[0]), test.output) { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.value(test.flag.Names()[0])) } return nil }, diff --git a/help.go b/help.go index 3e6a461..6c4f213 100644 --- a/help.go +++ b/help.go @@ -266,11 +266,9 @@ func printHelp(out io.Writer, templ string, data interface{}) { func checkVersion(c *Context) bool { found := false - if VersionFlag.Name != "" { - for _, name := range VersionFlag.Names() { - if c.Bool(name) { - found = true - } + for _, name := range VersionFlag.Names() { + if c.Bool(name) { + found = true } } return found @@ -278,11 +276,9 @@ func checkVersion(c *Context) bool { func checkHelp(c *Context) bool { found := false - if HelpFlag.Name != "" { - for _, name := range HelpFlag.Names() { - if c.Bool(name) { - found = true - } + for _, name := range HelpFlag.Names() { + if c.Bool(name) { + found = true } } return found @@ -307,14 +303,14 @@ func checkSubcommandHelp(c *Context) bool { } func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { - if !a.EnableBashCompletion { + if !a.EnableShellCompletion { return false, arguments } pos := len(arguments) - 1 lastArg := arguments[pos] - if lastArg != "--"+BashCompletionFlag.GetName() { + if lastArg != "--"+genCompName() { return false, arguments } @@ -322,7 +318,7 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { } func checkCompletions(c *Context) bool { - if !c.Bool(GenerateCompletionFlag.Name) && !c.App.EnableShellCompletion { + if !c.Bool(genCompName()) && !c.App.EnableShellCompletion { return false } @@ -339,7 +335,7 @@ func checkCompletions(c *Context) bool { } func checkCommandCompletions(c *Context, name string) bool { - if !c.Bool(GenerateCompletionFlag.Name) && !c.App.EnableShellCompletion { + if !c.Bool(genCompName()) && !c.App.EnableShellCompletion { return false } diff --git a/help_test.go b/help_test.go index 4fbb0fc..375b28e 100644 --- a/help_test.go +++ b/help_test.go @@ -259,7 +259,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) { func TestShowCommandHelp_Customtemplate(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Action: func(ctx *Context) error { @@ -301,7 +301,7 @@ EXAMPLES: func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", UsageText: "this is usage text", @@ -321,10 +321,10 @@ func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", - Subcommands: []Command{ + Subcommands: []*Command{ { Name: "bobbly", UsageText: "this is usage text", @@ -377,7 +377,7 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) { func TestShowAppHelp_CustomAppTemplate(t *testing.T) { app := &App{ - Commands: []Command{ + Commands: []*Command{ { Name: "frobbly", Action: func(ctx *Context) error { diff --git a/runtests b/runtests index bcd8f4c..fe0d011 100755 --- a/runtests +++ b/runtests @@ -1,7 +1,8 @@ #!/usr/bin/env python -from __future__ import print_function +from __future__ import print_function, unicode_literals import argparse +import codecs import glob import os import platform @@ -37,7 +38,7 @@ def _target(func): @_target def _test(): - if check_output('go version'.split()).split()[2] < 'go1.2': + if _go_version() < 'go1.2': _run('go test -v .') return @@ -61,7 +62,7 @@ def _test(): @_target def _gfmrun(): - go_version = check_output('go version'.split()).split()[2] + go_version = _go_version() if go_version < 'go1.3': print('runtests: skip on {}'.format(go_version), file=sys.stderr) return @@ -75,7 +76,7 @@ def _vet(): @_target def _migrations(): - go_version = check_output('go version'.split()).split()[2] + go_version = _go_version() if go_version < 'go1.3': print('runtests: skip on {}'.format(go_version), file=sys.stderr) return @@ -115,7 +116,7 @@ def _toc(): @_target def _gen(): - go_version = check_output('go version'.split()).split()[2] + go_version = _go_version() if go_version < 'go1.5': print('runtests: skip on {}'.format(go_version), file=sys.stderr) return @@ -133,26 +134,30 @@ def _run(command): def _gfmrun_count(): - with open('README.md') as infile: + with codecs.open('README.md', 'r', 'utf-8') as infile: lines = infile.read().splitlines() - return len(filter(_is_go_runnable, lines)) + return len(list(filter(_is_go_runnable, lines))) def _is_go_runnable(line): return line.startswith('package main') +def _go_version(): + return check_output('go version'.split()).decode('utf-8').split()[2] + + def _combine_coverprofiles(coverprofiles): combined = tempfile.NamedTemporaryFile( suffix='.coverprofile', delete=False ) - combined.write('mode: set\n') + combined.write(b'mode: set\n') for coverprofile in coverprofiles: - with open(coverprofile, 'r') as infile: + with codecs.open(coverprofile, 'r', 'utf-8') as infile: for line in infile.readlines(): if not line.startswith('mode: '): - combined.write(line) + combined.write(line.encode('utf-8')) combined.flush() name = combined.name From df5c6caa961b1ecd16a08bf5eb7961946477cf2a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 4 Aug 2017 12:43:16 -0400 Subject: [PATCH 105/158] Back out the testify/assert addition :sweat_smile: --- app_test.go | 12 +++++------- errors_test.go | 6 ++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/app_test.go b/app_test.go index 88c66ac..00b12e9 100644 --- a/app_test.go +++ b/app_test.go @@ -11,8 +11,6 @@ import ( "reflect" "strings" "testing" - - "github.com/stretchr/testify/assert" ) var ( @@ -276,10 +274,10 @@ func TestApp_Run(t *testing.T) { } err := app.Run([]string{"command", "foo"}) - assert.Nil(t, err) + expect(t, err, nil) err = app.Run([]string{"command", "bar"}) - assert.Nil(t, err) - assert.Equal(t, "foobar", s) + expect(t, err, nil) + expect(t, s, "foobar") } var commandAppTests = []struct { @@ -333,8 +331,8 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) { } app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) - assert.Equal(t, parsedOption, "my-option") - assert.Equal(t, firstArg, "my-arg") + expect(t, parsedOption, "my-option") + expect(t, firstArg, "my-arg") } func TestApp_RunAsSubcommandParseFlags(t *testing.T) { diff --git a/errors_test.go b/errors_test.go index 7c85d7b..9ed3be3 100644 --- a/errors_test.go +++ b/errors_test.go @@ -5,8 +5,6 @@ import ( "errors" "fmt" "testing" - - "github.com/stretchr/testify/assert" ) func TestHandleExitCoder_nil(t *testing.T) { @@ -119,6 +117,6 @@ func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { err := newMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2")) HandleExitCoder(err) - assert.True(t, called) - assert.Equal(t, "This the format: err1\nThis the format: err2\n", ErrWriter.(*bytes.Buffer).String()) + expect(t, called, true) + expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: err1\nThis the format: err2\n") } From a372849da6c710849700c3951f28138a203f1b13 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 4 Aug 2017 13:23:06 -0400 Subject: [PATCH 106/158] Use []*Command instead of []Command in CommandsByName --- README.md | 31 +++++++++++++++---------------- command.go | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 583a776..dbe1bf3 100644 --- a/README.md +++ b/README.md @@ -492,23 +492,22 @@ func main() { Usage: "Load configuration from `FILE`", }, }, - } - - app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - return nil + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + return nil + }, }, - }, - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - return nil + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + return nil + }, }, }, } diff --git a/command.go b/command.go index 31560c3..bf0acb2 100644 --- a/command.go +++ b/command.go @@ -56,7 +56,7 @@ type Command struct { CustomHelpTemplate string } -type CommandsByName []Command +type CommandsByName []*Command func (c CommandsByName) Len() int { return len(c) From ce3a0da1a3b9ea5f2835d5901276fea9c438b1da Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 8 Aug 2017 21:21:31 -0400 Subject: [PATCH 107/158] Ensure migrator does not mangle `cli.CommandsByName` --- cli-v1-to-v2 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 index aa83461..b815295 100755 --- a/cli-v1-to-v2 +++ b/cli-v1-to-v2 @@ -236,7 +236,11 @@ def _flag_name_stringly_repl(match): @_migrator def _commands_opaque_type(source): - return re.sub('cli\\.Commands', '[]*cli.Command', source, flags=re.UNICODE) + return _subfmt( + 'cli\\.Commands(?P[^B])', + '[]*cli.Command{suffix}', + source + ) @_migrator From 65b801c818cbf808eb337baf19a12f1cc575188a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 12 Aug 2017 22:02:54 -0400 Subject: [PATCH 108/158] Resolve remaining test errors for v1 => v2 merge --- app_test.go | 47 ++++++++++++------------------------ command.go | 4 ++++ context.go | 2 +- errors.go | 7 +++--- flag.go | 34 +++++++++++++++----------- flag_test.go | 67 +++++++++++++++++++++++++++++++++------------------- help.go | 28 ++++++++-------------- 7 files changed, 96 insertions(+), 93 deletions(-) diff --git a/app_test.go b/app_test.go index 00b12e9..796f665 100644 --- a/app_test.go +++ b/app_test.go @@ -187,17 +187,20 @@ func ExampleApp_Run_noAction() { app.Run([]string{"greet"}) // Output: // NAME: - // greet + // greet - A new cli application // // USAGE: - // [global options] command [command options] [arguments...] + // greet [global options] command [command options] [arguments...] + // + // VERSION: + // 0.0.0 // // COMMANDS: // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: - // --help, -h show help - // --version, -v print the version + // --help, -h show help (default: false) + // --version, -v print the version (default: false) } func ExampleApp_Run_subcommandNoAction() { @@ -215,19 +218,22 @@ func ExampleApp_Run_subcommandNoAction() { app.Run([]string{"greet", "describeit"}) // Output: // NAME: - // describeit - use it to see a description + // greet describeit - use it to see a description // // USAGE: - // describeit [arguments...] + // greet describeit [command options] [arguments...] // // DESCRIPTION: // This is how we describe describeit the function + // + // OPTIONS: + // --help, -h show help (default: false) } func ExampleApp_Run_shellComplete() { // set args for examples sake - os.Args = []string{"greet", "--generate-completion"} + os.Args = []string{"greet", fmt.Sprintf("--%s", genCompName())} app := &App{ Name: "greet", @@ -311,30 +317,6 @@ func TestApp_Setup_defaultsWriter(t *testing.T) { expect(t, app.Writer, os.Stdout) } -func TestApp_CommandWithArgBeforeFlags(t *testing.T) { - var parsedOption, firstArg string - - app := &App{ - Commands: []*Command{ - { - Name: "cmd", - Flags: []Flag{ - &StringFlag{Name: "option", Value: "", Usage: "some option"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - firstArg = c.Args().First() - return nil - }, - }, - }, - } - app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) - - expect(t, parsedOption, "my-option") - expect(t, firstArg, "my-arg") -} - func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context @@ -907,6 +889,7 @@ func TestApp_OrderOfOperations(t *testing.T) { app := &App{ EnableShellCompletion: true, ShellComplete: func(c *Context) { + fmt.Fprintf(os.Stderr, "---> ShellComplete(%#v)\n", c) counts.Total++ counts.ShellComplete = counts.Total }, @@ -971,7 +954,7 @@ func TestApp_OrderOfOperations(t *testing.T) { resetCounts() - _ = app.Run([]string{"command", "--generate-completion"}) + _ = app.Run([]string{"command", fmt.Sprintf("--%s", genCompName())}) expect(t, counts.ShellComplete, 1) expect(t, counts.Total, 1) diff --git a/command.go b/command.go index bf0acb2..4f0d142 100644 --- a/command.go +++ b/command.go @@ -159,6 +159,10 @@ func (c *Command) Run(ctx *Context) (err error) { } } + if c.Action == nil { + c.Action = helpSubcommand.Action + } + context.Command = c err = c.Action(context) diff --git a/context.go b/context.go index aa015b9..9802594 100644 --- a/context.go +++ b/context.go @@ -121,7 +121,7 @@ func (c *Context) Lineage() []*Context { return lineage } -// value returns the value of the flag coressponding to `name` +// value returns the value of the flag corresponding to `name` func (c *Context) value(name string) interface{} { return c.flagSet.Lookup(name).Value.(flag.Getter).Get() } diff --git a/errors.go b/errors.go index 236ef02..6259699 100644 --- a/errors.go +++ b/errors.go @@ -103,9 +103,8 @@ func HandleExitCoder(err error) { } if multiErr, ok := err.(MultiError); ok { - for _, merr := range multiErr.Errors() { - HandleExitCoder(merr) - } + code := handleMultiError(multiErr) + OsExiter(code) return } } @@ -115,7 +114,7 @@ func handleMultiError(multiErr MultiError) int { for _, merr := range multiErr.Errors() { if multiErr2, ok := merr.(MultiError); ok { code = handleMultiError(multiErr2) - } else { + } else if merr != nil { fmt.Fprintln(ErrWriter, merr) if exitErr, ok := merr.(ExitCoder); ok { code = exitErr.ExitCode() diff --git a/flag.go b/flag.go index 8e6920a..41a59af 100644 --- a/flag.go +++ b/flag.go @@ -23,8 +23,9 @@ var ( // GenerateCompletionFlag enables completion for all commands and subcommands var GenerateCompletionFlag Flag = &BoolFlag{ - Name: "generate-completion", - Hidden: true, + Name: "generate-completion", + Aliases: []string{"compgen"}, + Hidden: true, } func genCompName() string { @@ -218,7 +219,7 @@ func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as string value for flag %s: %s", envVal, f.Name, err) } } f.Value = newVal @@ -322,7 +323,7 @@ func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", envVal, f.Name, err) } } f.Value = newVal @@ -406,7 +407,7 @@ func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { for _, s := range strings.Split(envVal, ",") { s = strings.TrimSpace(s) if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", envVal, f.Name, err) } } f.Value = newVal @@ -436,11 +437,16 @@ func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error { if f.EnvVars != nil { for _, envVar := range f.EnvVars { if envVal, ok := syscall.Getenv(envVar); ok { - envValBool, err := strconv.ParseBool(envVal) - if err == nil { - f.Value = envValBool + if envVal == "" { + f.Value = false + break } + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %q as bool value for flag %s: %s", envVal, f.Name, err) + } + f.Value = envValBool break } } @@ -496,7 +502,7 @@ func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as int value for flag %s: %s", envVal, f.Name, err) } f.Value = int(envValInt) break @@ -527,7 +533,7 @@ func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseInt(envVal, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as int value for flag %s: %s", envVal, f.Name, err) } f.Value = envValInt @@ -559,7 +565,7 @@ func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseUint(envVal, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as uint value for flag %s: %s", envVal, f.Name, err) } f.Value = uint(envValInt) @@ -591,7 +597,7 @@ func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValInt, err := strconv.ParseUint(envVal, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", envVal, f.Name, err) } f.Value = uint64(envValInt) @@ -623,7 +629,7 @@ func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValDuration, err := time.ParseDuration(envVal) if err != nil { - return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as duration for flag %s: %s", envVal, f.Name, err) } f.Value = envValDuration @@ -655,7 +661,7 @@ func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error { if envVal, ok := syscall.Getenv(envVar); ok { envValFloat, err := strconv.ParseFloat(envVal, 10) if err != nil { - return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) + return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", envVal, f.Name, err) } f.Value = float64(envValFloat) diff --git a/flag_test.go b/flag_test.go index 1e44db6..2c42176 100644 --- a/flag_test.go +++ b/flag_test.go @@ -43,6 +43,24 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) { } func TestFlagsFromEnv(t *testing.T) { + newSetIntSlice := func(defaults ...int) IntSlice { + s := NewIntSlice(defaults...) + s.hasBeenSet = true + return *s + } + + newSetInt64Slice := func(defaults ...int64) Int64Slice { + s := NewInt64Slice(defaults...) + s.hasBeenSet = true + return *s + } + + newSetStringSlice := func(defaults ...string) StringSlice { + s := NewStringSlice(defaults...) + s.hasBeenSet = true + return *s + } + var flagTests = []struct { input string output interface{} @@ -52,54 +70,55 @@ func TestFlagsFromEnv(t *testing.T) { {"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, - {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, + {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value for flag debug: .*`}, {"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""}, - {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)}, + {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration for flag time: .*`}, {"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, {"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)}, + {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value for flag seconds: .*`}, {"1", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, - {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, + {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, + {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, {"1", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, - {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, + {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, + {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, - {"1,2", NewIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2,2", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)}, - {"foobar", NewIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)}, + {"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`}, + {"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`}, - {"1,2", NewInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2,2", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)}, - {"foobar", NewInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)}, + {"1,2", newSetInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value for flag seconds: .*`}, + {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value for flag seconds: .*`}, {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""}, - {"foo,bar", NewStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, + {"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, {"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)}, - {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)}, + {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value for flag seconds: .*`}, + {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value for flag seconds: .*`}, {"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)}, - {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)}, + {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value for flag seconds: .*`}, + {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value for flag seconds: .*`}, {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } - for _, test := range flagTests { + for i, test := range flagTests { clearenv() - os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVars").Slice(0, 1).String(), test.input) + envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) + os.Setenv(envVarSlice.Index(0).String(), test.input) a := App{ Flags: []Flag{test.flag}, Action: func(ctx *Context) error { if !reflect.DeepEqual(ctx.value(test.flag.Names()[0]), test.output) { - t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.value(test.flag.Names()[0])) + t.Errorf("ex:%01d expected %q to be parsed as %#v, instead was %#v", i, test.input, test.output, ctx.value(test.flag.Names()[0])) } return nil }, @@ -109,15 +128,15 @@ func TestFlagsFromEnv(t *testing.T) { if test.errRegexp != "" { if err == nil { - t.Errorf("expected error to match %s, got none", test.errRegexp) + t.Errorf("expected error to match %q, got none", test.errRegexp) } else { if matched, _ := regexp.MatchString(test.errRegexp, err.Error()); !matched { - t.Errorf("expected error to match %s, got error %s", test.errRegexp, err) + t.Errorf("expected error to match %q, got error %s", test.errRegexp, err) } } } else { if err != nil && test.errRegexp == "" { - t.Errorf("expected no error got %s", err) + t.Errorf("expected no error got %q", err) } } } diff --git a/help.go b/help.go index 6c4f213..a722564 100644 --- a/help.go +++ b/help.go @@ -318,29 +318,21 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { } func checkCompletions(c *Context) bool { - if !c.Bool(genCompName()) && !c.App.EnableShellCompletion { - return false + if c.shellComplete { + ShowCompletions(c) + return true } - if args := c.Args(); args.Present() { - name := args.First() - if cmd := c.App.Command(name); cmd != nil { - // let the command handle the completion - return false - } - } - - ShowCompletions(c) - return true + return false } func checkCommandCompletions(c *Context, name string) bool { - if !c.Bool(genCompName()) && !c.App.EnableShellCompletion { - return false + if c.shellComplete { + ShowCommandCompletions(c, name) + return true } - ShowCommandCompletions(c, name) - return true + return false } func checkInitCompletion(c *Context) (bool, error) { @@ -366,12 +358,12 @@ func bashCompletionCode(progName string) string { local cur opts base; COMPREPLY=(); cur="${COMP_WORDS[COMP_CWORD]}"; - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-completion ); + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --%s ); COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ); return 0; }; complete -F _cli_bash_autocomplete %s` - return fmt.Sprintf(template, progName) + return fmt.Sprintf(template, genCompName(), progName) } func zshCompletionCode(progName string) string { From 744fdb45b5a844910c56f66c0b8be2cc23a7f5ce Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 12 Aug 2017 22:07:03 -0400 Subject: [PATCH 109/158] Resolve conflict in completion flag check better --- help.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/help.go b/help.go index a722564..02fcf33 100644 --- a/help.go +++ b/help.go @@ -318,21 +318,29 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { } func checkCompletions(c *Context) bool { - if c.shellComplete { - ShowCompletions(c) - return true + if !c.shellComplete { + return false } - return false + if args := c.Args(); args.Present() { + name := args.First() + if cmd := c.App.Command(name); cmd != nil { + // let the command handle the completion + return false + } + } + + ShowCompletions(c) + return true } func checkCommandCompletions(c *Context, name string) bool { - if c.shellComplete { - ShowCommandCompletions(c, name) - return true + if !c.shellComplete { + return false } - return false + ShowCommandCompletions(c, name) + return true } func checkInitCompletion(c *Context) (bool, error) { From 1505b9f046c8b3d291fa772e11f1ba9caccfdb8f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 12 Aug 2017 22:11:32 -0400 Subject: [PATCH 110/158] Back out temporary flag alias whoopsie! :sweat_smile: --- flag.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/flag.go b/flag.go index 41a59af..d5ab42e 100644 --- a/flag.go +++ b/flag.go @@ -23,9 +23,8 @@ var ( // GenerateCompletionFlag enables completion for all commands and subcommands var GenerateCompletionFlag Flag = &BoolFlag{ - Name: "generate-completion", - Aliases: []string{"compgen"}, - Hidden: true, + Name: "generate-completion", + Hidden: true, } func genCompName() string { From d83210ca77b6ddf83d96d5e964e2a4247a5bacaa Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 12 Aug 2017 22:20:50 -0400 Subject: [PATCH 111/158] Attempt to better handle python(2|3) tempfile differences --- generate-flag-types | 14 +++++++++++--- runtests | 9 ++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/generate-flag-types b/generate-flag-types index 9135705..d77d470 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -68,6 +68,9 @@ import tempfile import textwrap +_PY3 = sys.version_info.major == 3 + + class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass @@ -104,9 +107,7 @@ def main(sysargs=sys.argv[:]): def _generate_flag_types(writefunc, output_go, input_json): types = json.load(input_json) - tmp = tempfile.NamedTemporaryFile( - suffix='.go', mode='w', delete=False, encoding='utf-8' - ) + tmp = _get_named_tmp_go() writefunc(tmp, types) tmp.close() @@ -119,6 +120,13 @@ def _generate_flag_types(writefunc, output_go, input_json): os.remove(tmp.name) +def _get_named_tmp_go(): + tmp_args = dict(suffix='.go', mode='w', delete=False) + if _PY3: + tmp_args['encoding'] = 'utf-8' + return tempfile.NamedTemporaryFile(**tmp_args) + + def _set_typedef_defaults(typedef): typedef.setdefault('doctail', '') typedef.setdefault('context_type', typedef['type']) diff --git a/runtests b/runtests index fe0d011..d5e6933 100755 --- a/runtests +++ b/runtests @@ -13,6 +13,7 @@ import tempfile from subprocess import check_call, check_output +_PY3 = sys.version_info.major == 3 _WINDOWS = platform.system().lower() == 'windows' _PACKAGE_NAME = os.environ.get( 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' @@ -148,9 +149,11 @@ def _go_version(): def _combine_coverprofiles(coverprofiles): - combined = tempfile.NamedTemporaryFile( - suffix='.coverprofile', delete=False - ) + tmp_args = dict(suffix='.coverprofile', delete=False) + if _PY3: + tmp_args['encoding'] = 'utf-8' + + combined = tempfile.NamedTemporaryFile(**tmp_args) combined.write(b'mode: set\n') for coverprofile in coverprofiles: From 463d4bada6238cd0e21cd2e778f4ae6163fdf1b7 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 12 Aug 2017 22:21:53 -0400 Subject: [PATCH 112/158] Explicitly set mode to 'w' when combining coverprofiles --- runtests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtests b/runtests index d5e6933..f5064a2 100755 --- a/runtests +++ b/runtests @@ -149,7 +149,7 @@ def _go_version(): def _combine_coverprofiles(coverprofiles): - tmp_args = dict(suffix='.coverprofile', delete=False) + tmp_args = dict(suffix='.coverprofile', mode='w', delete=False) if _PY3: tmp_args['encoding'] = 'utf-8' From 3164173947d2afdb2932b3c0fccd2533eaab0a4b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 12 Aug 2017 22:24:14 -0400 Subject: [PATCH 113/158] Switch some remaining encoding-related bits in test runner --- runtests | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtests b/runtests index f5064a2..d2c5ea6 100755 --- a/runtests +++ b/runtests @@ -154,13 +154,13 @@ def _combine_coverprofiles(coverprofiles): tmp_args['encoding'] = 'utf-8' combined = tempfile.NamedTemporaryFile(**tmp_args) - combined.write(b'mode: set\n') + combined.write('mode: set\n') for coverprofile in coverprofiles: with codecs.open(coverprofile, 'r', 'utf-8') as infile: for line in infile.readlines(): if not line.startswith('mode: '): - combined.write(line.encode('utf-8')) + combined.write(line) combined.flush() name = combined.name From 2da290b8729fdc4d6060a40885eef219766ed53f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 14 Aug 2017 09:33:47 -0400 Subject: [PATCH 114/158] Ensure indentation is consistent in example code plus remove usage of ancient builtin `println` :scream_cat: --- cli.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli.go b/cli.go index 13f06e9..81fc7ab 100644 --- a/cli.go +++ b/cli.go @@ -11,8 +11,8 @@ // Name: "greet", // Usage: "say a greeting", // Action: func(c *cli.Context) error { -// println("Greetings") -// return nil +// fmt.Println("Greetings") +// return nil // }, // } // From 163f24751936fa434cde04210cc00dbe1b40c6ba Mon Sep 17 00:00:00 2001 From: OneOfOne Date: Tue, 29 Aug 2017 17:26:30 +0200 Subject: [PATCH 115/158] lint + DefaultCommand --- app.go | 10 +++++++++- command.go | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index fca28c7..2638fed 100644 --- a/app.go +++ b/app.go @@ -212,7 +212,7 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { - err := a.OnUsageError(context, err, false) + err = a.OnUsageError(context, err, false) HandleExitCoder(err) return err } @@ -480,3 +480,11 @@ func (a *Author) String() string { return fmt.Sprintf("%v%v", a.Name, e) } + +// DefaultAppComplete returns an ActionFunc to run a default command if non were passed. +// Usage: `app.Action = DefaultCommand("command")` +func DefaultCommand(name string) ActionFunc { + return func(ctx *Context) error { + return ctx.App.Command(name).Run(ctx) + } +} diff --git a/command.go b/command.go index 4f0d142..05093b8 100644 --- a/command.go +++ b/command.go @@ -122,7 +122,7 @@ func (c *Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { - err := c.OnUsageError(context, err, false) + err = c.OnUsageError(context, err, false) HandleExitCoder(err) return err } From b7ab5b0c4823f439bba54438246d4be1bde49864 Mon Sep 17 00:00:00 2001 From: OneOfOne Date: Thu, 31 Aug 2017 00:26:52 +0200 Subject: [PATCH 116/158] don't add -- prefix if we manually specify one --- flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flag.go b/flag.go index d5ab42e..9e71a51 100644 --- a/flag.go +++ b/flag.go @@ -778,7 +778,7 @@ func visibleFlags(fl []Flag) []Flag { func prefixFor(name string) (prefix string) { if len(name) == 1 { prefix = "-" - } else { + } else if name[0] != '-' { prefix = "--" } From 0b665e6170b965548666bc68fe54dea7c05cf655 Mon Sep 17 00:00:00 2001 From: Vladislav Mitov Date: Tue, 26 Sep 2017 15:38:41 +0300 Subject: [PATCH 117/158] Add PathFlag --- altsrc/flag.go | 29 +++++++++++++++++++ altsrc/flag_generated.go | 26 +++++++++++++++++ altsrc/flag_test.go | 44 ++++++++++++++++++++++++++++- altsrc/input_source_context.go | 5 ++++ altsrc/json_source_context.go | 17 +++++++++++- altsrc/map_input_source.go | 6 ++++ altsrc/toml_file_loader.go | 2 +- altsrc/yaml_file_loader.go | 2 +- context_test.go | 11 ++++++++ flag-types.json | 6 ++++ flag.go | 27 ++++++++++++++++++ flag_generated.go | 45 ++++++++++++++++++++++++++++++ flag_test.go | 51 ++++++++++++++++++++++++++++++++++ 13 files changed, 267 insertions(+), 4 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index c1fb669..858e54e 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -2,6 +2,7 @@ package altsrc import ( "fmt" + "path/filepath" "strconv" "syscall" @@ -160,6 +161,34 @@ func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSource return nil } +// ApplyInputSourceValue applies a Path value to the flagSet if required +func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { + value, err := isc.String(f.PathFlag.Name) + if err != nil { + return err + } + if value != "" { + for _, name := range f.Names() { + + if !filepath.IsAbs(value) && isc.Source() != "" { + basePathAbs, err := filepath.Abs(isc.Source()) + if err != nil { + return err + } + + value = filepath.Join(filepath.Dir(basePathAbs), value) + } + + f.set.Set(name, value) + } + } + } + } + return nil +} + // ApplyInputSourceValue applies a int value to the flagSet if required func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { diff --git a/altsrc/flag_generated.go b/altsrc/flag_generated.go index ee2231c..87c7c5d 100644 --- a/altsrc/flag_generated.go +++ b/altsrc/flag_generated.go @@ -268,6 +268,32 @@ func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error { return f.StringFlag.ApplyWithError(set) } +// PathFlag is the flag type that wraps cli.PathFlag to allow +// for other values to be specified +type PathFlag struct { + *cli.PathFlag + set *flag.FlagSet +} + +// NewPathFlag creates a new PathFlag +func NewPathFlag(fl *cli.PathFlag) *PathFlag { + return &PathFlag{PathFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped PathFlag.Apply +func (f *PathFlag) Apply(set *flag.FlagSet) { + f.set = set + f.PathFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped PathFlag.ApplyWithError +func (f *PathFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.PathFlag.ApplyWithError(set) +} + // StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow // for other values to be specified type StringSliceFlag struct { diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index a4d1d59..087e607 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "os" + "runtime" "strings" "testing" "time" @@ -20,6 +21,7 @@ type testApplyInputSource struct { ContextValue flag.Value EnvVarValue string EnvVarName string + SourcePath string MapValue interface{} } @@ -178,6 +180,43 @@ func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { }) expect(t, "goodbye", c.String("test")) } +func TestPathApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewPathFlag(&cli.PathFlag{Name: "test"}), + FlagName: "test", + MapValue: "hello", + SourcePath: "/path/to/source/file", + }) + + expected := "/path/to/source/hello" + if runtime.GOOS == "windows" { + expected = `C:\path\to\source\hello` + } + expect(t, expected, c.String("test")) +} + +func TestPathApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewPathFlag(&cli.PathFlag{Name: "test"}), + FlagName: "test", + MapValue: "hello", + ContextValueString: "goodbye", + SourcePath: "/path/to/source/file", + }) + expect(t, "goodbye", c.String("test")) +} + +func TestPathApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewPathFlag(&cli.PathFlag{Name: "test", EnvVars: []string{"TEST"}}), + FlagName: "test", + MapValue: "hello", + EnvVarName: "TEST", + EnvVarValue: "goodbye", + SourcePath: "/path/to/source/file", + }) + expect(t, "goodbye", c.String("test")) +} func TestIntApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ @@ -270,7 +309,10 @@ func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { } func runTest(t *testing.T, test testApplyInputSource) *cli.Context { - inputSource := &MapInputSource{valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}} + inputSource := &MapInputSource{ + file: test.SourcePath, + valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}, + } set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError) c := cli.NewContext(nil, set, nil) if test.EnvVarName != "" && test.EnvVarValue != "" { diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index c45ba5c..bb0afdb 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -8,7 +8,12 @@ import ( // InputSourceContext is an interface used to allow // other input sources to be implemented as needed. +// +// Source returns an identifier for the input source. In case of file source +// it should return path to the file. type InputSourceContext interface { + Source() string + Int(name string) (int, error) Duration(name string) (time.Duration, error) Float64(name string) (float64, error) diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index a197d87..34c7eb9 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -29,7 +29,13 @@ func NewJSONSourceFromFile(f string) (InputSourceContext, error) { if err != nil { return nil, err } - return NewJSONSource(data) + s, err := newJSONSource(data) + if err != nil { + return nil, err + } + + s.file = f + return s, nil } // NewJSONSourceFromReader returns an InputSourceContext suitable for @@ -45,6 +51,10 @@ func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) { // NewJSONSource returns an InputSourceContext suitable for retrieving // config variables from raw JSON data. func NewJSONSource(data []byte) (InputSourceContext, error) { + return newJSONSource(data) +} + +func newJSONSource(data []byte) (*jsonSource, error) { var deserialized map[string]interface{} if err := json.Unmarshal(data, &deserialized); err != nil { return nil, err @@ -52,6 +62,10 @@ func NewJSONSource(data []byte) (InputSourceContext, error) { return &jsonSource{deserialized: deserialized}, nil } +func (x *jsonSource) Source() string { + return x.file +} + func (x *jsonSource) Int(name string) (int, error) { i, err := x.getValue(name) if err != nil { @@ -198,5 +212,6 @@ func jsonGetValue(key string, m map[string]interface{}) (interface{}, error) { } type jsonSource struct { + file string deserialized map[string]interface{} } diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 66a29b6..37709c7 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -12,6 +12,7 @@ import ( // MapInputSource implements InputSourceContext to return // data from the map that is loaded. type MapInputSource struct { + file string valueMap map[interface{}]interface{} } @@ -39,6 +40,11 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool return nil, false } +// Source returns the path of the source file +func (fsm *MapInputSource) Source() string { + return fsm.file +} + // Int returns an int from the map if it exists otherwise returns 0 func (fsm *MapInputSource) Int(name string) (int, error) { otherGenericValue, exists := fsm.valueMap[name] diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 423e5ff..1cb2d7b 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -86,7 +86,7 @@ func NewTomlSourceFromFile(file string) (InputSourceContext, error) { if err := readCommandToml(tsc.FilePath, &results); err != nil { return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error()) } - return &MapInputSource{valueMap: results.Map}, nil + return &MapInputSource{file: file, valueMap: results.Map}, nil } // NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context. diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 4c0060b..37c8d9c 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -32,7 +32,7 @@ func NewYamlSourceFromFile(file string) (InputSourceContext, error) { return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error()) } - return &MapInputSource{valueMap: results}, nil + return &MapInputSource{file: file, valueMap: results}, nil } // NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. diff --git a/context_test.go b/context_test.go index edfbaee..0509488 100644 --- a/context_test.go +++ b/context_test.go @@ -109,6 +109,17 @@ func TestContext_String(t *testing.T) { expect(t, c.String("top-flag"), "hai veld") } +func TestContext_Path(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.String("path", "path/to/file", "path to file") + parentSet := flag.NewFlagSet("test", 0) + parentSet.String("top-path", "path/to/top/file", "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) + expect(t, c.Path("path"), "path/to/file") + expect(t, c.Path("top-path"), "path/to/top/file") +} + func TestContext_Bool(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") diff --git a/flag-types.json b/flag-types.json index f609e07..bd5ec3f 100644 --- a/flag-types.json +++ b/flag-types.json @@ -68,6 +68,12 @@ "context_default": "\"\"", "parser": "f.Value.String(), error(nil)" }, + { + "name": "Path", + "type": "string", + "context_default": "\"\"", + "parser": "f.Value.String(), error(nil)" + }, { "name": "StringSlice", "type": "*StringSlice", diff --git a/flag.go b/flag.go index d5ab42e..364362c 100644 --- a/flag.go +++ b/flag.go @@ -488,6 +488,33 @@ func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error { return nil } +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f *PathFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f *PathFlag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { + if envVal, ok := syscall.Getenv(envVar); ok { + f.Value = envVal + break + } + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.String(name, f.Value, f.Usage) + } + return nil +} + // Apply populates the flag given the flag set and environment // Ignores errors func (f *IntFlag) Apply(set *flag.FlagSet) { diff --git a/flag_generated.go b/flag_generated.go index 187a6ca..a958150 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -450,6 +450,51 @@ func lookupString(name string, set *flag.FlagSet) string { return "" } +// PathFlag is a flag with type string +type PathFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + Hidden bool + Value string + DefaultText string + + Destination *string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *PathFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *PathFlag) Names() []string { + return flagNames(f) +} + +// Path looks up the value of a local PathFlag, returns +// "" if not found +func (c *Context) Path(name string) string { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupPath(name, fs) + } + return "" +} + +func lookupPath(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + parsed, err := f.Value.String(), error(nil) + if err != nil { + return "" + } + return parsed + } + return "" +} + // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { Name string diff --git a/flag_test.go b/flag_test.go index 2c42176..6ed3a76 100644 --- a/flag_test.go +++ b/flag_test.go @@ -96,6 +96,7 @@ func TestFlagsFromEnv(t *testing.T) { {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value for flag seconds: .*`}, {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""}, + {"path", "path", &PathFlag{Name: "path", EnvVars: []string{"PATH"}}, ""}, {"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, @@ -206,6 +207,56 @@ func TestStringFlagApply_SetsAllNames(t *testing.T) { expect(t, v, "YUUUU") } +var pathFlagTests = []struct { + name string + aliases []string + usage string + value string + expected string +}{ + {"f", nil, "", "", "-f value\t"}, + {"f", nil, "Path is the `path` of file", "/path/to/file", "-f path\tPath is the path of file (default: \"/path/to/file\")"}, +} + +func TestPathFlagHelpOutput(t *testing.T) { + for _, test := range pathFlagTests { + flag := &PathFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestPathFlagWithEnvVarHelpOutput(t *testing.T) { + clearenv() + os.Setenv("APP_PATH", "/path/to/file") + for _, test := range pathFlagTests { + flag := &PathFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_PATH"}} + output := flag.String() + + expectedSuffix := " [$APP_PATH]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_PATH%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +func TestPathFlagApply_SetsAllNames(t *testing.T) { + v := "mmm" + fl := PathFlag{Name: "path", Aliases: []string{"p", "PATH"}, Destination: &v} + set := flag.NewFlagSet("test", 0) + fl.Apply(set) + + err := set.Parse([]string{"--path", "/path/to/file/path", "-p", "/path/to/file/p", "--PATH", "/path/to/file/PATH"}) + expect(t, err, nil) + expect(t, v, "/path/to/file/PATH") +} + var stringSliceFlagTests = []struct { name string aliases []string From 01b6b9192c126b6b7659e26100afcd85ba3fe5dc Mon Sep 17 00:00:00 2001 From: Chris Branch Date: Thu, 6 Jul 2017 13:05:23 +0100 Subject: [PATCH 118/158] altsrc: Parse durations from strings --- altsrc/map_input_source.go | 24 ++++++++++++++---------- altsrc/map_input_source_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 altsrc/map_input_source_test.go diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 37709c7..41221b1 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -71,24 +71,28 @@ func (fsm *MapInputSource) Int(name string) (int, error) { func (fsm *MapInputSource) Duration(name string) (time.Duration, error) { otherGenericValue, exists := fsm.valueMap[name] if exists { - otherValue, isType := otherGenericValue.(time.Duration) - if !isType { - return 0, incorrectTypeForFlagError(name, "duration", otherGenericValue) - } - return otherValue, nil + return castDuration(name, otherGenericValue) } nestedGenericValue, exists := nestedVal(name, fsm.valueMap) if exists { - otherValue, isType := nestedGenericValue.(time.Duration) - if !isType { - return 0, incorrectTypeForFlagError(name, "duration", nestedGenericValue) - } - return otherValue, nil + return castDuration(name, nestedGenericValue) } return 0, nil } +func castDuration(name string, value interface{}) (time.Duration, error) { + if otherValue, isType := value.(time.Duration); isType { + return otherValue, nil + } + otherStringValue, isType := value.(string) + parsedValue, err := time.ParseDuration(otherStringValue) + if !isType || err != nil { + return 0, incorrectTypeForFlagError(name, "duration", value) + } + return parsedValue, nil +} + // Float64 returns an float64 from the map if it exists otherwise returns 0 func (fsm *MapInputSource) Float64(name string) (float64, error) { otherGenericValue, exists := fsm.valueMap[name] diff --git a/altsrc/map_input_source_test.go b/altsrc/map_input_source_test.go new file mode 100644 index 0000000..a921d04 --- /dev/null +++ b/altsrc/map_input_source_test.go @@ -0,0 +1,25 @@ +package altsrc + +import ( + "testing" + "time" +) + +func TestMapDuration(t *testing.T) { + inputSource := &MapInputSource{ + file: "test", + valueMap: map[interface{}]interface{}{ + "duration_of_duration_type": time.Minute, + "duration_of_string_type": "1m", + "duration_of_int_type": 1000, + }, + } + d, err := inputSource.Duration("duration_of_duration_type") + expect(t, time.Minute, d) + expect(t, nil, err) + d, err = inputSource.Duration("duration_of_string_type") + expect(t, time.Minute, d) + expect(t, nil, err) + d, err = inputSource.Duration("duration_of_int_type") + refute(t, nil, err) +} From 10c9734c5e7553418edf1cd20685d2e660f366f1 Mon Sep 17 00:00:00 2001 From: OneOfOne Date: Tue, 23 Jan 2018 19:45:38 +0200 Subject: [PATCH 119/158] notes --- app.go | 2 +- flag.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 2638fed..4c073c5 100644 --- a/app.go +++ b/app.go @@ -482,7 +482,7 @@ func (a *Author) String() string { } // DefaultAppComplete returns an ActionFunc to run a default command if non were passed. -// Usage: `app.Action = DefaultCommand("command")` +// Usage: `app.Action = cli.DefaultCommand("command")` func DefaultCommand(name string) ActionFunc { return func(ctx *Context) error { return ctx.App.Command(name).Run(ctx) diff --git a/flag.go b/flag.go index 9e71a51..d5ab42e 100644 --- a/flag.go +++ b/flag.go @@ -778,7 +778,7 @@ func visibleFlags(fl []Flag) []Flag { func prefixFor(name string) (prefix string) { if len(name) == 1 { prefix = "-" - } else if name[0] != '-' { + } else { prefix = "--" } From a35a28c15ec60b1c94b9153ec56e0faa0d96cf60 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sun, 28 Jan 2018 09:58:38 -0800 Subject: [PATCH 120/158] Update pip to pip2 for new OSX builds See https://github.com/travis-ci/travis-ci/issues/8829 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e395617..dea1790 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ cache: before_script: - if [[ $(uname) == Darwin ]]; then - sudo pip install flake8; + sudo pip2 install flake8; else pip install --user flake8; fi From 820040ec284f80533903c379a9784fc9ec8b7fe3 Mon Sep 17 00:00:00 2001 From: marwan-at-work Date: Mon, 5 Aug 2019 14:04:37 -0400 Subject: [PATCH 121/158] add context.Context to cli.Context --- context.go | 12 ++++++++++++ go.mod | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 go.mod diff --git a/context.go b/context.go index 9802594..0109f45 100644 --- a/context.go +++ b/context.go @@ -1,11 +1,14 @@ package cli import ( + "context" "errors" "flag" "os" + "os/signal" "reflect" "strings" + "syscall" ) // Context is a type that is passed through to @@ -13,6 +16,7 @@ import ( // can be used to retrieve context-specific args and // parsed command-line options. type Context struct { + context.Context App *App Command *Command shellComplete bool @@ -24,6 +28,14 @@ type Context struct { // NewContext creates a new context. For use in when invoking an App or Command action. func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { c := &Context{App: app, flagSet: set, parentContext: parentCtx} + ctx, cancel := context.WithCancel(context.Background()) + go func() { + defer cancel() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs + }() + c.Context = ctx if parentCtx != nil { c.shellComplete = parentCtx.shellComplete diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7ee92dd --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gopkg.in/urfave/fli.v2 + +go 1.12 From 006fad30ca33bb1882b6de91d0df38409cfd67ed Mon Sep 17 00:00:00 2001 From: marwan-at-work Date: Mon, 5 Aug 2019 15:03:26 -0400 Subject: [PATCH 122/158] fix go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7ee92dd..c21d59c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module gopkg.in/urfave/fli.v2 +module gopkg.in/urfave/cli.v2 go 1.12 From cee005ee62349b316defe3cb9c72099f9b118c52 Mon Sep 17 00:00:00 2001 From: marwan-at-work Date: Tue, 6 Aug 2019 12:55:29 -0400 Subject: [PATCH 123/158] only create context once --- context.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index 0109f45..427f737 100644 --- a/context.go +++ b/context.go @@ -28,17 +28,21 @@ type Context struct { // NewContext creates a new context. For use in when invoking an App or Command action. func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { c := &Context{App: app, flagSet: set, parentContext: parentCtx} - ctx, cancel := context.WithCancel(context.Background()) - go func() { - defer cancel() - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - <-sigs - }() - c.Context = ctx - if parentCtx != nil { + if parentCtx.Context != nil { + parentCtx.Context = context.Background() + } + c.Context = parentCtx.Context c.shellComplete = parentCtx.shellComplete + } else { + ctx, cancel := context.WithCancel(context.Background()) + go func() { + defer cancel() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs + }() + c.Context = ctx } return c From 1f7d1684b85350633d7b049d7b709f30412a221b Mon Sep 17 00:00:00 2001 From: marwan-at-work Date: Tue, 6 Aug 2019 13:57:00 -0400 Subject: [PATCH 124/158] fix nil check --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 427f737..1e5414e 100644 --- a/context.go +++ b/context.go @@ -29,7 +29,7 @@ type Context struct { func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { c := &Context{App: app, flagSet: set, parentContext: parentCtx} if parentCtx != nil { - if parentCtx.Context != nil { + if parentCtx.Context == nil { parentCtx.Context = context.Background() } c.Context = parentCtx.Context From 98e64f4507588a67659d0f9d9e762c0ac4496e0e Mon Sep 17 00:00:00 2001 From: marwan-at-work Date: Tue, 6 Aug 2019 14:04:51 -0400 Subject: [PATCH 125/158] add propagation tests --- app_test.go | 5 +---- context_test.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app_test.go b/app_test.go index 796f665..dfb3a88 100644 --- a/app_test.go +++ b/app_test.go @@ -236,7 +236,7 @@ func ExampleApp_Run_shellComplete() { os.Args = []string{"greet", fmt.Sprintf("--%s", genCompName())} app := &App{ - Name: "greet", + Name: "greet", EnableShellCompletion: true, Commands: []*Command{ { @@ -503,7 +503,6 @@ func TestApp_Float64Flag(t *testing.T) { } func TestApp_ParseSliceFlags(t *testing.T) { - var parsedOption, firstArg string var parsedIntSlice []int var parsedStringSlice []string @@ -518,8 +517,6 @@ func TestApp_ParseSliceFlags(t *testing.T) { Action: func(c *Context) error { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") - parsedOption = c.String("option") - firstArg = c.Args().First() return nil }, }, diff --git a/context_test.go b/context_test.go index 0509488..948af57 100644 --- a/context_test.go +++ b/context_test.go @@ -1,6 +1,7 @@ package cli import ( + "context" "flag" "sort" "testing" @@ -262,3 +263,24 @@ func TestContext_lookupFlagSet(t *testing.T) { t.Fail() } } + +// TestContextPropagation tests that +// *cli.Context always has a valid +// context.Context +func TestContextPropagation(t *testing.T) { + ctx := NewContext(nil, nil, nil) + if ctx.Context == nil { + t.Fatal("expected a non nil context when no parent is present") + } + parent := NewContext(nil, nil, nil) + parent.Context = context.WithValue(context.Background(), "key", "val") + ctx = NewContext(nil, nil, parent) + val := ctx.Value("key") + if val == nil { + t.Fatal("expected a parent context to be inherited but got nil") + } + valstr, _ := val.(string) + if valstr != "val" { + t.Fatalf("expected the context value to be %q but got %q", "val", valstr) + } +} From 2fb0dc188f59d42961fc3bf3e7a99a06f56ec162 Mon Sep 17 00:00:00 2001 From: marwan-at-work Date: Tue, 6 Aug 2019 14:14:12 -0400 Subject: [PATCH 126/158] ignore compile errors --- app_test.go | 5 +++++ go.mod | 5 +++++ go.sum | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 go.sum diff --git a/app_test.go b/app_test.go index dfb3a88..84631fd 100644 --- a/app_test.go +++ b/app_test.go @@ -503,6 +503,7 @@ func TestApp_Float64Flag(t *testing.T) { } func TestApp_ParseSliceFlags(t *testing.T) { + var parsedOption, firstArg string var parsedIntSlice []int var parsedStringSlice []string @@ -517,11 +518,15 @@ func TestApp_ParseSliceFlags(t *testing.T) { Action: func(c *Context) error { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") + parsedOption = c.String("option") + firstArg = c.Args().First() return nil }, }, }, } + var _ = parsedOption + var _ = firstArg app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"}) diff --git a/go.mod b/go.mod index c21d59c..b8f4fa3 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,8 @@ module gopkg.in/urfave/cli.v2 go 1.12 + +require ( + github.com/BurntSushi/toml v0.3.1 + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9a73308 --- /dev/null +++ b/go.sum @@ -0,0 +1,5 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From c8863d0b304ec3a200d6a3a0e12e3e59fcbfdd94 Mon Sep 17 00:00:00 2001 From: marwan-at-work Date: Tue, 6 Aug 2019 14:20:21 -0400 Subject: [PATCH 127/158] PR updates --- context.go | 6 ++---- context_test.go | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index 1e5414e..f1a01b4 100644 --- a/context.go +++ b/context.go @@ -29,12 +29,10 @@ type Context struct { func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { c := &Context{App: app, flagSet: set, parentContext: parentCtx} if parentCtx != nil { - if parentCtx.Context == nil { - parentCtx.Context = context.Background() - } c.Context = parentCtx.Context c.shellComplete = parentCtx.shellComplete - } else { + } + if c.Context == nil { ctx, cancel := context.WithCancel(context.Background()) go func() { defer cancel() diff --git a/context_test.go b/context_test.go index 948af57..7333ae0 100644 --- a/context_test.go +++ b/context_test.go @@ -264,17 +264,20 @@ func TestContext_lookupFlagSet(t *testing.T) { } } -// TestContextPropagation tests that -// *cli.Context always has a valid -// context.Context -func TestContextPropagation(t *testing.T) { +func TestNonNilContext(t *testing.T) { ctx := NewContext(nil, nil, nil) if ctx.Context == nil { t.Fatal("expected a non nil context when no parent is present") } +} + +// TestContextPropagation tests that +// *cli.Context always has a valid +// context.Context +func TestContextPropagation(t *testing.T) { parent := NewContext(nil, nil, nil) parent.Context = context.WithValue(context.Background(), "key", "val") - ctx = NewContext(nil, nil, parent) + ctx := NewContext(nil, nil, parent) val := ctx.Value("key") if val == nil { t.Fatal("expected a parent context to be inherited but got nil") @@ -283,4 +286,10 @@ func TestContextPropagation(t *testing.T) { if valstr != "val" { t.Fatalf("expected the context value to be %q but got %q", "val", valstr) } + parent = NewContext(nil, nil, nil) + parent.Context = nil + ctx = NewContext(nil, nil, parent) + if ctx.Context == nil { + t.Fatal("expected context to not be nil even if the parent's context is nil") + } } From be6dbba47b3a4455f5c8022f6fdecd14f6dd310b Mon Sep 17 00:00:00 2001 From: marwan-at-work Date: Tue, 6 Aug 2019 14:21:13 -0400 Subject: [PATCH 128/158] rm modules --- go.mod | 8 -------- go.sum | 5 ----- 2 files changed, 13 deletions(-) delete mode 100644 go.mod delete mode 100644 go.sum diff --git a/go.mod b/go.mod deleted file mode 100644 index b8f4fa3..0000000 --- a/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module gopkg.in/urfave/cli.v2 - -go 1.12 - -require ( - github.com/BurntSushi/toml v0.3.1 - gopkg.in/yaml.v2 v2.2.2 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 9a73308..0000000 --- a/go.sum +++ /dev/null @@ -1,5 +0,0 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 59f572e95336ec4d3cb27f6d4fb9f62fbe8cf21e Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Tue, 6 Aug 2019 21:09:50 +0100 Subject: [PATCH 129/158] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dea1790..58ed292 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: go sudo: false dist: trusty osx_image: xcode8.3 -go: 1.8.x +go: 1.11.x os: - linux From c75fee9224817c00f1e2630c31aa5686a70f5fb8 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Fri, 13 Sep 2019 04:56:14 +0530 Subject: [PATCH 130/158] Merge master @1.22.1 with v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit c71fbcefd21552b70cd625b2c54466006e258ad7 Merge: 61f3ae3 ef47250 Author: Ajitem Sahasrabuddhe Date: Thu Sep 12 05:35:50 2019 +0530 Merge pull request #887 from urfave/asahasrabuddhe-patch-1 Release 1.22.1 commit ef47250cda5ff52a313118c01ad6b0c5b4877a70 Merge: 71eaf37 61f3ae3 Author: Ajitem Sahasrabuddhe Date: Thu Sep 12 05:19:58 2019 +0530 Merge branch 'master' into asahasrabuddhe-patch-1 commit 61f3ae353bf455e3522aff0d5a28be9278bba7f2 Merge: 388c2dd fa858dc Author: Ajitem Sahasrabuddhe Date: Thu Sep 12 05:19:33 2019 +0530 Merge pull request #890 from urfave/issue-878 Fix #878 commit fa858dcc260fb07c25aab13650d9fa0e64f851c7 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 15:10:14 2019 +0530 Ensure flag is not blank commit f8bb66ae7d679973cf9b3f6f8c3dc6933404a31a Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:42:38 2019 +0530 Fix Typo commit 056aef13fe0b0e51403036ca5527854f50a1f3cd Merge: c6ee3b4 82a84fc Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:37:06 2019 +0530 Merge branch 'issue-878' of https://github.com/urfave/cli into issue-878 commit c6ee3b4904ed76d34f277c315c2097ae7b22d38f Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:34:41 2019 +0530 Use iterative logic to determine missing flag commit 82a84fc187c23434a5c2e1398a7fcfbc9c51df94 Merge: 1547ac2 388c2dd Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:17:11 2019 +0530 Merge branch 'master' into issue-878 commit 1547ac2f6a3d3d39fe4d49570c0d1c2401a8f20e Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:15:20 2019 +0530 Modify variable names commit 388c2dd0f4ffaa8541e371d49c8413870a04d9fe Merge: e19126a 6d888d6 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:13:40 2019 +0530 Merge pull request #891 from saschagrunert/fish-hidden Don't generate fish completion for hidden commands commit 71eaf37e337d5daea12c6a137113c71056151530 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:09:50 2019 +0530 Update CHANGELOG.md commit 6d888d693d81e13806356854c57574334d9ef3b9 Merge: bac5bde e19126a Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:04:44 2019 +0530 Merge branch 'master' into fish-hidden commit e19126a8198a7c076339e69ed4d372567750dd24 Merge: b207e20 35eb598 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:01:44 2019 +0530 Merge pull request #883 from urfave/remove-flag-generation Remove flag generation commit bac5bde38c7725990645cf9b2bf2c824594f3963 Author: Sascha Grunert Date: Wed Sep 11 09:06:02 2019 +0200 Don't generate fish completion for hidden commands Added the missing test case as well. Signed-off-by: Sascha Grunert commit 36cdaa9964df03e2b8f8d2147a99497536851ad9 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 10:34:00 2019 +0530 Update CHANGELOG.md commit cbb9e015b89225aa090c41085bdb0933f6290d96 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 09:21:45 2019 +0530 Improve Code and Add Test Case commit 7d6a604106e44732edc0a76f4a4800c8c27ddfbe Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 08:59:51 2019 +0530 Fix #878 commit be37c2cbda3ba6a37fa8f7a0df960de844afc843 Merge: 0aee120 b207e20 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 08:27:45 2019 +0530 Merge branch 'master' into asahasrabuddhe-patch-1 commit 35eb598d43c3ab639e3c0ccc72e37f294e5b5828 Merge: 8575558 b207e20 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 05:39:35 2019 +0530 Merge branch 'master' into remove-flag-generation commit 0aee120c32003fff6f320c2a00a41d03285fdde0 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 05:37:41 2019 +0530 Update CHANGELOG.md commit 5c019b10ca37c1a0b74b637d3b0aa25dfeb0110f Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 05:33:46 2019 +0530 Update CHANGELOG.md commit b207e20873f5805e7c9cc544b3418a0a3ec63e09 Merge: 249cb33 487be14 Author: Audrius Butkevicius Date: Tue Sep 10 21:28:35 2019 +0100 Merge pull request #889 from crosbymichael/hidden-man Don't output hidden commands for man pages commit 487be14dceb185a3321cacd9bf302f5e811f5ee8 Author: Michael Crosby Date: Tue Sep 10 13:49:11 2019 -0400 Don't output hidden commands for man pages Signed-off-by: Michael Crosby commit 85755588ac06f74702bf7d62802dab0655881182 Merge: 024692c 249cb33 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 15:04:12 2019 +0530 Merge branch 'master' into remove-flag-generation commit 249cb3339254361b1a884733a98d4202e2838b9b Merge: bfe2e92 abfb13b Author: Audrius Butkevicius Date: Tue Sep 10 08:28:09 2019 +0100 Merge pull request #885 from urfave/go-modules-support Go modules support commit abfb13b8542fbe3d542d46543ab0d3be6aacb4e5 Merge: 534d60b bfe2e92 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:56:41 2019 +0530 Merge branch 'master' into go-modules-support commit 054fbefec36cad7425dc6f4cfb6d2963c2710751 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:42:34 2019 +0530 Update CHANGELOG.md commit 534d60bb9bb0476141540ec77c5a3d51e176d162 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:40:45 2019 +0530 Bump minimum supported version of Go to 1.11 commit 024692c172f7000fe2431c3280a1e4b724b15945 Merge: 4a9e440 bfe2e92 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:33:21 2019 +0530 Merge branch 'master' into remove-flag-generation commit bfe2e925cfb6d44b40ad3a779165ea7e8aff9212 Merge: 3eca109 238c80f Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:24:10 2019 +0530 Merge pull request #882 from urfave/lynncyrin-patch-1 Release 1.22.0 commit 426e21c150d9a33e4d8c13c2a13c5234e85f3a0e Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 13:15:47 2019 +0530 Update .travis.yml Set GOPROXY in Travis environment commit 39bd6176649871817d1966b6b91f042be4b62fe9 Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 08:37:16 2019 +0530 Cleanup after before_script to avoid git diff errors remove windows build commit edbf66c25cf83541faee77d0064fdb5ac35a51b1 Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 08:23:04 2019 +0530 Update gfmrun import command to suite Go Modules pattern Fix test command typo in travis script commit afd0ecbbf2fbda2f9459046228ccc9e8d2693258 Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 08:19:06 2019 +0530 Add support for Go 1.13 Drop support for Go 1.11 Use md2man v2 to avoid dependency issues when building with Go Modules Enabled Update TravisCI build environment images (trusty was deprecated) Add optional Windows build commit 4a9e440503f4113a351322da93f8630df14dfcaa Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 08:12:09 2019 +0530 Fix AppVeyor build commit 5c81af9f10b974cecbec6e20e4976574e86fc78b Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 07:47:18 2019 +0530 Remove generate script from travis flow Remove unused dependencies from travis script commit b6c5d17a835d17e8dd8f2e34c02e96af7f43c9e4 Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 07:44:07 2019 +0530 Remove Flag Generation Remove Legacy Python Scripts commit 238c80f9b5e54a834f25423fd0887f68b9737cbb Author: Lynn Cyrin (they/them) Date: Sat Sep 7 20:44:48 2019 -0700 Update CHANGELOG.md commit 980742b7cc56e8f7b14b4668fb8ddcf89025c9ea Author: Lynn Cyrin (they/them) Date: Sat Sep 7 20:43:56 2019 -0700 typo commit 890d49ca7a15a3fb7f44d33e5cdb499f0e5c2331 Author: Lynn Cyrin (they/them) Date: Sat Sep 7 20:41:44 2019 -0700 Release 1.22.0 - adds the changelog for 1.22.0 - updates the changelog for 1.21.0. some PRs were missed, as was mentioned here https://github.com/urfave/cli/pull/829#issuecomment-517968795 - closes https://github.com/urfave/cli/issues/867 commit 3eca1090a37a65fea5b84bdbd2c61f8104211b74 Merge: 38a6c56 4bbff84 Author: Audrius Butkevicius Date: Fri Aug 30 15:53:55 2019 +0100 Merge pull request #879 from saschagrunert/escape-single-quotes Escape single quotes in fish shell completion commit 4bbff841690954ed86c147147151c0144dcf1765 Author: Sascha Grunert Date: Thu Aug 29 14:45:32 2019 +0200 Escape single quotes in fish shell completion Single quotes can break the generated fish shell completion and should be escaped correctly. Signed-off-by: Sascha Grunert commit 38a6c560b3b8ac6d47e031a44b92e0531824c7e5 Merge: fa6797b 687f721 Author: Audrius Butkevicius Date: Thu Aug 29 20:52:07 2019 +0100 Merge pull request #857 from saschagrunert/takes-file-fish Add `TakesFile` to fish shell completion commit 687f721eaa40859950820b37b9ad1fcd85b7da9f Author: Sascha Grunert Date: Mon Aug 26 10:07:50 2019 +0200 Update function alignment Signed-off-by: Sascha Grunert commit 0c01922a12c501867cad200bc4b36a25f9a073e0 Author: Sascha Grunert Date: Mon Aug 26 08:46:55 2019 +0200 Add type switch Signed-off-by: Sascha Grunert commit 38d0ac629677a7fbf08a52e17fec73894bb31263 Author: Sascha Grunert Date: Sun Aug 25 17:50:18 2019 +0200 Removed GetTakesFile and stick to type assertions Signed-off-by: Sascha Grunert commit a1cf7f44b6cf65c0c10c282c71524cc37442b798 Author: Sascha Grunert Date: Mon Aug 12 09:42:12 2019 +0200 Add `TakesFile` to fish shell completion The new `TakesFile` flag will be now consumed by the fish shell completion generator. Signed-off-by: Sascha Grunert commit fa6797beefc5727035323623aed515e4dfd3ccdf Merge: 2344c98 82eb0d7 Author: Lynn Cyrin (they/them) Date: Sat Aug 24 18:58:52 2019 -0700 Merge pull request #876 from urfave/lynncyrin-patch-1 Bump go version to 1.10 in readme commit 82eb0d70cbcf89de5e71965fc9ededbb41cdbd96 Merge: edd8cb2 2344c98 Author: Audrius Butkevicius Date: Sun Aug 25 01:04:33 2019 +0100 Merge branch 'master' into lynncyrin-patch-1 commit 2344c98f678ac236c6bf952fe724bc5b0a6bd69b Merge: 55de011 68ee2bc Author: Audrius Butkevicius Date: Sun Aug 25 01:04:17 2019 +0100 Merge pull request #860 from saschagrunert/takes-file-not-all Update `TakesFile` flag to apply only to supported flags commit edd8cb2068b6501d6b631299038cb42194926a8e Author: Lynn Cyrin (they/them) Date: Sat Aug 24 14:44:56 2019 -0700 Bump go version to 1.10 in readme Closes https://github.com/urfave/cli/issues/875 commit 68ee2bc4af27ae14cedbfb881384b0900a0ed3a9 Merge: 959d9ec 55de011 Author: Lynn Cyrin (they/them) Date: Sat Aug 24 14:34:15 2019 -0700 Merge branch 'master' into takes-file-not-all commit 55de011cf89b3d78842e3b3e2cf92f9d157fa399 Merge: 392c1de d3edef8 Author: Audrius Butkevicius Date: Sat Aug 24 11:55:28 2019 +0100 Merge pull request #873 from urfave/show-test-failures build: show failures when running tests commit d3edef887a2fc39830216cd41b16955ef60d0d3c Author: Audrius Butkevicius Date: Sat Aug 24 11:34:03 2019 +0100 Update build.go commit c2d1a132082e3b02a219e61eeef49da364d2c315 Author: Lynn Cyrin Date: Sat Aug 24 03:05:45 2019 -0700 Revert "check length" This reverts commit 1095838cca9d596e55cff88bcb35b67cf83bf4e4. commit 959d9ec36b7848004fd2e85f07b810266d65c8d2 Merge: 3681b05 392c1de Author: Sascha Grunert Date: Sat Aug 24 11:23:51 2019 +0200 Merge branch 'master' into takes-file-not-all commit 7d62a9d0547cbab68e78c2c92a79db97ee61f115 Merge: 1095838 392c1de Author: Lynn Cyrin (they/them) Date: Sat Aug 24 00:50:42 2019 -0700 Merge branch 'master' into show-test-failures commit 1095838cca9d596e55cff88bcb35b67cf83bf4e4 Author: Lynn Cyrin Date: Sat Aug 24 00:49:29 2019 -0700 check length commit 29ad6ee6ad7e02dbec3334e8843bb6711c011b55 Author: [[ BOT ]] Lynn Cyrin Date: Fri Aug 23 20:09:08 2019 -0700 DRY commit 392c1de1a2b3f8bc2ca95c2389dd05469d347b14 Merge: 23c8303 487c723 Author: Audrius Butkevicius Date: Fri Aug 23 22:51:39 2019 +0100 Merge pull request #874 from saschagrunert/go-mod-cleanup Cleanup go modules commit 487c7236736db7d5d2e46633d09453d0e149a0bd Author: Sascha Grunert Date: Fri Aug 23 10:28:32 2019 +0200 Cleanup go modules These two dependencies are not really needed, which can be reproduced via: ``` > export GO111MODULE=on && go mod tidy ``` Signed-off-by: Sascha Grunert commit 8469a9de07c45435b61cbfd4aed7167fb9e59cca Author: [[ BOT ]] Lynn Cyrin Date: Thu Aug 22 21:42:07 2019 -0700 show test failures commit 23c83030263f7adfc0e3c34b567ee508e8d536cf Merge: ecd576e 6a25af9 Author: Lynn Cyrin (they/them) Date: Sat Aug 17 11:24:05 2019 -0700 Merge pull request #862 from russoj88/UpdateREADME_gopkg.in_v1 Rewrite the "pinning to v1" section. commit 6a25af96413deaeb4d6c451d6288079db0840a82 Merge: 3bc62c4 ecd576e Author: russoj88 Date: Sat Aug 17 10:01:35 2019 -0700 Merge branch 'master' into UpdateREADME_gopkg.in_v1 commit ecd576e779bce41496738b34e9ee6272c63801d0 Merge: 6cc7e98 e11183f Author: Audrius Butkevicius Date: Sat Aug 17 16:51:43 2019 +0100 Merge pull request #868 from urfave/lynncyrin-patch-1 Modernize readme commit e11183fe50e3b3d75e481b1262e3222c565ba8bf Author: Lynn Cyrin (they/them) Date: Sat Aug 17 02:44:49 2019 -0700 Modernize readme I assume that people no longer care about what the package was named many years ago commit 3bc62c4fde03e107cad02f8828780470258b8fc0 Author: russoj88 Date: Thu Aug 15 12:30:29 2019 -0700 Mimic v2 example code from above. commit 62b8a7cc2cb05b1a454908087b35e5780a1d12ad Author: russoj88 Date: Wed Aug 14 11:20:09 2019 -0700 Add "Using v1 releases" to table of contents. commit cc091db561b137c49cbf370766a94b47cfdae182 Author: russoj88 Date: Wed Aug 14 11:21:40 2019 -0700 Update README.md Only instruct on right way to use library. Co-Authored-By: Lynn Cyrin (they/them) commit f529dad70caa6e307f95eecb2db6f16efc0f964d Author: russoj88 Date: Wed Aug 14 11:20:58 2019 -0700 Update README.md Include suggestion to put example in. Co-Authored-By: Lynn Cyrin (they/them) commit f2c26bab772e6b69a9fca945534728678578eb2b Author: russoj88 Date: Tue Aug 13 21:10:38 2019 -0700 Rewrite the "pinning to v1" section. commit 3681b057c5df7f380e75974674a8282cf5632dc3 Author: Sascha Grunert Date: Tue Aug 13 09:43:57 2019 +0200 Update `TakesFile` flag to apply only to supported flags Signed-off-by: Sascha Grunert commit 6cc7e987c4fa553caa5014c7dbc1e7acaea9f0f1 Merge: 7e49cc2 08c24e2 Author: Audrius Butkevicius Date: Mon Aug 12 21:30:37 2019 +0100 Merge pull request #856 from FaranIdo/master Add Subcommand fallback call to ExitErrHandler, fixing #816 commit 08c24e22ed2c4bebb348a738caf92c40bb63133c Author: FaranIdo Date: Mon Aug 12 00:29:46 2019 +0300 add missing ExitErrHandler in command + matching test, fixing #816 commit 7e49cc210a231eec218c2fba82df106af06d05b5 Merge: 8b18c71 4e42a2f Author: Ajitem Sahasrabuddhe Date: Sat Aug 10 09:01:16 2019 +0000 Merge pull request #848 from saschagrunert/fish-shell Add fish shell completion support commit 4e42a2f02ceb3cbfe2f8c4e5c6e419a712c335c9 Merge: 56d12d0 8b18c71 Author: Ajitem Sahasrabuddhe Date: Sat Aug 10 08:47:31 2019 +0000 Merge branch 'master' into fish-shell commit 8b18c71e1a4eabe8d7ba20d81d7fbd882709833d Merge: 7058c58 c6c2008 Author: Ajitem Sahasrabuddhe Date: Sat Aug 10 08:47:23 2019 +0000 Merge pull request #851 from saschagrunert/takes-file Add `TakesFile` indicator to flag commit 56d12d0c2f27a159e95165cf3cec2396df6f68af Merge: 7506b11 7058c58 Author: Ajitem Sahasrabuddhe Date: Fri Aug 9 17:21:24 2019 +0530 Merge branch 'master' into fish-shell commit c6c200864d770982106717a20ad99603396fb042 Merge: e9e9e0a 7058c58 Author: Sascha Grunert Date: Fri Aug 9 13:48:36 2019 +0200 Merge branch 'master' into takes-file commit 7058c58eb6af9ee166dafdf82012e1241890223d Merge: 2e0e39a de0fa70 Author: Ajitem Sahasrabuddhe Date: Fri Aug 9 17:16:13 2019 +0530 Merge pull request #847 from saschagrunert/remove-date-var Remove unused `Date` variable from `cliTemplate` commit de0fa704331adf0183d6f1b6d94a2390a48a810c Merge: 0d79d1d 2e0e39a Author: Audrius Butkevicius Date: Fri Aug 9 12:38:50 2019 +0100 Merge branch 'master' into remove-date-var commit e9e9e0ac5dfce215fb2207b3bbde2534a67907f6 Author: Sascha Grunert Date: Fri Aug 9 09:05:55 2019 +0200 Add `TakesFile` indicator to flag This new member of `Flag` indicates if the flag expects a file as input. This is especially useful for documentation and shell completion purposes. Signed-off-by: Sascha Grunert commit 7506b11da746beef287831f805f5b0e49264b400 Author: Sascha Grunert Date: Thu Aug 8 15:50:36 2019 +0200 Add fish shell completion support This commit adds a new method `ToFishCompletion` to the `*App` which can be used to generate a fish completion string for the application. Relates to: #351 Signed-off-by: Sascha Grunert commit 2e0e39a03b46023f83ec2e70f1948836e0581543 Merge: 946f918 aed704a Author: Ajitem Sahasrabuddhe Date: Fri Aug 9 10:34:28 2019 +0530 Merge pull request #845 from urfave/lint-fixes linter fixes commit 0d79d1d9d99db9e380e37034f677523b0ca435b5 Author: Sascha Grunert Date: Thu Aug 8 14:04:21 2019 +0200 Remove unused `Date` variable from `cliTemplate` Signed-off-by: Sascha Grunert commit aed704a9d036852c332867dffd97c60c51e8a38d Merge: 0990ca2 946f918 Author: Ajitem Sahasrabuddhe Date: Thu Aug 8 14:44:02 2019 +0530 Merge branch 'master' into lint-fixes commit 946f918365f62f6fe8d7fb7d4ea54dd441eccfb6 Merge: 2c477e7 286133f Author: Audrius Butkevicius Date: Thu Aug 8 09:13:09 2019 +0100 Merge pull request #735 from rliebz/combined Add app-wide support for combining short flags commit 0990ca2391ac8a72bc59d393e64ca520d9c53772 Merge: fdba7e0 2c477e7 Author: Ajitem Sahasrabuddhe Date: Thu Aug 8 13:36:30 2019 +0530 Merge branch 'master' into lint-fixes commit 286133fee5ef662bcfc9fdb7e410ce83528ab1f8 Merge: 815c29f 2c477e7 Author: Ajitem Sahasrabuddhe Date: Thu Aug 8 13:33:32 2019 +0530 Merge branch 'master' into combined commit 2c477e720e69b9ce81e9d6cf68c81a0334446016 Merge: e0057bb 99fad61 Author: Audrius Butkevicius Date: Thu Aug 8 08:41:19 2019 +0100 Merge pull request #830 from saschagrunert/docs-gen Add markdown and man page docs generation methods commit 99fad61ded52131321a0d7e5d330554512254ebe Merge: 40d4a25 e0057bb Author: Audrius Butkevicius Date: Thu Aug 8 07:06:08 2019 +0100 Merge branch 'master' into docs-gen commit e0057bb59731900e8b702b0b5282378577cb99e5 Merge: 521735b fd39578 Author: Audrius Butkevicius Date: Thu Aug 8 07:04:25 2019 +0100 Merge pull request #846 from urfave/asahasrabuddhe-patch-1 Update README.md commit 815c29ffc73623b32e587298eeda0a0dd2ff5737 Merge: a77c440 521735b Author: Audrius Butkevicius Date: Thu Aug 8 07:03:49 2019 +0100 Merge branch 'master' into combined commit fd395786a2c77da3f111c6208ba50b3041fe6ee2 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 21:00:07 2019 +0530 Update README.md remove quotes around coverage badge commit fdba7e0f8c921d4ce169cb416b2eae58026c83e9 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 20:14:50 2019 +0530 linter fixes code cleanup changing some test code to ensure uniformity commit 40d4a25a01296d2f51bf7bbf0b6e8eb99ba4a84c Author: Sascha Grunert Date: Sat Aug 3 12:41:50 2019 +0200 Add markdown and man page docs generation methods This adds two new methods to the `App` struct: - `ToMarkdown`: creates a markdown documentation string - `ToMan`: creates a man page string Signed-off-by: Sascha Grunert commit 521735b7608a25d771a39d42e2267e061e7e84b8 Merge: 97179ca 22e1fc8 Author: Audrius Butkevicius Date: Wed Aug 7 12:10:34 2019 +0100 Merge pull request #844 from urfave/asahasrabuddhe-patch-1 Update README.md commit 22e1fc84192059f056a7b53aa5ef2ee7113d9a83 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 14:02:52 2019 +0530 Update README.md add codecov.io badge commit 97179ca390abf228a187e6ebbedca69636d60f0d Merge: b6f7dd9 3a41d6d Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 13:58:51 2019 +0530 Merge pull request #843 from lafriks/patch-1 Support GoLang 1.10 to 1.12 commit 3a41d6d7851b15f132131444865e82b17baf0be0 Author: Lauris BH Date: Wed Aug 7 11:21:31 2019 +0300 Lower support to GoLang compiler version 1.10 commit 1f4473209dcad42ed88ba68f1be7d4e906ae91be Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 13:50:04 2019 +0530 Update .travis.yml support go versions in line with go's release policy commit e3fa7e8566f9374ac6c1e08ace0e0555f9666e10 Author: Lauris BH Date: Wed Aug 7 11:06:15 2019 +0300 Support also GoLang 1.11 compiler commit b6f7dd93594d17c08d349ba5f974e501b8c12b7b Merge: 93392d1 e2de8c7 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 13:28:34 2019 +0530 Merge pull request #836 from urfave/flag-type-generation-golang Flag Generation in the CLI commit e2de8c74587d464770155415cda09c3569224692 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 12:38:17 2019 +0530 update readme with correct error message, add 1.12 and 1.11 to travis commit a77c440b8476d59b59fcf2606a92fd3bb82603c3 Merge: 8d31c5e 93392d1 Author: Robert Liebowitz Date: Tue Aug 6 22:33:49 2019 -0400 Merge branch 'master' into combined commit 24de27b05e91ef797b9ba97e3c146842fb8e29d8 Merge: c19938f 93392d1 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 01:54:44 2019 +0530 Merge branch 'master' into flag-type-generation-golang commit c19938fbbfb19120beeca5d0af02291a99e61f27 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 01:51:02 2019 +0530 update ci commands commit 6ee5b89e03a0fc47c6351c902ef714f1475e8fde Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 01:50:50 2019 +0530 move build.go to root commit e8bbb4c3b5472320f0af5fb00961c76f1061edd4 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 01:46:22 2019 +0530 remove unnecessary sprintf commit 5070d00811cd5f2f21b0a6e021581e8efb2479e9 Author: Ajitem Sahasrabuddhe Date: Tue Aug 6 12:33:33 2019 +0530 move packages slice to global scope commit 93392d12e8cd60a1c7c62dc4bf4bab7a8e001eec Merge: 26945c5 1db0496 Author: Audrius Butkevicius Date: Tue Aug 6 07:59:53 2019 +0100 Merge pull request #808 from yogeshlonkar/master Add support for flags bash completion commit adfe6a09c121a6b96357ea161f2d679d509c013f Author: Ajitem Sahasrabuddhe Date: Tue Aug 6 12:14:18 2019 +0530 indenting fix commit 1db049685ac49e11b2e27285e1287793cfe0ea84 Author: Yogesh Lonkar Date: Mon Aug 5 20:22:52 2019 +0200 Fix unused regex commit 2be2bc755e4634d34136769a426a7ca52e698cc0 Author: Yogesh Lonkar Date: Mon Aug 5 20:18:08 2019 +0200 Add additional test for log flag completion and comments commit c3f51bed6fffdf84227c5b59bd3f2e90683314df Author: Yogesh Lonkar Date: Mon Aug 5 17:07:46 2019 +0200 Fix SC2199: Arrays implicitly concatenate in commit c5612e8cd21e0cd99f73d23103df99a9af70f853 Author: Yogesh Lonkar Date: Mon Aug 5 16:58:04 2019 +0200 Fix review comments commit 8d31c5e167103ef4235cc5553b0fb45a2f6e8f74 Author: Robert Liebowitz Date: Mon Aug 5 07:05:07 2019 -0400 Update README.md Co-Authored-By: Ajitem Sahasrabuddhe commit 03153b9cf8988d787fe79d02bd4138283ea507bd Author: Robert Liebowitz Date: Mon Aug 5 06:16:30 2019 -0400 Allow combining short flags globally commit d6523cf8692d40c0ff9f6d81f7ac25341c58da7a Merge: e949dc2 26945c5 Author: Yogesh Lonkar Date: Mon Aug 5 11:00:26 2019 +0200 Merge branch 'master' into master commit 26945c58edddd8cb0830baf73ebc7bee44b5f455 Merge: d09efb5 c25e4ca Author: Lynn Cyrin (they/them) Date: Sun Aug 4 12:36:23 2019 -0700 Merge pull request #823 from xordspar0/master Make the exit code example more clear commit c25e4cab32bab49d1d3c4847a0a6419e2cb3dd15 Merge: b1a7c50 d09efb5 Author: Lynn Cyrin (they/them) Date: Sun Aug 4 12:30:28 2019 -0700 Merge branch 'master' into master commit ac5c97b41844032ae47d5f94d7f73533af629f11 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:45:28 2019 +0530 add latest assets file commit 489d92d2e2477b240015e83af07658e826eaa7cb Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:44:15 2019 +0530 add missing os package commit a7f0d86509d0845980b77999dc5882b32f244818 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:33:21 2019 +0530 add zero mod fs back and commit file with latest ts commit 798e1f3d3aba8d04c9118962870b41ad099b7bea Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:29:53 2019 +0530 fix spacing issue commit 7a6f3d4394003447c99b223ce1c1e19e23b20124 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:23:32 2019 +0530 fix tests commit 58ae5eb590667b0115d3f82e03593d87e2924b1c Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:06:44 2019 +0530 move entire build system to go commit 8547458f1d93654348b3bbeccb6d04424d5eab3e Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 19:01:47 2019 +0530 remove zero mod fs commit d09efb5fbd744f23d561c02b99a16a61e679bba6 Merge: 7745000 1327f58 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 18:00:11 2019 +0530 Merge pull request #837 from urfave/codeowners Use codeowners instead of maintainers commit 1327f583142a70a5d2fb9ef8422862b84a1a8780 Merge: 9938dec 7745000 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 17:52:42 2019 +0530 Merge branch 'master' into codeowners commit e949dc2cc05a553645eea447ded78eddfec0ad37 Merge: 11c9e59 7745000 Author: Yogesh Lonkar Date: Sun Aug 4 10:38:56 2019 +0200 Merge branch 'master' into master commit 4b0a4104630bddd01a7a6c5aa8a70dc94642ab52 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 12:26:48 2019 +0530 fix travis build commit fb4cea5f30995b6caaa700ea789902af499d63b2 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 12:22:45 2019 +0530 add new generation logic to travis commit 365557021fe254d059d0f7065bb0c94686208723 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 12:17:27 2019 +0530 remove legacy version check code commit b6bfbe97f8430a4dfe05791363719d61cb921793 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 12:16:07 2019 +0530 update appveyor to go 1.11, add support for code coverage generation in tests commit 826954c97919610e0f4086ab3bcc007ac3326184 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 11:09:34 2019 +0530 update app name and remove version commit 04948f21526ed8343ae9697600e284962d8972be Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 11:06:19 2019 +0530 generate test with go generate commit 86e10211dea0c2b369610aabea1c987888bbb01a Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 11:05:50 2019 +0530 remove redundant go generate from altsrc update go generate in cli package to generate both files regeneration test commit c676ed4caa76219414ad737584ec46b03e94cbf8 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 11:04:23 2019 +0530 indentation fixes in template regeneration test commit c4fc88e46d182072228b2b2a0236b0f77a45e567 Merge: 2a08494 9260850 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 10:29:34 2019 +0530 Merge branch 'flag-type-generation-golang' of https://github.com/urfave/cli into flag-type-generation-golang commit 2a084945a47235959c023291f87127ead86fc168 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 10:28:08 2019 +0530 move around code change package to flag-gen to avoid conflict with flag-generator binary test code generation commit 065fe9e9af992d82126929c157edb16b1a1f06ab Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 10:05:44 2019 +0530 change structure to embed source json and template files restructure code to have defaults in place of choices commit d1ded77768d33ce64657686558884e69cbb5bce4 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 08:45:29 2019 +0530 rename utility from fg > flag-generator commit 7745000a0eabd118a94df095da6feeae887f9a34 Merge: e6cf83e 81acbeb Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 08:15:00 2019 +0530 Merge pull request #774 from whereswaldon/patch-1 Clarify that altsrc supports both TOML and JSON commit 81acbeb629cebde2c8c3c36421644e42ffd9e8f9 Merge: 8abc5a2 e6cf83e Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 08:12:24 2019 +0530 Merge branch 'master' into patch-1 commit e6cf83ec39f6e1158ced1927d4ed14578fda8edb Merge: 244eba7 eee6ce8 Author: Lynn Cyrin (they/them) Date: Sat Aug 3 19:37:52 2019 -0700 Merge pull request #829 from urfave/lynncyrin-patch-2 Release 1.21.0 commit 8abc5a2e49624c8f12a0b1734b57bd12aadf1604 Merge: b2421d1 244eba7 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 08:04:13 2019 +0530 Merge branch 'master' into patch-1 commit 9938dec695d6a0ba5a4d84b703766333cd7d10e8 Author: [[ BOT ]] Lynn Cyrin Date: Sat Aug 3 10:26:07 2019 -0700 update contributing docs commit 97dbddb32db290fdc6392e6a669a92acbadef9ff Author: [[ BOT ]] Lynn Cyrin Date: Sat Aug 3 10:23:29 2019 -0700 use codeowners instead of maintainers commit 92608509a4c011a598dcc5b10d15930040fa403e Merge: d209be3 244eba7 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:49:24 2019 +0530 Merge branch 'master' into flag-type-generation-golang commit d209be324522a802f8056094f8bb89b4562ca9a3 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:32:36 2019 +0530 update go generate command test file generation commit add69c7d4fbef52ac8541c2f7dfa465fdd9be2c3 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:16:25 2019 +0530 updated flag types generated courtesy fg cli commit c133a5aeb16e0978e3c29f8446ddf02922b7e150 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:15:43 2019 +0530 add explicit true/false choices for value and dest keys due to go default false for bool types commit 16c7a60528bc5f7d98030e09630a38f3d8fc9ddc Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:14:58 2019 +0530 finish generation of flag types for altsrc package rename package to fg (flag generator) commit 32ddef5ca7f20a9aa0e7b80484e59d1653e856e4 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 21:48:48 2019 +0530 finish generation of flag types for cli package commit 9766be8d3e11e71a2a4bb5c235fc6961278df983 Author: mingrammer Date: Thu Mar 7 00:04:18 2019 +0900 get latest changes from master commit e01e3c540c7aea9e4e9740d002bd212491c2fe00 Author: mingrammer Date: Wed Mar 6 23:51:22 2019 +0900 Fix the unaligned indents for the commands that have no categories commit b1a7c502eba6a479bf9432052136f73d7740a69b Merge: c75a689 244eba7 Author: Lynn Cyrin (they/them) Date: Sat Aug 3 09:36:27 2019 -0700 Merge branch 'master' into master commit eee6ce83c075e8aeb1d71a22f69e6fddbc70a248 Merge: 8a7f65e 244eba7 Author: Lynn Cyrin (they/them) Date: Sat Aug 3 09:06:47 2019 -0700 Merge branch 'master' into lynncyrin-patch-2 commit 11c9e598b06a0a2201f356f945c4cd2355a8ccbf Merge: 01ab016 244eba7 Author: Yogesh Lonkar Date: Sat Aug 3 15:52:08 2019 +0200 Merge branch 'master' into master commit 244eba7e4c24eb9a416bb1edadaf74d943a7bb89 Merge: 1169906 4627bbe Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 18:04:31 2019 +0530 Merge pull request #831 from saschagrunert/go-modules Add go module support commit b2421d123539be62bc9f2f9bdd77fce3cc2d1af6 Merge: 3e14507 1169906 Author: Audrius Butkevicius Date: Sat Aug 3 13:25:34 2019 +0100 Merge branch 'master' into patch-1 commit 4627bbe109fb902221de0a86f20048ad5679ea0c Author: Sascha Grunert Date: Sat Aug 3 12:55:06 2019 +0200 Add go module support This adds a go.{mod,sum} file to official support go modules. Signed-off-by: Sascha Grunert commit 1169906f575ec070559cc1a6fc083b38498160c0 Merge: 07c1638 94f4f83 Author: Audrius Butkevicius Date: Sat Aug 3 11:02:34 2019 +0100 Merge pull request #773 from teresy/redundant-nil-check-slice Remove redundant nil checks commit 94f4f8367278436d4554b3e7a72ec6b66c209312 Merge: da581b2 07c1638 Author: Lynn Cyrin (they/them) Date: Sat Aug 3 02:05:33 2019 -0700 Merge branch 'master' into redundant-nil-check-slice commit 07c163896936a0fb3ad24dcbf05b25e9c7aaee53 Merge: 842e3fe 7a51175 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 08:47:32 2019 +0530 Merge pull request #806 from mingrammer/fix-help-indentation Fix the unaligned indents for the command help messages commit 7a51175ce1cb78d5009e91001697240c1af84ee2 Merge: 330a914 842e3fe Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 08:37:34 2019 +0530 Merge branch 'master' into fix-help-indentation commit 842e3fe1b6adbb257f711a1f66553ad03174c5c0 Merge: 7675649 fa51d00 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 08:12:05 2019 +0530 Merge pull request #828 from urfave/lynncyrin-patch-1 Update maintainers for current reality commit 8a7f65e05215a76a2246b7b42cb9c082b5eae483 Author: Lynn Cyrin (they/them) Date: Fri Aug 2 19:30:41 2019 -0700 Update CHANGELOG.md commit e8eac43d9d73e96f6b034f89770ab2fdbec2cb5b Author: Lynn Cyrin Date: Fri Aug 2 18:26:41 2019 -0700 Update CHANGELOG.md commit 330a9143fb1ebfe61bb382b5e45833c02a405c0c Merge: ddc3453 7675649 Author: Lynn Cyrin Date: Fri Aug 2 18:14:50 2019 -0700 Merge branch 'master' into fix-help-indentation commit fa51d00dc6fb57edca14295fde308401d7720e61 Author: Lynn Cyrin Date: Fri Aug 2 18:06:15 2019 -0700 Update maintainers for current reality commit 01ab0164275805b0c43bc5f7f534a2d8f6abe48e Merge: d79d2a0 7675649 Author: Yogesh Lonkar Date: Fri Aug 2 22:03:55 2019 +0200 Merge branch 'master' into master commit c75a689f629137700e8a30651f95cc41cf12a6d1 Author: Jordan Christiansen Date: Fri Aug 2 14:28:57 2019 -0500 Make exit code example more clear The purpose of this example is to show that you can exit with an error code if a flag is unspecified, but with the code as it is, the only way to cause a non-zero exit is by adding the flag `--ginger-crouton=false`, which is not explained in the example. In this new version of the example, running the command with no flag will exit with an error, and running it with the flag will exit normally. commit 7675649a174ac724b1d7fa9d5932eff3dd1582a2 Merge: 656063a f8ba505 Author: Ajitem Sahasrabuddhe Date: Fri Aug 2 22:52:32 2019 +0530 Merge pull request #819 from lynncyrin/required-flags-take-2 Required flags commit f8ba505a7cc01559767d9d961fb68bda833d5d3d Merge: 60fb297 656063a Author: Ajitem Sahasrabuddhe Date: Fri Aug 2 22:49:29 2019 +0530 Merge branch 'master' into required-flags-take-2 commit 656063a84689d3e45f16ab9c40706e4df219190a Merge: 693af58 6505336 Author: Ajitem Sahasrabuddhe Date: Fri Aug 2 22:49:09 2019 +0530 Merge pull request #788 from benzvan/master adds test coverage to context commit 60fb2972328d6a7487c6821a58a86d476167c2bd Author: Lynn Cyrin Date: Thu Aug 1 23:27:34 2019 -0700 remove help assertion stuff commit d7ec4e801357fa5ccfab53669a42f78fc1a69d39 Author: Lynn Cyrin Date: Thu Aug 1 23:26:43 2019 -0700 add env var tests commit f4128a02f3215e532dff0b96e21a8e2cb08389a1 Author: Lynn Cyrin Date: Thu Aug 1 22:54:15 2019 -0700 Update command.go commit 38f9e1622d2d4a5e1a86afd8f8f9d6cbf0157816 Author: Lynn Cyrin Date: Thu Aug 1 22:52:21 2019 -0700 add environment variable support :tada: commit f21b22dd904b638518d9ea321d718f219bd6593c Author: Lynn Cyrin Date: Thu Aug 1 22:10:18 2019 -0700 cleanup some issues with error display commit fdd4d106912b363ccffa03eec51a56dd3a6a822b Author: Lynn Cyrin Date: Thu Aug 1 21:48:52 2019 -0700 update comments commit ef9acb4a3b846728c98844f7f92964ae2a79f259 Author: Lynn Cyrin Date: Thu Aug 1 21:46:56 2019 -0700 rename cases commit 45f2b3d8e71e11822cf591f1c370f8587726c425 Author: Lynn Cyrin Date: Thu Aug 1 21:45:11 2019 -0700 more test cases commit 78db152323afb7934f9f0dd207eeaf34147bb300 Author: Lynn Cyrin Date: Thu Aug 1 21:35:15 2019 -0700 add typed error assertions commit d4740d10d0cbde53a8e3132a0964464b2b50fc0b Author: Lynn Cyrin Date: Thu Aug 1 20:58:08 2019 -0700 more test cases commit 595382c50970039261f765043aee4c647aeccbd5 Author: Lynn Cyrin Date: Thu Aug 1 20:39:37 2019 -0700 expand test cases commit 3d6eec825ac768894a385ca3c3156a7905c27ce3 Author: Lynn Cyrin Date: Thu Aug 1 20:35:23 2019 -0700 add test cases commit 7b9e16b6b5255803ea279fe1ee0e41973f49a42e Author: Lynn Cyrin Date: Thu Aug 1 20:30:43 2019 -0700 update test names commit 95d3a8624d8aa6661831d0009550b602458fcb4d Author: Lynn Cyrin Date: Thu Aug 1 20:27:51 2019 -0700 update test to reflect app flag usage commit 714a73f028fa5a3c5b6512bf5e55b94be388de8f Author: Lynn Cyrin Date: Thu Aug 1 19:57:14 2019 -0700 remove unused thing commit 9438aba3b89e7053070ef277121a14e5fb95947e Author: Lynn Cyrin Date: Thu Aug 1 19:54:57 2019 -0700 remove showFlagError, we can use the help printer assertion to accomplish the same goal commit 386b379d1950e8939c8a3dbba0335cf79903f421 Author: Lynn Cyrin Date: Sun Jul 28 22:45:43 2019 -0700 Revert "reset generated flags changes" This reverts commit 9ec594d5290b846de59b0bc350849b848cabfbd7. commit 9ec594d5290b846de59b0bc350849b848cabfbd7 Author: Lynn Cyrin Date: Sun Jul 28 22:34:07 2019 -0700 reset generated flags changes commit 23f09ac1e82395dc1a70c36d649ab03929e32d79 Author: Lynn Cyrin Date: Sun Jul 28 22:19:35 2019 -0700 cleanup tests, check required flags in more places commit d79d2a04242b21441061e00475287f4b826614f8 Author: Yogesh Lonkar Date: Wed Jul 24 16:08:47 2019 +0200 Fix issue with source command completion Avoid competion for bash builtin `source` and fallback to default implementation as it throws below error ``` -bash: source: --: invalid option source: usage: source filename [arguments] ``` commit 7ce0af189ed431005f47e583a63648ea9a0a99ea Author: Lynn Cyrin Date: Thu Jul 18 00:52:24 2019 -0700 remove unused code commit d8985dc6d56ac75b35f0422d8efbc04814bf17f3 Author: Lynn Cyrin Date: Thu Jul 18 00:51:16 2019 -0700 reduce diff commit 19140e1fb52f458727a3c718f82fb93861d5849c Author: Lynn Cyrin Date: Thu Jul 18 00:48:09 2019 -0700 show errors commit 2299852c3c3512dafac738a10847da3bb3699b62 Author: Lynn Cyrin Date: Thu Jul 18 00:47:18 2019 -0700 cleanup subcommand and specs commit 300288670fe7713da8ae6e4a449d12e6c911b713 Author: Lynn Cyrin Date: Thu Jul 18 00:20:32 2019 -0700 add subcommand commit cc1cf8c459c947156bb429ef319f4cf762b1e468 Author: Lynn Cyrin Date: Thu Jul 18 00:09:07 2019 -0700 wording shift commit 32d84d8e870a7f475c228c3c58f2c879f6a4009e Author: Lynn Cyrin Date: Wed Jul 17 00:25:13 2019 -0700 copy update commit 01d5cfab7066912c97eeaf94cbbda8f90fc490f7 Author: Lynn Cyrin Date: Wed Jul 17 00:20:44 2019 -0700 use strings.Join commit cdc7af744e07ac8dbb34793f8b392af46ba443f7 Author: Lynn Cyrin Date: Wed Jul 17 00:16:40 2019 -0700 add handling for multiple required flags commit 9293f5b3cc6f5a96a1976bf2f810c957044c5ee8 Author: Lynn Cyrin Date: Sun Jul 14 21:00:16 2019 -0700 visually shorten logic commit f00f35ce8c1a6ebd7a3900901dd8b05049fbefc7 Author: Lynn Cyrin Date: Sat Jul 13 14:02:45 2019 -0700 docs commit 17108e1db49db34480170f575131e642b22bda2d Author: Lynn Cyrin Date: Sat Jul 13 13:59:29 2019 -0700 tabs commit cf824804c2353572e0f99b098829f2d7ffe2a0ec Author: Lynn Cyrin Date: Sat Jul 13 13:57:06 2019 -0700 update tests commit 80d7e91191cfb38c7e3fccbcf2b1320807d4b05d Author: Lynn Cyrin Date: Sat Jul 13 03:51:26 2019 -0700 fill out test cases commit 746866c10daf9425d41140f78ffc518ee4d9ae01 Author: Lynn Cyrin Date: Sat Jul 13 03:44:39 2019 -0700 add update integration with the help output commit 550ed20ea429e19b7b132984a6e34c057acabc42 Author: Lynn Cyrin Date: Sat Jul 13 01:26:47 2019 -0700 update tests commit f6777bf4bf44867abbcaa63a97a67db60469ea80 Author: Lynn Cyrin Date: Sat Jul 13 01:03:46 2019 -0700 quote the flag name commit 6a2ae783730e54eb7ea91cf6839ed46446134017 Author: Lynn Cyrin Date: Thu Jul 11 21:53:10 2019 -0700 backwards compatible RequiredFlag implementation commit 922d2318916c3b59eafde03c36b12551a71f2d51 Author: Lynn Cyrin Date: Thu Jul 11 21:28:09 2019 -0700 ./generate-flag-types cli -i flag-types.json -o flag_generated.go commit 8a58b7e039e37b0631d6ced0ab3279c319c4d8c8 Author: Lynn Cyrin Date: Thu Jul 11 20:47:47 2019 -0700 remove manual isRequired funcs commit 62e99ad1c16714cda6c9f8b980dd9483372771e2 Author: Lynn Cyrin Date: Thu Jul 11 20:46:22 2019 -0700 add IsRequired to generator commit 310bfeb1942571dfe0ac9f60f45e75df11189e4e Author: Lynn Cyrin Date: Thu Jul 11 20:44:41 2019 -0700 add required attr to generator commit af627c73c3ddc2d4ff1e4c0847c3355bc0a47c0d Author: Lynn Cyrin Date: Thu Jul 11 20:34:17 2019 -0700 update func name commit 3d2d6975b4fffee753c9422f3440d6b7c114ef40 Author: Lynn Cyrin Date: Thu Jul 11 20:32:42 2019 -0700 reduce diff commit 0608059cc709e86905bfd18886d6649275c9937e Author: Lynn Cyrin Date: Thu Jul 11 20:32:15 2019 -0700 reduce diff commit 9c299e7e8af265e017adf7abf431a0fe0c89dd95 Author: Lynn Cyrin Date: Thu Jul 11 20:28:29 2019 -0700 reduce diff commit 30a71dc427bc2634f00d9fe315e5717022e0eb66 Author: Lynn Cyrin Date: Thu Jul 11 20:25:52 2019 -0700 update Run command commit f7d5e2c21e4cca02de26a7f448d69f4dac531af7 Author: Lynn Cyrin Date: Thu Jul 11 20:22:16 2019 -0700 reduce diff commit e6842c0b7521b5e608da30a4e8a5ed06e6469cf7 Author: Lynn Cyrin Date: Thu Jul 11 20:21:05 2019 -0700 merge in test file commit fa8187f2ce6a7d8258899b46ccfe081c9c0ea6f7 Author: Lynn Cyrin Date: Thu Jul 11 20:19:42 2019 -0700 reduce diff commit ce1630141e70b2ca599a21fd9494e98b88f25b2d Author: Lynn Cyrin Date: Thu Jul 11 20:18:52 2019 -0700 reduce diff??? commit 138dbaafec9db29d5b0b10af383ca7c6848a2c0d Merge: aba73ce 693af58 Author: Lynn Cyrin Date: Thu Jul 11 20:07:55 2019 -0700 Merge branch 'master' into required_flags commit da581b24e88a3b8d5b3d4b7685f9eee32ec0df8e Merge: 6aa7f35 693af58 Author: Audrius Butkevicius Date: Fri Jun 28 07:55:04 2019 +0100 Merge branch 'master' into redundant-nil-check-slice commit 65053360c7533fc585bdb9f53abada1e9b39f564 Author: Ben Zvan Date: Wed Jun 26 09:41:11 2019 -0500 Revert "Created using Colaboratory" This reverts commit 83b99c4109dce6ac7a6b5d2048e26f1ad60ef3f3. This commit was randomly created here when I connected to colaboratory commit 83b99c4109dce6ac7a6b5d2048e26f1ad60ef3f3 Author: Ben Zvan Date: Tue Jun 25 18:47:58 2019 -0500 Created using Colaboratory commit 23042d37079702af32e4bcae8d41bb72569431da Merge: 4a76377 693af58 Author: Ben Zvan Date: Fri Jun 14 10:14:07 2019 -0500 Merge branch 'master' into master commit 62f02f21ef0b5c3c0aa67d3240aee15bc8a53457 Author: Yogesh Lonkar Date: Thu Apr 11 10:57:58 2019 +0530 Don't complete hidden flags commit 1d7a2b08d6f8e9764e2f2b911b1bb9fa49596f92 Author: Yogesh Lonkar Date: Thu Mar 21 13:01:48 2019 +0530 Add default completion on commands, test cases, refactor code commit fb1421d9031313c5e0f3c4a92625ed9cf5739b0d Author: Yogesh Lonkar Date: Wed Mar 20 21:34:56 2019 +0530 Fix duplicate completion of existing flag commit 58a072d5733d4bb2dc61ffbc3557ec9592e34adc Author: Yogesh Lonkar Date: Wed Mar 20 20:28:51 2019 +0530 Add bash completion support for flags commit ddc3453179ea450663473db4689f7c256225a72b Author: mingrammer Date: Thu Mar 7 00:04:18 2019 +0900 Update README.md commit a0453b2200cafa97ce263a4a5df87f5087d2abda Author: mingrammer Date: Wed Mar 6 23:51:22 2019 +0900 Fix the unaligned indents for the commands that have no categories commit 693af58b4d51b8fcc7f9d89576da170765980581 Merge: e229212 d7c3be8 Author: Audrius Butkevicius Date: Sun Feb 3 18:40:40 2019 +0000 Merge pull request #766 from agis/patch-1 Fix README typo commit 6aa7f352fa56438b4c8fcaff43e1050855526051 Merge: 21dfc6e e229212 Author: Audrius Butkevicius Date: Sun Feb 3 18:39:49 2019 +0000 Merge branch 'master' into redundant-nil-check-slice commit e2292127695d01e9fc3511f2ec7ef651bf3ca8af Merge: b67dcf9 5b83c89 Author: Audrius Butkevicius Date: Sun Feb 3 18:37:18 2019 +0000 Merge pull request #798 from Quasilyte/patch-1 use type switch instead of if/else commit 5b83c895a70b7714548f0aa4f43deb3fa5fc1601 Author: Iskander (Alex) Sharipov Date: Tue Jan 29 22:51:02 2019 +0300 use type switch instead of if/else This reduces the syntax noise of the code by removing excessive type assertions. Signed-off-by: Iskander Sharipov commit 4a76377775cebfc3dca4af752ba2837f9694b9d8 Author: Ben Zvan Date: Wed Dec 26 12:48:12 2018 -0600 go fmt commit d63733fe14aad10beca5490a453904bc1d67fe16 Author: Ben Zvan Date: Wed Dec 26 12:41:27 2018 -0600 adds test coverage to context commit b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b Merge: cbebba9 11ab68f Author: Audrius Butkevicius Date: Mon Oct 29 21:32:00 2018 +0000 Merge pull request #776 from gliptak/patch-2 Bring Go version current commit 11ab68f24d392fc36615c650bc6241c0b96c4318 Merge: 769f6d5 cbebba9 Author: Audrius Butkevicius Date: Mon Oct 29 21:19:45 2018 +0000 Merge branch 'master' into patch-2 commit cbebba941b23ee6f666b057c9f3d0937263ddd01 Merge: 934abfb 9587fc2 Author: Audrius Butkevicius Date: Mon Oct 29 21:18:40 2018 +0000 Merge pull request #775 from gliptak/patch-1 Correct typo commit 769f6d543bd3c9b36b98e3a46ad646cf63769120 Author: Gábor Lipták Date: Thu Oct 18 21:00:02 2018 -0400 Bring Go version current commit 9587fc27bd923141975eac8c34288bcf8de5cca2 Author: Gábor Lipták Date: Thu Oct 18 20:56:13 2018 -0400 Correct typo commit 3e145076abdbaf5c6e47e311b5e659251604a49b Author: Christopher Waldon Date: Fri Oct 12 11:30:46 2018 -0400 Clarify that altsrc supports both TOML and JSON commit 21dfc6eb8302c4db3547a22b0843c43e36fe058e Author: teresy Date: Wed Oct 10 14:54:48 2018 -0400 Remove redundant nil checks commit d7c3be82673f869fed4ea77a0c5e3f13bd65ba89 Author: Agis Anastasopoulos <827224+agis@users.noreply.github.com> Date: Tue Aug 21 11:19:37 2018 +0300 Fix README typo commit 934abfb2f102315b5794e15ebc7949e4ca253920 Merge: 8e01ec4 3e5a935 Author: Audrius Butkevicius Date: Tue Aug 21 07:40:27 2018 +0100 Merge pull request #758 from vrothberg/fix-short-opts-parsing short opt handling: fix parsing commit 3e5a935ed3cafadcddc6f5ab2fe7ddd2aa0c3cea Author: Valentin Rothberg Date: Tue Aug 21 08:33:42 2018 +0200 fix `go vet` warning command_test.go:342:3 value declared but not used Signed-off-by: Valentin Rothberg commit c23dfba7018a4666892af705d89150a5f1ac8293 Author: Valentin Rothberg Date: Thu Jun 28 16:41:02 2018 +0200 short opt handling: fix parsing Only split a given string (e.g., "-abc") into short options (e.g., "-a", "-b", "-c") if all those are flags. To further avoid mistakenly transform common arguments, catch "flag provided but not defined" errors to iteratively transform short options. Signed-off-by: Valentin Rothberg Fixes: https://github.com/projectatomic/libpod/issues/714 commit 8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff Merge: d4bf9ce 8dc47eb Author: Dan Buch Date: Sun Feb 25 22:02:53 2018 -0500 Merge pull request #598 from urfave/backport-json-support Backport JSON InputSource to v1 commit 8dc47eb3cbaea99e0d3e04424f6f3649657a6944 Merge: f551359 d4bf9ce Author: Dan Buch Date: Sun Feb 25 16:09:48 2018 -0500 Merge branch 'master' into backport-json-support commit d4bf9ce8609adfc60775b62676fa3471e7d978e0 Merge: b09aafd e59e474 Author: Dan Buch Date: Sun Feb 25 16:09:04 2018 -0500 Merge pull request #498 from urfave/merging-jereksel-zsh Merging #489 (plus hack) commit e59e4743b884a22e06b1b78a893c68513c0702b7 Merge: 5fc8124 b09aafd Author: Dan Buch Date: Sat Feb 24 22:02:40 2018 -0500 Merge branch 'master' into merging-jereksel-zsh commit b09aafdfe9ad3fa4353b82eaed4e80598878c128 Merge: 446f49e bc77a15 Author: Dan Buch Date: Sat Feb 24 22:02:19 2018 -0500 Merge pull request #681 from urfave/check-run-error-in-readme Update README examples to check for errors commit 5fc8124af17aae44085140a2a7b3141a973dbda0 Merge: 688c5a9 446f49e Author: Dan Buch Date: Sat Feb 24 21:58:26 2018 -0500 Merge branch 'master' into merging-jereksel-zsh commit bc77a15c69f9aadd39b15be9373abc4916c8ad53 Merge: 59e1ddb 446f49e Author: Dan Buch Date: Sat Feb 24 21:54:06 2018 -0500 Merge branch 'master' into check-run-error-in-readme commit 446f49e78f79a164079a99a88446182703331c75 Merge: a1c7408 45289ea Author: Jesse Szwedko Date: Tue Feb 20 21:16:31 2018 -0800 Merge pull request #715 from urfave/maintainers-notice Adjust contribution and maintainer prose per current reality commit 45289ea7a0de564a71532e13b9916961a38abc8e Author: Dan Buch Date: Tue Feb 20 12:40:43 2018 -0500 Adjust contribution and maintainer prose per current reality commit 59e1ddb43ed48e52bf1a0aca8a310d9bca9897d7 Merge: 9838c8b a1c7408 Author: Dan Buch Date: Tue Feb 13 15:27:04 2018 -0500 Merge branch 'master' into check-run-error-in-readme commit a1c7408de3f632d86eee604a3bb755f1ffb68226 Merge: 803d066 3a87b13 Author: Jesse Szwedko Date: Sat Feb 10 18:18:39 2018 -0800 Merge pull request #712 from windler/fix_args_reorder Fix args reordering when bool flags are present commit 3a87b13b01ac8628694f1e1b20bdb452cc0f54d2 Author: Nico Windler Date: Sat Feb 10 13:35:23 2018 +0100 Fix args reordering when bool flags are present commit 803d0665796d3b09d3190067803fc285d1604732 Merge: 75104e9 d7555e1 Author: Jesse Szwedko Date: Fri Feb 2 13:13:18 2018 -0800 Merge pull request #704 from dolmen/replace-unneeded-Sprintf Fix unnecessary uses of Sprintf commit d7555e172994da8d058334aa1fe69533b1685924 Author: Olivier Mengué Date: Fri Jan 26 21:14:34 2018 +0100 Fix unnecessary uses of Sprintf - use strconv directly - use concatenation for "%s%s" commit 75104e932ac2ddb944a6ea19d9f9f26316ff1145 Merge: 39908eb e38e4ae Author: Jesse Szwedko Date: Sat Jan 6 11:10:48 2018 -0800 Merge pull request #697 from urfave/fix-skip-flag-parsing Fix regression of SkipFlagParsing behavior commit e38e4ae2d05acf5b5164c160a67fb7048e1358b0 Author: Jesse Szwedko Date: Fri Dec 29 13:38:18 2017 -0500 Fix regression of SkipFlagParsing behavior Introduced by df562bf1a8626f2d16f91fcbf7230a5bdca3d592 Was mistakenly prepending the command name. commit 39908eb08fee7c10d842622a114a5c133fb0a3c6 Merge: 119bb65 2610681 Author: Jesse Szwedko Date: Tue Dec 12 08:34:29 2017 -0800 Merge pull request #691 from urfave/refactor-686 Refactor flag handling logic commit 2610681040722bb0a9d04c3a784a44d2efb52379 Merge: 0671b16 119bb65 Author: Jesse Szwedko Date: Mon Dec 11 18:51:46 2017 -0800 Merge branch 'master' into refactor-686 commit 0671b166dcacb3dc1215ba65bf986dab194581dc Author: Jesse Szwedko Date: Mon Dec 4 09:23:40 2017 -0800 Add tests for flag reordering commit 119bb6564841921ce6f1401e0f5d75317bdd9f4d Merge: c9eba3f c6eb2a0 Author: Jesse Szwedko Date: Sun Dec 3 13:42:37 2017 -0800 Merge pull request #690 from gliptak/patch-1 Correct go vet for Go tip commit df562bf1a8626f2d16f91fcbf7230a5bdca3d592 Author: Jesse Szwedko Date: Sun Dec 3 13:38:50 2017 -0800 Refactor flag handling logic Refactor logic introduced by #686 commit c9eba3f37a524c4fed60a8f3585ea5f304fd436d Merge: c6af884 ceaac7c Author: Jesse Szwedko Date: Sun Dec 3 12:48:28 2017 -0800 Merge pull request #686 from baude/shortoptionSkipArg Handle ShortOptions and SkipArgReorder commit c6eb2a051026c083d4e33591f8d6e95d5f4189dc Author: Gábor Lipták Date: Thu Nov 30 19:43:12 2017 -0500 Correct go vet for Go tip https://travis-ci.org/cloudflare/logshare/jobs/309796141#L646 commit ceaac7c9152121e6ba0f3b492b3254d61346f92a Author: baude Date: Mon Nov 20 09:32:03 2017 -0600 Handle ShortOptions and SkipArgReorder There was a bug in parsing when both ShortOptions and SkipArgReorder were being used together. Signed-off-by: baude commit c6af8847eb2b7b297d07c3ede98903e95e680ef9 Merge: 7ace96b 37b7abb Author: Jesse Szwedko Date: Mon Nov 27 19:55:04 2017 -0800 Merge pull request #687 from joshuarubin/master Don't clobber slices with EnvVar commit 37b7abb1c491c8c3630a2a98bb02a7051efbcc06 Author: Joshua Rubin Date: Tue Nov 21 15:21:31 2017 -0700 dont clobber slices with envvar Signed-off-by: Joshua Rubin commit 7ace96b43d4bdc46f81d0d1219742b2469874cf6 Merge: 44cb242 fd5382e Author: Jesse Szwedko Date: Wed Nov 15 20:56:12 2017 -0800 Merge pull request #684 from baude/shortOptionHandling Combine bool short names commit fd5382e7a539858cc19d7eed7755f7102bae5da9 Author: baude Date: Mon Nov 13 15:28:23 2017 -0600 Combine bool short names Adds the ability to allow the combination of bool short-name options. For example, cmd foobar -ov This is done through a bool "UseShortOptionHandler" set in the command struct. Built upon PR #621 Signed-off-by: baude commit 9838c8bcaa19fdb33259f6e0f9740d9fd3cbe13c Author: Jesse Szwedko Date: Sat Nov 11 16:23:24 2017 -0800 Update README examples to check for errors To encourage good practices. commit 43c8c02cf5a10196e5a4c458fdbfee90a561e97c Author: zhuchensong Date: Mon Apr 17 00:47:04 2017 +0800 Support POSIX-style short flag combining commit 44cb242eeb4d76cc813fdc69ba5c4b224677e799 Merge: 7f4b273 f971fca Author: Jesse Szwedko Date: Fri Nov 3 19:35:40 2017 -0700 Merge pull request #675 from jmccann/continue3 Ability to load variable from file - UPDATED commit f971fca2b2664c4dec0cee24225dc3c415211498 Author: Jacob McCann Date: Thu Oct 26 13:08:03 2017 -0500 Allow FilePath to take []string commit 18a556e1927fbe11c31fae47a7e3acf275ef6ae4 Author: Brad Rydzewski Date: Mon Apr 10 16:45:51 2017 +0200 fix FilePath documentation in README.md commit 4cc453ba6792515a8013340f8919e6c4b44851b7 Author: Brad Rydzewski Date: Sat Apr 1 12:55:46 2017 +0900 document field in README commit c698b821b896e9723d53c4ad1e81680f39a8cdc1 Author: Brad Rydzewski Date: Sat Apr 1 12:37:06 2017 +0900 unit tests for load from file commit 21fcab0dee7dab6969e929cf1740306bae1e16ad Author: Brad Rydzewski Date: Fri Mar 31 16:24:15 2017 +0900 ability to load variable from file commit 7f4b273a05858e05b96b6adf0a7907b7b695c352 Merge: 7bc6a0a b44660a Author: Jesse Szwedko Date: Mon Oct 30 19:55:34 2017 -0700 Merge pull request #676 from rliebz/lexicographic-sort Consider case when sorting strings commit b44660ac3da2f8e651372c40ae803782bddea283 Author: Robert Liebowitz Date: Sat Oct 28 03:00:11 2017 -0400 Consider case when sorting strings This makes sorting flags and other sections consistent with how most command line tools function, by placing both flags `-A` and `-a` before a flag `-B`. commit 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c Merge: 2997500 40263f4 Author: Jesse Szwedko Date: Sat Oct 14 13:27:26 2017 -0700 Merge pull request #628 from phinnaeus/master Allow custom ExitError handler function commit 40263f4d6aaf7383fb53bd97a5c34c02be39eda8 Merge: 7233c50 2997500 Author: Tyler Davis Date: Fri Oct 13 12:05:14 2017 -0700 Merge branch 'master' into master commit 2997500ba5f393daa5d76c18544c6367b2c48d16 Merge: ac24947 c202606 Author: Jesse Szwedko Date: Sat Oct 7 13:56:23 2017 -0700 Merge pull request #672 from sierraechobravo/master fix go report card issues commit c202606a17a763fcc1b320cac6cf584662e31364 Author: Sebastian Sprenger Date: Fri Oct 6 07:29:13 2017 +0200 fix golint issues commit c3cc74dac756e33c2919ab998481809e8720e068 Author: Sebastian Sprenger Date: Fri Oct 6 07:28:43 2017 +0200 fix ineffective assigns commit 67ee172e6da2cdad8e48af107eef0fbfd1e85eec Author: Sebastian Sprenger Date: Fri Oct 6 07:28:18 2017 +0200 fix misspelling issue commit ac249472b7de27a9e8990819566d9be95ab5b816 Merge: 7fb9c86 cbbe4c1 Author: Jesse Szwedko Date: Mon Sep 25 20:41:18 2017 -0700 Merge pull request #661 from rliebz/custom-flag-help Allow customization of prefixes and environment variable hints in flag help strings commit 7233c502e31b3e6d93e3e8cf5fe0616f4d32c020 Merge: 5dc55f2 7fb9c86 Author: Tyler Davis Date: Fri Sep 22 10:08:36 2017 -0700 Merge branch 'master' into master commit cbbe4c1a2c34e52c8ad0937c01c9c15ef407a6d5 Author: Robert Liebowitz Date: Mon Sep 18 00:44:42 2017 -0400 Add tests for custom flag prefix/env hints commit 11d45572f9727acfbc93daa8565f379d396125d6 Author: rliebz Date: Sat Aug 26 07:42:25 2017 -0400 Export funcs to configure flag prefix/env hints This will allow users to customize the prefix section or env hint section of the flag entries in the help menu without having to reimplement the rest of the logic required in defining FlagStringer. commit 7fb9c86b14e6a702a4157ccb5a863f07d844a207 Merge: f017f86 1d334f1 Author: Jesse Szwedko Date: Sun Sep 10 21:08:19 2017 -0700 Merge pull request #664 from maguro/master Add newline before command categories commit 1d334f10ce73c2b9e65c50a2290a86be3c743ff2 Author: Alan D. Cabrera Date: Fri Sep 8 10:37:48 2017 -0700 Add newline before command categories The simple formatting change adds a nice blank line before each command category. Documentation in README.md is also updated to be more accurate. commit 5dc55f22878a35487bdc17393f391bf25142c6e3 Merge: 10e81ba f017f86 Author: Dan Buch Date: Sun Aug 13 12:42:49 2017 -0400 Merge branch 'master' into master commit f017f86fccc5a039a98f23311f34fdf78b014f78 Merge: cfb3883 44c6487 Author: Dan Buch Date: Sun Aug 13 10:59:49 2017 -0400 Merge pull request #659 from urfave/define-flag-precedence Define flag source precedence in README commit 44c648739b75283265541baca66ed984476a17f5 Merge: e1fa109 cfb3883 Author: Dan Buch Date: Sun Aug 13 10:54:04 2017 -0400 Merge branch 'master' into define-flag-precedence commit cfb38830724cc34fedffe9a2a29fb54fa9169cd1 Author: Jesse Szwedko Date: Thu Aug 10 18:42:03 2017 -0700 Prepare CHANGELOG for v1.20.0 release commit f5513590f52b5f90566a10ad9452f52dffd469f8 Merge: 6a70c4c b99aa81 Author: Jesse Szwedko Date: Thu Aug 10 18:06:12 2017 -0700 Merge branch 'master' into backport-json-support commit e1fa109a3195a9fedcb635841ca1907b764ada1f Author: Jesse Szwedko Date: Thu Aug 10 17:54:24 2017 -0700 Define flag source precedence in README Fixes #646 commit 688c5a9d4f3beffff9d4fa50bd85907b7067d9a4 Merge: 7250c97 4b90d79 Author: Dan Buch Date: Thu Aug 3 14:38:20 2017 -0400 Merge branch 'master' into merging-jereksel-zsh commit 10e81bacd12f5c5a44d3a3e2d6e168d0c2533245 Merge: 5d528e2 4b90d79 Author: Tyler Davis Date: Thu Jul 20 12:44:56 2017 -0700 Merge branch 'master' into master commit 5d528e2052b3e7a49293d6aa0fac245047ea61e3 Author: Tyler Davis Date: Wed Jun 28 13:04:09 2017 -0700 use exit errors in uts commit 58450552ee1bada60f4175897aff8d69f7c904a1 Author: Tyler Davis Date: Wed Jun 28 12:52:50 2017 -0700 Add Test commit 71bdf81f5a65dc253482cb727c2ae973ae3b3830 Author: Tyler Davis Date: Wed Jun 28 10:10:11 2017 -0700 sigh... fix one more named parameter issue commit 172bb92059ed885c8b4249230f3ccbe9e3e1272b Author: Tyler Davis Date: Wed Jun 28 10:07:25 2017 -0700 fix named parameter issue commit 530df59178874f8d792d2d9cfd745464076f1eda Author: Tyler Davis Date: Wed Jun 28 09:52:12 2017 -0700 Pass context into handleExitCoder commit 9d61cbad0260bc7f2a72b07142a0120072e3800a Author: Tyler Davis Date: Tue Apr 25 12:45:08 2017 -0700 Updated command.go to use App handleExitCoder commit ceee6408d5cbbb9f113157d0a62b1ffed1f2b510 Author: Tyler Davis Date: Tue Apr 25 13:02:05 2017 -0700 Revert "Fix how to do defaults in app.go" This reverts commit 8906567dc2ad52fd31c50cf02fa606505a1323ba. commit 80b09a4d1117ad69430582685e59dfe560caa948 Author: Tyler Davis Date: Tue Apr 25 11:20:41 2017 -0700 Fix how to do defaults in app.go commit 827da610b4bff0ffbc06cd2d92eddae552f7d1a2 Author: Tyler Davis Date: Tue Apr 25 09:33:54 2017 -0700 Add a bit more documentation commit 538742687bbd979a7b4f975468af76ce5cffb972 Author: Tyler Davis Date: Tue Apr 25 09:31:53 2017 -0700 Add ExitErrHandlerFunc type commit c48a82964028acd0f19ee17257789f7c9f5afc78 Author: Tyler Davis Date: Tue Apr 25 09:29:43 2017 -0700 Allow custom exit err handlers commit 6a70c4cc923c7359bacfa0500dc234d62e0ca986 Author: John Weldon Date: Sat Jul 2 12:35:48 2016 -0700 Add JSON InputSource to altsrc package - Implement NewJSONSource* functions for returning an InputSource from various JSON data sources. - Copy and modify YAML tests for the JSON InputSource Changes: * Reverted the method calls and structs to match the v1 interface commit 7250c97913c213f17c721cb3fac5e2f555b198ca Merge: 363d9c9 0bdedde Author: Dan Buch Date: Wed Dec 21 15:11:00 2016 -0500 Merge branch 'master' into merging-jereksel-zsh commit 363d9c9a314cdb9ed68cad1a27c767b45eee8840 Author: Dan Buch Date: Sun Jul 24 17:29:13 2016 -0400 Add a hack so that zsh completion only runs for zsh commit 1cbb9a7f300b11a8e5a92b1fb24d8aeb168e0275 Merge: e43a9fb ceeebab Author: Dan Buch Date: Sun Jul 24 17:12:43 2016 -0400 Merge branch 'zsh' of https://github.com/jereksel/cli into jereksel-zsh commit ceeebaba04790bab2ecd03caded488528b0caf97 Author: Andrzej Ressel Date: Thu Jul 21 00:02:16 2016 +0200 [PoC] Improve zsh autocompletions commit aba73cedacbb7b1cec2efb9962460683cd00a90c Author: jhowarth Date: Tue Mar 3 14:02:42 2015 -0800 Copy the writer of the App to the subcommand App commit a6482d268753644175e769dd91ca3a4dfe838964 Merge: b5844af 50c77ec Author: jhowarth Date: Mon Mar 2 15:21:01 2015 -0800 Merge remote-tracking branch 'upstream/master' Conflicts: app.go command.go flag.go commit b5844af29892a881ea1d22ed0082f1e0a1559bfa Merge: 8f1fb06 145da32 Author: Jesse Howarth Date: Mon Mar 2 14:53:57 2015 -0800 Merge pull request #2 from ivey/requiredFlags Required flags commit 145da3210f41f401b1f42a08385d11ee8a80ec97 Author: jhowarth Date: Mon Mar 2 12:06:42 2015 -0800 don't require flags when the help flag is included commit 6023f370c1dfea78d4ff99a6ecc6be261347bfc9 Author: jhowarth Date: Mon Mar 2 12:00:21 2015 -0800 dry error messages commit e67e05f617978eec7bba579a6c86f3d0c11ad96b Author: jhowarth Date: Mon Mar 2 11:56:29 2015 -0800 DRY error handling commit cbd95292ac9c4ba7eb30ca121fbe3825ced64f72 Author: jhowarth Date: Mon Mar 2 11:18:59 2015 -0800 Remove debugging commit 8f1fb06a585610fdb76d38bc67a5edc89da4e82f Merge: 9908e96 4b2fcdb Author: Jesse Howarth Date: Tue Dec 2 15:23:01 2014 -0800 Merge pull request #1 from ivey/required_flags Required flags commit 4b2fcdb1ade79300c56074de8e7a7bf754cd407e Author: Jesse Howarth and Michael Ivey Date: Tue Dec 2 21:08:24 2014 +0000 Add tests for required flags commit 73e64a14fde90fc3e85fdebb3647af6024e48de0 Author: Jesse Howarth and Michael Ivey Date: Tue Dec 2 19:02:56 2014 +0000 Add (required) to help of flags that are required. commit 7e0532002650b69f219f34f4614656261be45363 Author: Jesse Howarth and Michael Ivey Date: Tue Dec 2 17:44:55 2014 +0000 Implement required flags --- .github/CODEOWNERS | 4 + .gitignore | 1 + .travis.yml | 41 +- CHANGELOG.md | 111 +++- CODE_OF_CONDUCT.md | 74 +++ CONTRIBUTING.md | 18 + README.md | 366 +++++++++--- altsrc/altsrc.go | 3 - altsrc/flag.go | 16 +- altsrc/flag_generated.go | 207 ++----- altsrc/flag_test.go | 29 +- altsrc/input_source_context.go | 3 +- altsrc/json_command_test.go | 29 +- altsrc/json_source_context.go | 18 +- altsrc/map_input_source.go | 17 +- altsrc/toml_command_test.go | 63 +- altsrc/toml_file_loader.go | 10 +- altsrc/yaml_command_test.go | 47 +- altsrc/yaml_file_loader.go | 6 +- app.go | 195 +++++-- app_test.go | 598 +++++++++++++++++-- appveyor.yml | 29 +- autocomplete/bash_autocomplete | 11 +- autocomplete/zsh_autocomplete | 14 +- build.go | 164 ++++++ category.go | 6 +- cli.go | 2 +- command.go | 52 +- command_test.go | 155 ++++- context.go | 170 ++++-- context_test.go | 286 ++++++++- docs.go | 148 +++++ docs_test.go | 122 ++++ errors.go | 22 +- fish.go | 194 +++++++ fish_test.go | 17 + flag-types.json | 98 ---- flag.go | 828 ++++----------------------- flag_bool.go | 107 ++++ flag_duration.go | 104 ++++ flag_float64.go | 106 ++++ flag_float64_slice.go | 164 ++++++ flag_generated.go | 629 -------------------- flag_generic.go | 104 ++++ flag_int.go | 103 ++++ flag_int64.go | 103 ++++ flag_int64_slice.go | 164 ++++++ flag_int_slice.go | 175 ++++++ flag_path.go | 85 +++ flag_string.go | 95 +++ flag_string_slice.go | 155 +++++ flag_test.go | 546 +++++++++++------- flag_uint.go | 104 ++++ flag_uint64.go | 104 ++++ funcs.go | 20 +- generate-flag-types | 255 --------- go.mod | 9 + go.sum | 14 + help.go | 126 +++- help_test.go | 46 +- helpers_unix_test.go | 9 - helpers_windows_test.go | 20 - parse.go | 80 +++ runtests | 172 ------ sort.go | 29 + sort_test.go | 30 + template.go | 121 ++++ testdata/expected-doc-full.man | 80 +++ testdata/expected-doc-full.md | 62 ++ testdata/expected-doc-no-commands.md | 36 ++ testdata/expected-doc-no-flags.md | 47 ++ testdata/expected-fish-full.fish | 28 + 72 files changed, 5468 insertions(+), 2738 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md delete mode 100644 altsrc/altsrc.go create mode 100644 build.go create mode 100644 docs.go create mode 100644 docs_test.go create mode 100644 fish.go create mode 100644 fish_test.go delete mode 100644 flag-types.json create mode 100644 flag_bool.go create mode 100644 flag_duration.go create mode 100644 flag_float64.go create mode 100644 flag_float64_slice.go delete mode 100644 flag_generated.go create mode 100644 flag_generic.go create mode 100644 flag_int.go create mode 100644 flag_int64.go create mode 100644 flag_int64_slice.go create mode 100644 flag_int_slice.go create mode 100644 flag_path.go create mode 100644 flag_string.go create mode 100644 flag_string_slice.go create mode 100644 flag_uint.go create mode 100644 flag_uint64.go delete mode 100755 generate-flag-types create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 helpers_unix_test.go delete mode 100644 helpers_windows_test.go create mode 100644 parse.go delete mode 100755 runtests create mode 100644 sort.go create mode 100644 sort_test.go create mode 100644 template.go create mode 100644 testdata/expected-doc-full.man create mode 100644 testdata/expected-doc-full.md create mode 100644 testdata/expected-doc-no-commands.md create mode 100644 testdata/expected-doc-no-flags.md create mode 100644 testdata/expected-fish-full.fish diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4f138e8 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# See https://help.github.com/articles/about-codeowners/ +# for more info about CODEOWNERS file + +* @urfave/cli diff --git a/.gitignore b/.gitignore index faf70c4..7a7e2d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.coverprofile node_modules/ +vendor \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 58ed292..e500b38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,35 @@ language: go sudo: false -dist: trusty -osx_image: xcode8.3 -go: 1.11.x +dist: bionic +osx_image: xcode10 +go: + - 1.11.x + - 1.12.x + - 1.13.x os: -- linux -- osx + - linux + - osx + +env: + GO111MODULE=on + GOPROXY=https://proxy.golang.org cache: directories: - - node_modules + - node_modules before_script: -- if [[ $(uname) == Darwin ]]; then - sudo pip2 install flake8; - else - pip install --user flake8; - fi -- mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave -- rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 -- rm -rvf ${GOPATH%%:*}/pkg/*/gopkg.in/urfave/cli.v2.a -- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 + - go get github.com/urfave/gfmrun/cmd/gfmrun + - go get golang.org/x/tools/cmd/goimports + - npm install markdown-toc + - go mod tidy script: -- flake8 runtests cli-v1-to-v2 generate-flag-types -- make all + - go run build.go vet + - go run build.go test + - go run build.go gfmrun + - go run build.go toc + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa0ca0..d1b5c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,11 +39,110 @@ ### Removed +## [1.22.1] - 2019-09-11 + ### Fixed -### Deprecated +* Hide output of hidden commands on man pages in [urfave/cli/pull/889](https://github.com/urfave/cli/pull/889) via [@crosbymichael](https://github.com/crosbymichael) +* Don't generate fish completion for hidden commands [urfave/cli/pull/891](https://github.com/urfave/891) via [@saschagrunert](https://github.com/saschagrunert) +* Using short flag names for required flags throws an error in [urfave/cli/pull/890](https://github.com/urfave/cli/pull/890) via [@asahasrabuddhe](https://github.com/asahasrabuddhe) -### Security +### Changed + +* Remove flag code generation logic, legacy python test runner in [urfave/cli/pull/883](https://github.com/urfave/cli/pull/883) via [@asahasrabuddhe](https://github.com/asahasrabuddhe) +* Enable Go Modules support, drop support for `Go 1.10` add support for `Go 1.13` in [urfave/cli/pull/885](https://github.com/urfave/cli/pull/885) via [@asahasrabuddhe](https://github.com/asahasrabuddhe) + +## [1.22.0] - 2019-09-07 + +### Fixed + +* Fix Subcommands not falling back to `app.ExitEventHandler` in [urfave/cli/pull/856](https://github.com/urfave/cli/pull/856) via [@FaranIdo](https://github.com/FaranIdo) + +### Changed + +* Clarify that altsrc supports both TOML and JSON in [urfave/cli/pull/774](https://github.com/urfave/cli/pull/774) via [@whereswaldon](https://github.com/whereswaldon) +* Made the exit code example more clear in [urfave/cli/pull/823](https://github.com/urfave/cli/pull/823) via [@xordspar0](https://github.com/xordspar0) +* Removed the use of python for internal flag generation in [urfave/cli/pull/836](https://github.com/urfave/cli/pull/836) via [@asahasrabuddhe](https://github.com/asahasrabuddhe) +* Changed the supported go versions to `1.10`, `1.11`, `1.12` in [urfave/cli/pull/843](https://github.com/urfave/cli/pull/843) via [@lafriks](https://github.com/lafriks) +* Changed the v1 releases section in the readme in [urfave/cli/pull/862](https://github.com/urfave/cli/pull/862) via [@russoj88](https://github.com/russoj88) +* Cleaned up go modules in [urfave/cli/pull/874](https://github.com/urfave/cli/pull/874) via [@saschagrunert](https://github.com/saschagrunert) + +### Added + +* Added `UseShortOptionHandling` for combining short flags in [urfave/cli/pull/735](https://github.com/urfave/cli/pull/735) via [@rliebz](https://github.com/rliebz) +* Added support for flags bash completion in [urfave/cli/pull/808](https://github.com/urfave/cli/pull/808) via [@yogeshlonkar](https://github.com/yogeshlonkar) +* Added the `TakesFile` indicator to flag in [urfave/cli/pull/851](https://github.com/urfave/cli/pull/851) via [@saschagrunert](https://github.com/saschagrunert) +* Added fish shell completion support in [urfave/cli/pull/848](https://github.com/urfave/cli/pull/848) via [@saschagrunert](https://github.com/saschagrunert) + +## [1.21.0] - 2019-08-02 + +### Fixed + +* Fix using "slice" flag types with `EnvVar` in [urfave/cli/pull/687](https://github.com/urfave/cli/pull/687) via [@joshuarubin](https://github.com/joshuarubin) +* Fix regression of `SkipFlagParsing` behavior in [urfave/cli/pull/697](https://github.com/urfave/cli/pull/697) via [@jszwedko](https://github.com/jszwedko) +* Fix handling `ShortOptions` and `SkipArgReorder` in [urfave/cli/pull/686](https://github.com/urfave/cli/pull/686) via [@baude](https://github.com/baude) +* Fix args reordering when bool flags are present in [urfave/cli/pull/712](https://github.com/urfave/cli/pull/712) via [@windler](https://github.com/windler) +* Fix parsing of short options in [urfave/cli/pull/758](https://github.com/urfave/cli/pull/758) via [@vrothberg](https://github.com/vrothberg) +* Fix unaligned indents for the command help messages in [urfave/cli/pull/806](https://github.com/urfave/cli/pull/806) via [@mingrammer](https://github.com/mingrammer) + +### Changed + +* Cleaned up help output in [urfave/cli/pull/664](https://github.com/urfave/cli/pull/664) via [@maguro](https://github.com/maguro) +* Remove redundant nil checks in [urfave/cli/pull/773](https://github.com/urfave/cli/pull/773) via [@teresy](https://github.com/teresy) +* Case is now considered when sorting strings in [urfave/cli/pull/676](https://github.com/urfave/cli/pull/676) via [@rliebz](https://github.com/rliebz) + +### Added + +* Added _"required flags"_ support in [urfave/cli/pull/819](https://github.com/urfave/cli/pull/819) via [@lynncyrin](https://github.com/lynncyrin/) +* Backport JSON `InputSource` to v1 in [urfave/cli/pull/598](https://github.com/urfave/cli/pull/598) via [@jszwedko](https://github.com/jszwedko) +* Allow more customization of flag help strings in [urfave/cli/pull/661](https://github.com/urfave/cli/pull/661) via [@rliebz](https://github.com/rliebz) +* Allow custom `ExitError` handler function in [urfave/cli/pull/628](https://github.com/urfave/cli/pull/628) via [@phinnaeus](https://github.com/phinnaeus) +* Allow loading a variable from a file in [urfave/cli/pull/675](https://github.com/urfave/cli/pull/675) via [@jmccann](https://github.com/jmccann) +* Allow combining short bool names in [urfave/cli/pull/684](https://github.com/urfave/cli/pull/684) via [@baude](https://github.com/baude) +* Added test coverage to context in [urfave/cli/pull/788](https://github.com/urfave/cli/pull/788) via [@benzvan](https://github.com/benzvan) +* Added go module support in [urfave/cli/pull/831](https://github.com/urfave/cli/pull/831) via [@saschagrunert](https://github.com/saschagrunert) + +## [1.20.0] - 2017-08-10 + +### Fixed + +* `HandleExitCoder` is now correctly iterates over all errors in + a `MultiError`. The exit code is the exit code of the last error or `1` if + there are no `ExitCoder`s in the `MultiError`. +* Fixed YAML file loading on Windows (previously would fail validate the file path) +* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly + propogated +* `ErrWriter` is now passed downwards through command structure to avoid the + need to redefine it +* Pass `Command` context into `OnUsageError` rather than parent context so that + all fields are avaiable +* Errors occuring in `Before` funcs are no longer double printed +* Use `UsageText` in the help templates for commands and subcommands if + defined; otherwise build the usage as before (was previously ignoring this + field) +* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if + a program calls `Set` or `GlobalSet` directly after flag parsing (would + previously only return `true` if the flag was set during parsing) + +### Changed + +* No longer exit the program on command/subcommand error if the error raised is + not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was + determined to be a regression in functionality. See [the + PR](https://github.com/urfave/cli/pull/595) for discussion. + +### Added + +* `CommandsByName` type was added to make it easy to sort `Command`s by name, + alphabetically +* `altsrc` now handles loading of string and int arrays from TOML +* Support for definition of custom help templates for `App` via + `CustomAppHelpTemplate` +* Support for arbitrary key/value fields on `App` to be used with + `CustomAppHelpTemplate` via `ExtraInfo` +* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be + `cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag` + interface to be used. ## [1.19.1] - 2016-11-21 ### Fixed @@ -403,7 +502,13 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD +[Unreleased]: https://github.com/urfave/cli/compare/v1.22.1...HEAD +[1.22.1]: https://github.com/urfave/cli/compare/v1.22.0...v1.22.1 +[1.22.0]: https://github.com/urfave/cli/compare/v1.21.0...v1.22.0 +[1.21.0]: https://github.com/urfave/cli/compare/v1.20.0...v1.21.0 +[1.20.0]: https://github.com/urfave/cli/compare/v1.19.1...v1.20.0 +[1.19.1]: https://github.com/urfave/cli/compare/v1.19.0...v1.19.1 +[1.19.0]: https://github.com/urfave/cli/compare/v1.18.0...v1.19.0 [1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0 [1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 [1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..41ba294 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be +reviewed and investigated and will result in a response that is deemed necessary +and appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9a4640a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +## Contributing + +Use @urfave/cli to ping the maintainers. + +Feel free to put up a pull request to fix a bug or maybe add a feature. We will +give it a code review and make sure that it does not break backwards +compatibility. If collaborators agree that it is in line with +the vision of the project, we will work with you to get the code into +a mergeable state and merge it into the master branch. + +If you have contributed something significant to the project, we will most +likely add you as a collaborator. As a collaborator you are given the ability +to merge others pull requests. It is very important that new code does not +break existing code, so be careful about what code you do choose to merge. + +If you feel like you have contributed to the project but have not yet been added +as a collaborator, we probably forgot to add you :sweat_smile:. Please open an +issue! diff --git a/README.md b/README.md index dbe1bf3..7624c48 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,11 @@ cli [![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli) + [![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) -[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / -[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) - -**Notice:** This is the library formerly known as -`github.com/codegangsta/cli` -- Github will automatically redirect requests -to this repository, but we recommend updating your references for clarity. +[![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli) cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line @@ -23,7 +19,7 @@ applications in an expressive way. - [Installation](#installation) * [Supported platforms](#supported-platforms) * [Using the `v2` branch](#using-the-v2-branch) - * [Pinning to the `v1` releases](#pinning-to-the-v1-releases) + * [Using `v1` releases](#using-v1-releases) - [Getting Started](#getting-started) - [Examples](#examples) * [Arguments](#arguments) @@ -32,12 +28,22 @@ applications in an expressive way. + [Alternate Names](#alternate-names) + [Ordering](#ordering) + [Values from the Environment](#values-from-the-environment) + + [Values from files](#values-from-files) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) +<<<<<<< HEAD + [Default Values for help output](#default-values-for-help-output) * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) * [Exit code](#exit-code) * [Shell Completion](#shell-completion) +======= + + [Precedence](#precedence) + * [Subcommands](#subcommands) + * [Subcommands categories](#subcommands-categories) + * [Exit code](#exit-code) + * [Combining short options](#combining-short-options) + * [Bash Completion](#bash-completion) +>>>>>>> master + [Enabling](#enabling) + [Distribution](#distribution) + [Customization](#customization) @@ -62,12 +68,12 @@ organized, and expressive! ## Installation -Make sure you have a working Go environment. Go version 1.2+ is supported. [See +Make sure you have a working Go environment. Go version 1.10+ is supported. [See the install instructions for Go](http://golang.org/doc/install.html). To install cli, simply run: ``` -$ go get github.com/urfave/cli +$ go get github.com/urfave/cli/v2 ``` Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can @@ -91,21 +97,21 @@ the new `master` branch once development there has settled down. The current `master` branch (mirrored as `v1`) is being manually merged into `v2` on an irregular human-based schedule, but generally if one wants to "upgrade" to `v2` *now* and accept the volatility (read: "awesomeness") that comes along with -that, please use whatever version pinning of your preference, such as via -`gopkg.in`: +that, please use: ``` -$ go get gopkg.in/urfave/cli.v2 +$ go get github.com/urfave/cli/v2 ``` ``` go ... import ( - "gopkg.in/urfave/cli.v2" // imports as package "cli" + "github.com/urfave/cli/v2" // imports as package "cli" ) ... ``` +<<<<<<< HEAD **NOTE**: There is a [migrator (python) script](./cli-v1-to-v2) available to aid with the transition from the v1 to v2 API. @@ -114,20 +120,22 @@ with the transition from the v1 to v2 API. Similarly to the section above describing use of the `v2` branch, if one wants to avoid any unexpected compatibility pains once `v2` becomes `master`, then pinning to `v1` is an acceptable option, e.g.: +======= +### Using `v1` releases +>>>>>>> master ``` -$ go get gopkg.in/urfave/cli.v1 +$ go get github.com/urfave/cli ``` -``` go +```go ... import ( - "gopkg.in/urfave/cli.v1" // imports as package "cli" + "github.com/urfave/cli/v2" ) ... ``` -This will pull the latest tagged `v1` release (e.g. `v1.18.1` at the time of writing). ## Getting Started @@ -142,9 +150,10 @@ discovery. So a cli app can be as little as one line of code in `main()`. package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -163,9 +172,10 @@ package main import ( "fmt" + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -178,7 +188,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -202,9 +215,10 @@ package main import ( "fmt" + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -217,7 +231,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -266,9 +283,10 @@ package main import ( "fmt" + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -279,7 +297,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -295,9 +316,10 @@ package main import ( "fmt" + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -323,7 +345,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -337,10 +362,11 @@ scanned. package main import ( + "log" "os" "fmt" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -369,7 +395,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -390,9 +419,10 @@ For example this: package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -406,7 +436,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -432,9 +465,10 @@ list for the `Name`. e.g. package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -449,7 +483,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -473,10 +510,11 @@ For example this: package main import ( + "log" "os" "sort" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func main() { @@ -515,7 +553,10 @@ func main() { sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.CommandsByName(app.Commands)) - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -538,9 +579,10 @@ You can also have the default value set from the environment via `EnvVars`. e.g package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -556,7 +598,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -571,9 +616,10 @@ resolves is used as the default. package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -589,10 +635,52 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` +#### Values from files + +You can also have the default value set from file via `FilePath`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "password, p", + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the environment (e.g. `EnvVar`). + #### Values from alternate input sources (YAML, TOML, and others) There is a separate package altsrc that adds support for getting flag values @@ -600,6 +688,7 @@ from other file input sources. Currently supported input source formats: * YAML +* JSON * TOML In order to get values for a flag from an alternate input source the following @@ -622,9 +711,9 @@ the yaml input source for any flags that are defined on that command. As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. -Currently only the aboved specified formats are supported but developers can -add support for other input sources by implementing the -altsrc.InputSourceContext for their given sources. +Currently only YAML, JSON, and TOML files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. Here is a more complete sample of a command using YAML support: @@ -637,10 +726,11 @@ package notmain import ( "fmt" + "log" "os" - "gopkg.in/urfave/cli.v2" - "gopkg.in/urfave/cli.v2/altsrc" + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" ) func main() { @@ -678,7 +768,7 @@ package main import ( "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -693,7 +783,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -703,6 +796,14 @@ Will result in help output like: --port value Use a randomized port (default: random) ``` +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag ### Subcommands @@ -717,9 +818,10 @@ package main import ( "fmt" + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -769,7 +871,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -785,9 +890,10 @@ E.g. package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -807,7 +913,10 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -815,7 +924,7 @@ Will include: ``` COMMANDS: - noop + noop Template actions: add @@ -828,14 +937,17 @@ Calling `App.Run` will not automatically call `os.Exit`, which means that by default the exit code will "fall through" to being `0`. An explicit exit code may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a `cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: - + ``` go package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -855,13 +967,86 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` -### Shell Completion +### Combining short options -You can enable completion commands by setting the `EnableShellCompletion` +Traditional use of options using their shortnames look like this: + +``` +$ cmd -s -o -m "Some message" +``` + +Suppose you want users to be able to combine options with their shortnames. This +can be done using the `UseShortOptionHandling` bool in your app configuration, +or for individual commands by attaching it to the command configuration. For +example: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{} + app.UseShortOptionHandling = true + app.Commands = []*cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "serve, s"}, + cli.BoolFlag{Name: "option, o"}, + cli.StringFlag{Name: "message, m"}, + }, + Action: func(c *cli.Context) error { + fmt.Println("serve:", c.Bool("serve")) + fmt.Println("option:", c.Bool("option")) + fmt.Println("message:", c.String("message")) + return nil + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If your program has any number of bool flags such as `serve` and `option`, and +optionally one non-bool flag `message`, with the short options of `-s`, `-o`, +and `-m` respectively, setting `UseShortOptionHandling` will also support the +following syntax: + +``` +$ cmd -som "Some message" +``` + +If you enable `UseShortOptionHandling`, then you must not use any flags that +have a single leading `-` or this will result in failures. For example, +`-option` can no longer be used. Flags with two leading dashes (such as +`--options`) are still valid. + +### Bash Completion + +You can enable completion commands by setting the `EnableBashCompletion` flag on the `App` object. By default, this setting will only auto-complete to show an app's subcommands, but you can write your own completion methods for the App or its subcommands. @@ -875,16 +1060,17 @@ package main import ( "fmt" + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} app := &cli.App{ - EnableShellCompletion: true, + EnableBashCompletion: true, Commands: []*cli.Command{ { Name: "complete", @@ -894,7 +1080,7 @@ func main() { fmt.Println("completed task: ", c.Args().First()) return nil }, - ShellComplete: func(c *cli.Context) { + BashComplete: func(c *cli.Context) { // This will complete if no args are passed if c.NArg() > 0 { return @@ -907,23 +1093,19 @@ func main() { }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` #### Enabling -You can generate bash or zsh completion code by using the flag `--init-completion bash` or `--init-completion zsh`. - -To setup for bash: +Source the `autocomplete/bash_autocomplete` file in your .bashrc file while setting the `PROG` variable to the name of your program: ``` -eval "`myprogram --init-completion bash`" -``` - -Alternatively, you can put the completion code in your `.bashrc` file: -``` -myprogram --init-completion bash >> ~/.bashrc +PROG=myprogram source /.../cli/autocomplete/bash_autocomplete ``` #### Distribution @@ -955,9 +1137,10 @@ The default shell completion flag (`--generate-completion`) is defined as package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -974,7 +1157,10 @@ func main() { }, }, } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } } ``` @@ -1000,10 +1186,11 @@ package main import ( "fmt" + "log" "io" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -1042,7 +1229,7 @@ VERSION: cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { fmt.Println("Ha HA. I pwnd the help!!1") } - + (&cli.App{}).Run(os.Args) } ``` @@ -1058,9 +1245,10 @@ setting `cli.HelpFlag`, e.g.: package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -1069,7 +1257,7 @@ func main() { Usage: "HALP", EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, } - + (&cli.App{}).Run(os.Args) } ``` @@ -1093,9 +1281,10 @@ setting `cli.VersionFlag`, e.g.: package main import ( + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func main() { @@ -1103,7 +1292,7 @@ func main() { Name: "print-version", Aliases: []string{"V"}, Usage: "print only the version", } - + app := &cli.App{ Name: "partay", Version: "v19.99.0", @@ -1123,9 +1312,10 @@ package main import ( "fmt" + "log" "os" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) var ( @@ -1136,7 +1326,7 @@ func main() { cli.VersionPrinter = func(c *cli.Context) { fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) } - + app := &cli.App{ Name: "partay", Version: "v19.99.0", @@ -1165,7 +1355,7 @@ import ( "os" "time" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func init() { @@ -1217,7 +1407,7 @@ func (g *genericType) String() string { } func main() { - app := cli.App{ + app := &cli.App{ Name: "kənˈtrīv", Version: "v19.99.0", Compiled: time.Now(), @@ -1406,7 +1596,7 @@ func main() { app.Writer = &hexWriter{} app.ErrWriter = &hexWriter{} } - + app.Run(os.Args) } @@ -1418,16 +1608,4 @@ func wopAction(c *cli.Context) error { ## Contribution Guidelines -Feel free to put up a pull request to fix a bug or maybe add a feature. I will -give it a code review and make sure that it does not break backwards -compatibility. If I or any other collaborators agree that it is in line with -the vision of the project, we will work with you to get the code into -a mergeable state and merge it into the master branch. - -If you have contributed something significant to the project, we will most -likely add you as a collaborator. As a collaborator you are given the ability -to merge others pull requests. It is very important that new code does not -break existing code, so be careful about what code you do choose to merge. - -If you feel like you have contributed to the project but have not yet been -added as a collaborator, we probably forgot to add you, please open an issue. +See [./CONTRIBUTING.md](./CONTRIBUTING.md) diff --git a/altsrc/altsrc.go b/altsrc/altsrc.go deleted file mode 100644 index ac34bf6..0000000 --- a/altsrc/altsrc.go +++ /dev/null @@ -1,3 +0,0 @@ -package altsrc - -//go:generate python ../generate-flag-types altsrc -i ../flag-types.json -o flag_generated.go diff --git a/altsrc/flag.go b/altsrc/flag.go index 858e54e..31b8a04 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -6,7 +6,7 @@ import ( "strconv" "syscall" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) // FlagInputSourceExtension is an extension interface of cli.Flag that @@ -72,7 +72,7 @@ func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourc } if value != nil { for _, name := range f.Names() { - f.set.Set(name, value.String()) + _ = f.set.Set(name, value.String()) } } } @@ -135,7 +135,7 @@ func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo } if value { for _, name := range f.Names() { - f.set.Set(name, strconv.FormatBool(value)) + _ = f.set.Set(name, strconv.FormatBool(value)) } } } @@ -153,7 +153,7 @@ func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSource } if value != "" { for _, name := range f.Names() { - f.set.Set(name, value) + _ = f.set.Set(name, value) } } } @@ -181,7 +181,7 @@ func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo value = filepath.Join(filepath.Dir(basePathAbs), value) } - f.set.Set(name, value) + _ = f.set.Set(name, value) } } } @@ -199,7 +199,7 @@ func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCon } if value > 0 { for _, name := range f.Names() { - f.set.Set(name, strconv.FormatInt(int64(value), 10)) + _ = f.set.Set(name, strconv.FormatInt(int64(value), 10)) } } } @@ -217,7 +217,7 @@ func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour } if value > 0 { for _, name := range f.Names() { - f.set.Set(name, value.String()) + _ = f.set.Set(name, value.String()) } } } @@ -236,7 +236,7 @@ func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourc if value > 0 { floatStr := float64ToString(value) for _, name := range f.Names() { - f.set.Set(name, floatStr) + _ = f.set.Set(name, floatStr) } } } diff --git a/altsrc/flag_generated.go b/altsrc/flag_generated.go index 87c7c5d..de3e2b5 100644 --- a/altsrc/flag_generated.go +++ b/altsrc/flag_generated.go @@ -2,12 +2,9 @@ package altsrc import ( "flag" - - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) -// WARNING: This file is generated! - // BoolFlag is the flag type that wraps cli.BoolFlag to allow // for other values to be specified type BoolFlag struct { @@ -20,18 +17,11 @@ func NewBoolFlag(fl *cli.BoolFlag) *BoolFlag { return &BoolFlag{BoolFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped BoolFlag.Apply -func (f *BoolFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped BoolFlag.Apply +func (f *BoolFlag) Apply(set *flag.FlagSet) error { f.set = set - f.BoolFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped BoolFlag.ApplyWithError -func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.BoolFlag.ApplyWithError(set) + return f.BoolFlag.Apply(set) } // DurationFlag is the flag type that wraps cli.DurationFlag to allow @@ -46,18 +36,11 @@ func NewDurationFlag(fl *cli.DurationFlag) *DurationFlag { return &DurationFlag{DurationFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped DurationFlag.Apply -func (f *DurationFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped DurationFlag.Apply +func (f *DurationFlag) Apply(set *flag.FlagSet) error { f.set = set - f.DurationFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped DurationFlag.ApplyWithError -func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.DurationFlag.ApplyWithError(set) + return f.DurationFlag.Apply(set) } // Float64Flag is the flag type that wraps cli.Float64Flag to allow @@ -72,18 +55,11 @@ func NewFloat64Flag(fl *cli.Float64Flag) *Float64Flag { return &Float64Flag{Float64Flag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped Float64Flag.Apply -func (f *Float64Flag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped Float64Flag.Apply +func (f *Float64Flag) Apply(set *flag.FlagSet) error { f.set = set - f.Float64Flag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped Float64Flag.ApplyWithError -func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.Float64Flag.ApplyWithError(set) + return f.Float64Flag.Apply(set) } // GenericFlag is the flag type that wraps cli.GenericFlag to allow @@ -98,18 +74,11 @@ func NewGenericFlag(fl *cli.GenericFlag) *GenericFlag { return &GenericFlag{GenericFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped GenericFlag.Apply -func (f *GenericFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped GenericFlag.Apply +func (f *GenericFlag) Apply(set *flag.FlagSet) error { f.set = set - f.GenericFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped GenericFlag.ApplyWithError -func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.GenericFlag.ApplyWithError(set) + return f.GenericFlag.Apply(set) } // Int64Flag is the flag type that wraps cli.Int64Flag to allow @@ -124,18 +93,11 @@ func NewInt64Flag(fl *cli.Int64Flag) *Int64Flag { return &Int64Flag{Int64Flag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped Int64Flag.Apply -func (f *Int64Flag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped Int64Flag.Apply +func (f *Int64Flag) Apply(set *flag.FlagSet) error { f.set = set - f.Int64Flag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped Int64Flag.ApplyWithError -func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.Int64Flag.ApplyWithError(set) + return f.Int64Flag.Apply(set) } // IntFlag is the flag type that wraps cli.IntFlag to allow @@ -150,18 +112,11 @@ func NewIntFlag(fl *cli.IntFlag) *IntFlag { return &IntFlag{IntFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped IntFlag.Apply -func (f *IntFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped IntFlag.Apply +func (f *IntFlag) Apply(set *flag.FlagSet) error { f.set = set - f.IntFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped IntFlag.ApplyWithError -func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.IntFlag.ApplyWithError(set) + return f.IntFlag.Apply(set) } // IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow @@ -176,18 +131,11 @@ func NewIntSliceFlag(fl *cli.IntSliceFlag) *IntSliceFlag { return &IntSliceFlag{IntSliceFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped IntSliceFlag.Apply -func (f *IntSliceFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped IntSliceFlag.Apply +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { f.set = set - f.IntSliceFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped IntSliceFlag.ApplyWithError -func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.IntSliceFlag.ApplyWithError(set) + return f.IntSliceFlag.Apply(set) } // Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow @@ -202,18 +150,11 @@ func NewInt64SliceFlag(fl *cli.Int64SliceFlag) *Int64SliceFlag { return &Int64SliceFlag{Int64SliceFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped Int64SliceFlag.Apply -func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped Int64SliceFlag.Apply +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { f.set = set - f.Int64SliceFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped Int64SliceFlag.ApplyWithError -func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.Int64SliceFlag.ApplyWithError(set) + return f.Int64SliceFlag.Apply(set) } // Float64SliceFlag is the flag type that wraps cli.Float64SliceFlag to allow @@ -230,16 +171,9 @@ func NewFloat64SliceFlag(fl *cli.Float64SliceFlag) *Float64SliceFlag { // Apply saves the flagSet for later usage calls, then calls the // wrapped Float64SliceFlag.Apply -func (f *Float64SliceFlag) Apply(set *flag.FlagSet) { +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { f.set = set - f.Float64SliceFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped Float64SliceFlag.ApplyWithError -func (f *Float64SliceFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.Float64SliceFlag.ApplyWithError(set) + return f.Float64SliceFlag.Apply(set) } // StringFlag is the flag type that wraps cli.StringFlag to allow @@ -254,18 +188,11 @@ func NewStringFlag(fl *cli.StringFlag) *StringFlag { return &StringFlag{StringFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped StringFlag.Apply -func (f *StringFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped StringFlag.Apply +func (f *StringFlag) Apply(set *flag.FlagSet) error { f.set = set - f.StringFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped StringFlag.ApplyWithError -func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.StringFlag.ApplyWithError(set) + return f.StringFlag.Apply(set) } // PathFlag is the flag type that wraps cli.PathFlag to allow @@ -282,16 +209,9 @@ func NewPathFlag(fl *cli.PathFlag) *PathFlag { // Apply saves the flagSet for later usage calls, then calls the // wrapped PathFlag.Apply -func (f *PathFlag) Apply(set *flag.FlagSet) { +func (f *PathFlag) Apply(set *flag.FlagSet) error { f.set = set - f.PathFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped PathFlag.ApplyWithError -func (f *PathFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.PathFlag.ApplyWithError(set) + return f.PathFlag.Apply(set) } // StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow @@ -306,18 +226,11 @@ func NewStringSliceFlag(fl *cli.StringSliceFlag) *StringSliceFlag { return &StringSliceFlag{StringSliceFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped StringSliceFlag.Apply -func (f *StringSliceFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped StringSliceFlag.Apply +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { f.set = set - f.StringSliceFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped StringSliceFlag.ApplyWithError -func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.StringSliceFlag.ApplyWithError(set) + return f.StringSliceFlag.Apply(set) } // Uint64Flag is the flag type that wraps cli.Uint64Flag to allow @@ -332,18 +245,11 @@ func NewUint64Flag(fl *cli.Uint64Flag) *Uint64Flag { return &Uint64Flag{Uint64Flag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped Uint64Flag.Apply -func (f *Uint64Flag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped Uint64Flag.Apply +func (f *Uint64Flag) Apply(set *flag.FlagSet) error { f.set = set - f.Uint64Flag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped Uint64Flag.ApplyWithError -func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.Uint64Flag.ApplyWithError(set) + return f.Uint64Flag.Apply(set) } // UintFlag is the flag type that wraps cli.UintFlag to allow @@ -358,16 +264,9 @@ func NewUintFlag(fl *cli.UintFlag) *UintFlag { return &UintFlag{UintFlag: fl, set: nil} } -// Apply saves the flagSet for later usage calls, then calls the -// wrapped UintFlag.Apply -func (f *UintFlag) Apply(set *flag.FlagSet) { +// Apply saves the flagSet for later usage calls, then calls +// the wrapped UintFlag.Apply +func (f *UintFlag) Apply(set *flag.FlagSet) error { f.set = set - f.UintFlag.Apply(set) -} - -// ApplyWithError saves the flagSet for later usage calls, then calls the -// wrapped UintFlag.ApplyWithError -func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error { - f.set = set - return f.UintFlag.ApplyWithError(set) + return f.UintFlag.Apply(set) } diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 087e607..b5fcbf5 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -3,13 +3,12 @@ package altsrc import ( "flag" "fmt" + "github.com/urfave/cli/v2" "os" "runtime" "strings" "testing" "time" - - "gopkg.in/urfave/cli.v2" ) type testApplyInputSource struct { @@ -252,30 +251,30 @@ func TestDurationApplyInputSourceMethodSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", - MapValue: time.Duration(30 * time.Second), + MapValue: 30 * time.Second, }) - expect(t, time.Duration(30*time.Second), c.Duration("test")) + expect(t, 30*time.Second, c.Duration("test")) } func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", - MapValue: time.Duration(30 * time.Second), - ContextValueString: time.Duration(15 * time.Second).String(), + MapValue: 30 * time.Second, + ContextValueString: (15 * time.Second).String(), }) - expect(t, time.Duration(15*time.Second), c.Duration("test")) + expect(t, 15*time.Second, c.Duration("test")) } func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", - MapValue: time.Duration(30 * time.Second), + MapValue: 30 * time.Second, EnvVarName: "TEST", - EnvVarValue: time.Duration(15 * time.Second).String(), + EnvVarValue: (15 * time.Second).String(), }) - expect(t, time.Duration(15*time.Second), c.Duration("test")) + expect(t, 15*time.Second, c.Duration("test")) } func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { @@ -316,19 +315,19 @@ func runTest(t *testing.T, test testApplyInputSource) *cli.Context { set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError) c := cli.NewContext(nil, set, nil) if test.EnvVarName != "" && test.EnvVarValue != "" { - os.Setenv(test.EnvVarName, test.EnvVarValue) + _ = os.Setenv(test.EnvVarName, test.EnvVarValue) defer os.Setenv(test.EnvVarName, "") } test.Flag.Apply(set) if test.ContextValue != nil { - flag := set.Lookup(test.FlagName) - flag.Value = test.ContextValue + f := set.Lookup(test.FlagName) + f.Value = test.ContextValue } if test.ContextValueString != "" { - set.Set(test.FlagName, test.ContextValueString) + _ = set.Set(test.FlagName, test.ContextValueString) } - test.Flag.ApplyInputSourceValue(c, inputSource) + _ = test.Flag.ApplyInputSourceValue(c, inputSource) return c } diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index bb0afdb..5f60574 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -1,9 +1,8 @@ package altsrc import ( + "github.com/urfave/cli/v2" "time" - - "gopkg.in/urfave/cli.v2" ) // InputSourceContext is an interface used to allow diff --git a/altsrc/json_command_test.go b/altsrc/json_command_test.go index ccb5041..bd8e6cf 100644 --- a/altsrc/json_command_test.go +++ b/altsrc/json_command_test.go @@ -2,11 +2,10 @@ package altsrc import ( "flag" + "github.com/urfave/cli/v2" "io/ioutil" "os" "testing" - - "gopkg.in/urfave/cli.v2" ) const ( @@ -22,7 +21,7 @@ func TestCommandJSONFileTest(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -52,11 +51,11 @@ func TestCommandJSONFileTestGlobalEnvVarWins(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", fileName} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -87,11 +86,11 @@ func TestCommandJSONFileTestGlobalEnvVarWinsNested(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", fileName} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -123,7 +122,7 @@ func TestCommandJSONFileTestSpecifiedFlagWins(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName, "--test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -155,7 +154,7 @@ func TestCommandJSONFileTestSpecifiedFlagWinsNested(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName, "--top.test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -187,7 +186,7 @@ func TestCommandJSONFileTestDefaultValueFileWins(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -219,7 +218,7 @@ func TestCommandJSONFileTestDefaultValueFileWinsNested(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) test := []string{"test-cmd", "--load", fileName} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -250,11 +249,11 @@ func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWins(t *testing.T app := &cli.App{} set := flag.NewFlagSet("test", 0) - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", fileName} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -284,11 +283,11 @@ func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWinsNested(t *tes app := &cli.App{} set := flag.NewFlagSet("test", 0) - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", fileName} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index 34c7eb9..41602ad 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -3,12 +3,11 @@ package altsrc import ( "encoding/json" "fmt" + "github.com/urfave/cli/v2" "io" "io/ioutil" "strings" "time" - - "gopkg.in/urfave/cli.v2" ) // NewJSONSourceFromFlagFunc returns a func that takes a cli.Context @@ -29,13 +28,8 @@ func NewJSONSourceFromFile(f string) (InputSourceContext, error) { if err != nil { return nil, err } - s, err := newJSONSource(data) - if err != nil { - return nil, err - } - s.file = f - return s, nil + return NewJSONSource(data) } // NewJSONSourceFromReader returns an InputSourceContext suitable for @@ -51,10 +45,6 @@ func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) { // NewJSONSource returns an InputSourceContext suitable for retrieving // config variables from raw JSON data. func NewJSONSource(data []byte) (InputSourceContext, error) { - return newJSONSource(data) -} - -func newJSONSource(data []byte) (*jsonSource, error) { var deserialized map[string]interface{} if err := json.Unmarshal(data, &deserialized); err != nil { return nil, err @@ -77,9 +67,9 @@ func (x *jsonSource) Int(name string) (int, error) { case int: return v, nil case float64: - return int(float64(v)), nil + return int(v), nil case float32: - return int(float32(v)), nil + return int(v), nil } } diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 41221b1..66bd6e8 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -2,11 +2,10 @@ package altsrc import ( "fmt" + "github.com/urfave/cli/v2" "reflect" "strings" "time" - - "gopkg.in/urfave/cli.v2" ) // MapInputSource implements InputSourceContext to return @@ -23,15 +22,15 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool if sections := strings.Split(name, "."); len(sections) > 1 { node := tree for _, section := range sections[:len(sections)-1] { - if child, ok := node[section]; !ok { + child, ok := node[section] + if !ok { return nil, false - } else { - if ctype, ok := child.(map[interface{}]interface{}); !ok { - return nil, false - } else { - node = ctype - } } + ctype, ok := child.(map[interface{}]interface{}) + if !ok { + return nil, false + } + node = ctype } if val, ok := node[sections[len(sections)-1]]; ok { return val, true diff --git a/altsrc/toml_command_test.go b/altsrc/toml_command_test.go index 2f28d22..84558aa 100644 --- a/altsrc/toml_command_test.go +++ b/altsrc/toml_command_test.go @@ -7,20 +7,19 @@ package altsrc import ( "flag" + "github.com/urfave/cli/v2" "io/ioutil" "os" "testing" - - "gopkg.in/urfave/cli.v2" ) func TestCommandTomFileTest(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -45,15 +44,15 @@ func TestCommandTomFileTest(t *testing.T) { } func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -79,15 +78,15 @@ func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) { } func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) defer os.Remove("current.toml") - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -113,13 +112,13 @@ func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) { } func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml", "--test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -145,14 +144,14 @@ func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) { } func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte(`[top] + _ = ioutil.WriteFile("current.toml", []byte(`[top] test = 15`), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml", "--top.test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -178,13 +177,13 @@ func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) { } func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -210,13 +209,13 @@ func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) { } func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) defer os.Remove("current.toml") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -244,14 +243,14 @@ func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) { func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) { app := (&cli.App{}) set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -276,16 +275,16 @@ func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T } func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + _ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) defer os.Remove("current.toml") - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.toml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 1cb2d7b..3f410fc 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -10,7 +10,7 @@ import ( "reflect" "github.com/BurntSushi/toml" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) type tomlMap struct { @@ -28,7 +28,7 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { case reflect.String: ret[key] = val.(string) case reflect.Int: - ret[key] = int(val.(int)) + ret[key] = val.(int) case reflect.Int8: ret[key] = int(val.(int8)) case reflect.Int16: @@ -50,7 +50,7 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { case reflect.Float32: ret[key] = float64(val.(float32)) case reflect.Float64: - ret[key] = float64(val.(float64)) + ret[key] = val.(float64) case reflect.Map: if tmp, err := unmarshalMap(val); err == nil { ret[key] = tmp @@ -66,9 +66,9 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { return ret, nil } -func (self *tomlMap) UnmarshalTOML(i interface{}) error { +func (tm *tomlMap) UnmarshalTOML(i interface{}) error { if tmp, err := unmarshalMap(i); err == nil { - self.Map = tmp + tm.Map = tmp } else { return err } diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 5290e84..2190c2b 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -10,17 +10,16 @@ import ( "io/ioutil" "os" "testing" - - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" ) func TestCommandYamlFileTest(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -47,13 +46,13 @@ func TestCommandYamlFileTest(t *testing.T) { func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -81,14 +80,14 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte(`top: + _ = ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) defer os.Remove("current.yaml") - os.Setenv("THE_TEST", "10") + _ = os.Setenv("THE_TEST", "10") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -116,11 +115,11 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml", "--test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -148,12 +147,12 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte(`top: + _ = ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml", "--top.test", "7"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -181,11 +180,11 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -213,12 +212,12 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte(`top: + _ = ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) defer os.Remove("current.yaml") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -246,14 +245,14 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + _ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) defer os.Remove("current.yaml") - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) @@ -280,15 +279,15 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) { app := &cli.App{} set := flag.NewFlagSet("test", 0) - ioutil.WriteFile("current.yaml", []byte(`top: + _ = ioutil.WriteFile("current.yaml", []byte(`top: test: 15`), 0666) defer os.Remove("current.yaml") - os.Setenv("THE_TEST", "11") + _ = os.Setenv("THE_TEST", "11") defer os.Setenv("THE_TEST", "") test := []string{"test-cmd", "--load", "current.yaml"} - set.Parse(test) + _ = set.Parse(test) c := cli.NewContext(app, set, nil) diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 37c8d9c..9b1dc05 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -14,7 +14,7 @@ import ( "runtime" "strings" - "gopkg.in/urfave/cli.v2" + "github.com/urfave/cli/v2" "gopkg.in/yaml.v2" ) @@ -86,7 +86,7 @@ func loadDataFrom(filePath string) ([]byte, error) { return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) } return ioutil.ReadFile(filePath) - } else { - return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) } + + return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) } diff --git a/app.go b/app.go index 4c073c5..229254b 100644 --- a/app.go +++ b/app.go @@ -1,9 +1,9 @@ package cli import ( + "flag" "fmt" "io" - "io/ioutil" "os" "path/filepath" "reflect" @@ -11,7 +11,21 @@ import ( "time" ) -// App is the main structure of a cli application. +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." + + errInvalidActionType = NewExitError("ERROR invalid Action type. "+ + fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ + fmt.Sprintf("See %s", appActionDeprecationURL), 2) +) + +// App is the main structure of a cli application. It is recommended that +// an app be created with the cli.NewApp() function type App struct { // The name of the program. Defaults to path.Base(os.Args[0]) Name string @@ -31,8 +45,8 @@ type App struct { Commands []*Command // List of flags to parse Flags []Flag - // Boolean to enable shell completion commands - EnableShellCompletion bool + // Boolean to enable bash completion commands + EnableBashCompletion bool // Boolean to hide built-in help command HideHelp bool // Boolean to hide built-in version flag and the VERSION section of help @@ -40,7 +54,7 @@ type App struct { // Categories contains the categorized commands and is populated on app startup Categories CommandCategories // An action to execute when the shell completion flag is set - ShellComplete ShellCompleteFunc + BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready // If a non-nil error is returned, no subcommands are run Before BeforeFunc @@ -63,6 +77,9 @@ type App struct { Writer io.Writer // ErrWriter writes error output ErrWriter io.Writer + // Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to + // function as a default, so this is optional. + ExitErrHandler ExitErrHandlerFunc // Other custom info Metadata map[string]interface{} // Carries a function which returns app specific info. @@ -71,6 +88,10 @@ type App struct { // cli.go uses text/template to render templates. You can // 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 + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool didSetup bool } @@ -85,6 +106,22 @@ func compileTime() time.Time { return info.ModTime() } +// NewApp creates a new cli Application with some reasonable defaults for Name, +// Usage, Version and Action. +func NewApp() *App { + return &App{ + Name: filepath.Base(os.Args[0]), + HelpName: filepath.Base(os.Args[0]), + Usage: "A new cli application", + UsageText: "", + Version: "0.0.0", + BashComplete: DefaultAppComplete, + Action: helpCommand.Action, + Compiled: compileTime(), + Writer: os.Stdout, + } +} + // Setup runs initialization code to ensure all data structures are ready for // `Run` or inspection prior to `Run`. It is internally called by `Run`, but // will return early if setup has already happened. @@ -111,8 +148,8 @@ func (a *App) Setup() { a.Version = "0.0.0" } - if a.ShellComplete == nil { - a.ShellComplete = DefaultAppComplete + if a.BashComplete == nil { + a.BashComplete = DefaultAppComplete } if a.Action == nil { @@ -127,14 +164,15 @@ func (a *App) Setup() { a.Writer = os.Stdout } - newCmds := []*Command{} + var newCommands []*Command + for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } - newCmds = append(newCmds, c) + newCommands = append(newCommands, c) } - a.Commands = newCmds + a.Commands = newCommands if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.appendCommand(helpCommand) @@ -144,10 +182,10 @@ func (a *App) Setup() { } } - if a.EnableShellCompletion { - a.appendFlag(GenerateCompletionFlag) - a.appendFlag(InitCompletionFlag) - } + //if a.EnableShellCompletion { + // a.appendFlag(GenerateCompletionFlag) + // a.appendFlag(InitCompletionFlag) + //} if !a.HideVersion { a.appendFlag(VersionFlag) @@ -168,6 +206,14 @@ func (a *App) Setup() { } } +func (a *App) newFlagSet() (*flag.FlagSet, error) { + return flagSet(a.Name, a.Flags) +} + +func (a *App) useShortOptionHandling() bool { + return a.UseShortOptionHandling +} + // Run is the entry point to the cli app. Parses the arguments slice and routes // to the proper flag/args combination func (a *App) Run(arguments []string) (err error) { @@ -181,19 +227,17 @@ func (a *App) Run(arguments []string) (err error) { // always appends the completion flag at the end of the command shellComplete, arguments := checkShellCompleteFlag(a, arguments) - // parse flags - set, err := flagSet(a.Name, a.Flags) + _, err = a.newFlagSet() if err != nil { return err } - set.SetOutput(ioutil.Discard) - err = set.Parse(arguments[1:]) + set, err := parseIter(a, arguments[1:]) nerr := normalizeFlags(a.Flags, set) context := NewContext(a, set, nil) if nerr != nil { - fmt.Fprintln(a.Writer, nerr) - ShowAppHelp(context) + _, _ = fmt.Fprintln(a.Writer, nerr) + _ = ShowAppHelp(context) return nerr } context.shellComplete = shellComplete @@ -212,17 +256,22 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { + //<<<<<<< HEAD err = a.OnUsageError(context, err, false) HandleExitCoder(err) + //======= + // err := a.OnUsageError(context, err, false) + // a.handleExitCoder(context, err) + //>>>>>>> master return err } - fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) - ShowAppHelp(context) + _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + _ = ShowAppHelp(context) return err } if !a.HideHelp && checkHelp(context) { - ShowAppHelp(context) + _ = ShowAppHelp(context) return nil } @@ -231,6 +280,12 @@ func (a *App) Run(arguments []string) (err error) { return nil } + cerr := checkRequiredFlags(a.Flags, context) + if cerr != nil { + _ = ShowAppHelp(context) + return cerr + } + if a.After != nil { defer func() { if afterErr := a.After(context); afterErr != nil { @@ -246,8 +301,9 @@ func (a *App) Run(arguments []string) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - ShowAppHelp(context) - HandleExitCoder(beforeErr) + _, _ = fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) + _ = ShowAppHelp(context) + a.handleExitCoder(context, beforeErr) err = beforeErr return err } @@ -269,10 +325,22 @@ func (a *App) Run(arguments []string) (err error) { // Run default Action err = a.Action(context) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } +// RunAndExitOnError calls .Run() and exits non-zero if an error was returned +// +// Deprecated: instead you should return an error that fulfills cli.ExitCoder +// to cli.App.Run. This will cause the application to exit with the given eror +// code in the cli.ExitCoder +func (a *App) RunAndExitOnError() { + if err := a.Run(os.Args); err != nil { + _, _ = fmt.Fprintln(a.errWriter(), err) + OsExiter(1) + } +} + // RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to // generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { @@ -298,29 +366,32 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } a.Commands = newCmds - // append flags - if a.EnableShellCompletion { - a.appendFlag(GenerateCompletionFlag) - } - - // parse flags - set, err := flagSet(a.Name, a.Flags) + //<<<<<<< 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 } - set.SetOutput(ioutil.Discard) - err = set.Parse(ctx.Args().Tail()) + set, err := parseIter(a, ctx.Args().Tail()) nerr := normalizeFlags(a.Flags, set) context := NewContext(a, set, ctx) if nerr != nil { - fmt.Fprintln(a.Writer, nerr) - fmt.Fprintln(a.Writer) + _, _ = fmt.Fprintln(a.Writer, nerr) + _, _ = fmt.Fprintln(a.Writer) if len(a.Commands) > 0 { - ShowSubcommandHelp(context) + _ = ShowSubcommandHelp(context) } else { - ShowCommandHelp(ctx, context.Args().First()) + _ = ShowCommandHelp(ctx, context.Args().First()) } return nerr } @@ -332,11 +403,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } - fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) - ShowSubcommandHelp(context) + _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + _ = ShowSubcommandHelp(context) return err } @@ -350,11 +421,17 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } + cerr := checkRequiredFlags(a.Flags, context) + if cerr != nil { + _ = ShowSubcommandHelp(context) + return cerr + } + if a.After != nil { defer func() { afterErr := a.After(context) if afterErr != nil { - HandleExitCoder(err) + a.handleExitCoder(context, err) if err != nil { err = newMultiError(err, afterErr) } else { @@ -367,7 +444,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - HandleExitCoder(beforeErr) + a.handleExitCoder(context, beforeErr) err = beforeErr return err } @@ -385,7 +462,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { // Run default Action err = a.Action(context) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } @@ -419,7 +496,7 @@ func (a *App) VisibleCategories() []CommandCategory { // VisibleCommands returns a slice of the Commands with Hidden=false func (a *App) VisibleCommands() []*Command { - ret := []*Command{} + var ret []*Command for _, command := range a.Commands { if !command.Hidden { ret = append(ret, command) @@ -444,7 +521,6 @@ func (a *App) hasFlag(flag Flag) bool { } func (a *App) errWriter() io.Writer { - // When the app ErrWriter is nil use the package level one. if a.ErrWriter == nil { return ErrWriter @@ -465,6 +541,14 @@ func (a *App) appendCommand(c *Command) { } } +func (a *App) handleExitCoder(context *Context, err error) { + if a.ExitErrHandler != nil { + a.ExitErrHandler(context, err) + } else { + HandleExitCoder(err) + } +} + // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name @@ -488,3 +572,20 @@ func DefaultCommand(name string) ActionFunc { return ctx.App.Command(name).Run(ctx) } } + +// HandleAction attempts to figure out which Action signature was used. If +// it's an ActionFunc or a func with the legacy signature for Action, the func +// is run! +func HandleAction(action interface{}, context *Context) (err error) { + switch a := action.(type) { + case ActionFunc: + return a(context) + case func(*Context) error: + return a(context) + case func(*Context): // deprecated function signature + a(context) + return nil + } + + return errInvalidActionType +} diff --git a/app_test.go b/app_test.go index 84631fd..e4bbbcb 100644 --- a/app_test.go +++ b/app_test.go @@ -86,7 +86,7 @@ func ExampleApp_Run_subcommand() { }, } - app.Run(os.Args) + _ = app.Run(os.Args) // Output: // Hello, Jeremy } @@ -119,7 +119,7 @@ func ExampleApp_Run_appHelp() { }, }, } - app.Run(os.Args) + _ = app.Run(os.Args) // Output: // NAME: // greet - A new cli application @@ -138,8 +138,8 @@ func ExampleApp_Run_appHelp() { // Oliver Allen // // COMMANDS: - // describeit, d use it to see a description - // help, h Shows a list of commands or help for one command + // describeit, d use it to see a description + // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: // --name value a name to say (default: "bob") @@ -169,7 +169,7 @@ func ExampleApp_Run_commandHelp() { }, }, } - app.Run(os.Args) + _ = app.Run(os.Args) // Output: // NAME: // greet describeit - use it to see a description @@ -184,7 +184,7 @@ func ExampleApp_Run_commandHelp() { func ExampleApp_Run_noAction() { app := App{} app.Name = "greet" - app.Run([]string{"greet"}) + _ = app.Run([]string{"greet"}) // Output: // NAME: // greet - A new cli application @@ -196,7 +196,7 @@ func ExampleApp_Run_noAction() { // 0.0.0 // // COMMANDS: - // help, h Shows a list of commands or help for one command + // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: // --help, -h show help (default: false) @@ -215,7 +215,7 @@ func ExampleApp_Run_subcommandNoAction() { }, }, } - app.Run([]string{"greet", "describeit"}) + _ = app.Run([]string{"greet", "describeit"}) // Output: // NAME: // greet describeit - use it to see a description @@ -231,13 +231,103 @@ func ExampleApp_Run_subcommandNoAction() { } -func ExampleApp_Run_shellComplete() { +func ExampleApp_Run_bashComplete_withShortFlag() { + os.Args = []string{"greet", "-", "--generate-bash-completion"} + + app := NewApp() + app.Name = "greet" + app.EnableBashCompletion = true + app.Flags = []Flag{ + &IntFlag{ + Name: "other", + Aliases: []string{"o"}, + }, + &StringFlag{ + Name: "xyz", + Aliases: []string{"x"}, + }, + } + + _ = app.Run(os.Args) + // Output: + // --other + // -o + // --xyz + // -x + // --help + // -h + // --version + // -v +} + +func ExampleApp_Run_bashComplete_withLongFlag() { + os.Args = []string{"greet", "--s", "--generate-bash-completion"} + + app := NewApp() + app.Name = "greet" + app.EnableBashCompletion = true + app.Flags = []Flag{ + &IntFlag{ + Name: "other", + Aliases: []string{"o"}, + }, + &StringFlag{ + Name: "xyz", + Aliases: []string{"x"}, + }, + &StringFlag{ + Name: "some-flag,s", + }, + &StringFlag{ + Name: "similar-flag", + }, + } + + _ = app.Run(os.Args) + // Output: + // --some-flag + // --similar-flag +} +func ExampleApp_Run_bashComplete_withMultipleLongFlag() { + os.Args = []string{"greet", "--st", "--generate-bash-completion"} + + app := NewApp() + app.Name = "greet" + app.EnableBashCompletion = true + app.Flags = []Flag{ + &IntFlag{ + Name: "int-flag", + Aliases: []string{"i"}, + }, + &StringFlag{ + Name: "string", + Aliases: []string{"s"}, + }, + &StringFlag{ + Name: "string-flag-2", + }, + &StringFlag{ + Name: "similar-flag", + }, + &StringFlag{ + Name: "some-flag", + }, + } + + _ = app.Run(os.Args) + // Output: + // --string + // --string-flag-2 +} + +func ExampleApp_Run_bashComplete() { // set args for examples sake - os.Args = []string{"greet", fmt.Sprintf("--%s", genCompName())} + // set args for examples sake + os.Args = []string{"greet", "--generate-bash-completion"} app := &App{ Name: "greet", - EnableShellCompletion: true, + EnableBashCompletion: true, Commands: []*Command{ { Name: "describeit", @@ -260,7 +350,7 @@ func ExampleApp_Run_shellComplete() { }, } - app.Run(os.Args) + _ = app.Run(os.Args) // Output: // describeit // d @@ -269,6 +359,44 @@ func ExampleApp_Run_shellComplete() { // h } +func ExampleApp_Run_zshComplete() { + // set args for examples sake + os.Args = []string{"greet", "--generate-bash-completion"} + _ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1") + + app := NewApp() + app.Name = "greet" + app.EnableBashCompletion = true + app.Commands = []*Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, + }, { + Name: "next", + Usage: "next example", + Description: "more stuff to see when generating bash completion", + Action: func(c *Context) error { + fmt.Printf("the next example") + return nil + }, + }, + } + + _ = app.Run(os.Args) + // Output: + // describeit:use it to see a description + // d:use it to see a description + // next:next example + // help:Shows a list of commands or help for one command + // h:Shows a list of commands or help for one command +} + func TestApp_Run(t *testing.T) { s := "" @@ -317,6 +445,63 @@ func TestApp_Setup_defaultsWriter(t *testing.T) { expect(t, app.Writer, os.Stdout) } + +func TestApp_CommandWithArgBeforeFlags(t *testing.T) { + var parsedOption, firstArg string + + app := NewApp() + command := &Command{ + Name: "cmd", + Flags: []Flag{ + &StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *Context) error { + parsedOption = c.String("option") + firstArg = c.Args().First() + return nil + }, + } + app.Commands = []*Command{command} + + _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) + + expect(t, parsedOption, "my-option") + expect(t, firstArg, "my-arg") +} + +func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) { + var parsedOption, parsedSecondOption, firstArg string + var parsedBool, parsedSecondBool bool + + app := NewApp() + command := &Command{ + Name: "cmd", + Flags: []Flag{ + &StringFlag{Name: "option", Value: "", Usage: "some option"}, + &StringFlag{Name: "secondOption", Value: "", Usage: "another option"}, + &BoolFlag{Name: "boolflag", Usage: "some bool"}, + &BoolFlag{Name: "b", Usage: "another bool"}, + }, + Action: func(c *Context) error { + parsedOption = c.String("option") + parsedSecondOption = c.String("secondOption") + parsedBool = c.Bool("boolflag") + parsedSecondBool = c.Bool("b") + firstArg = c.Args().First() + return nil + }, + } + app.Commands = []*Command{command} + + _ = app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"}) + + expect(t, parsedOption, "my-option") + expect(t, parsedSecondOption, "fancy-option") + expect(t, parsedBool, true) + expect(t, parsedSecondBool, true) + expect(t, firstArg, "my-arg") +} + func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context @@ -339,7 +524,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { }, }, } - a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) + _ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) expect(t, context.Args().Get(0), "abcd") expect(t, context.String("lang"), "spanish") @@ -355,7 +540,7 @@ func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) { } set := flag.NewFlagSet("", flag.ContinueOnError) - set.Parse([]string{"", "---foo"}) + _ = set.Parse([]string{"", "---foo"}) c := &Context{flagSet: set} err := a.RunAsSubcommand(c) @@ -383,7 +568,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { }, } - app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}) + _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) expect(t, parsedOption, "my-option") expect(t, args.Get(0), "my-arg") @@ -406,7 +591,7 @@ func TestApp_CommandWithDash(t *testing.T) { }, } - app.Run([]string{"", "cmd", "my-arg", "-"}) + _ = app.Run([]string{"", "cmd", "my-arg", "-"}) expect(t, args.Get(0), "my-arg") expect(t, args.Get(1), "-") @@ -427,7 +612,7 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { }, } - app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) + _ = app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) expect(t, args.Get(0), "my-arg") expect(t, args.Get(1), "--") @@ -485,6 +670,93 @@ func TestApp_VisibleCommands(t *testing.T) { } } +func TestApp_UseShortOptionHandling(t *testing.T) { + var one, two bool + var name string + expected := "expectedName" + + app := NewApp() + app.UseShortOptionHandling = true + app.Flags = []Flag{ + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, + } + app.Action = func(c *Context) error { + one = c.Bool("one") + two = c.Bool("two") + name = c.String("name") + return nil + } + + _ = app.Run([]string{"", "-on", expected}) + expect(t, one, true) + expect(t, two, false) + expect(t, name, expected) +} + +func TestApp_UseShortOptionHandlingCommand(t *testing.T) { + var one, two bool + var name string + expected := "expectedName" + + app := NewApp() + app.UseShortOptionHandling = true + command := &Command{ + Name: "cmd", + Flags: []Flag{ + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, + }, + Action: func(c *Context) error { + one = c.Bool("one") + two = c.Bool("two") + name = c.String("name") + return nil + }, + } + app.Commands = []*Command{command} + + _ = app.Run([]string{"", "cmd", "-on", expected}) + expect(t, one, true) + expect(t, two, false) + expect(t, name, expected) +} + +func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { + var one, two bool + var name string + expected := "expectedName" + + app := NewApp() + app.UseShortOptionHandling = true + command := &Command{ + Name: "cmd", + } + subCommand := &Command{ + Name: "sub", + Flags: []Flag{ + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, + }, + Action: func(c *Context) error { + one = c.Bool("one") + two = c.Bool("two") + name = c.String("name") + return nil + }, + } + app.Commands = []*Command{command, subCommand} + + err := app.Run([]string{"", "cmd", "sub", "-on", expected}) + expect(t, err, nil) + expect(t, one, true) + expect(t, two, false) + expect(t, name, expected) +} + func TestApp_Float64Flag(t *testing.T) { var meters float64 @@ -498,7 +770,7 @@ func TestApp_Float64Flag(t *testing.T) { }, } - app.Run([]string{"", "--height", "1.93"}) + _ = app.Run([]string{"", "--height", "1.93"}) expect(t, meters, 1.93) } @@ -528,7 +800,7 @@ func TestApp_ParseSliceFlags(t *testing.T) { var _ = parsedOption var _ = firstArg - app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"}) + _ = app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) IntsEquals := func(a, b []int) bool { if len(a) != len(b) { @@ -586,7 +858,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { }, } - app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"}) + _ = app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) var expectedIntSlice = []int{2} var expectedStringSlice = []string{"A"} @@ -818,6 +1090,145 @@ func TestAppNoHelpFlag(t *testing.T) { } } +func TestRequiredFlagAppRunBehavior(t *testing.T) { + tdata := []struct { + testCase string + appFlags []Flag + appRunInput []string + appCommands []*Command + expectedAnError bool + }{ + // assertion: empty input, when a required flag is present, errors + { + testCase: "error_case_empty_input_with_required_flag_on_app", + appRunInput: []string{"myCLI"}, + appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + expectedAnError: true, + }, + { + testCase: "error_case_empty_input_with_required_flag_on_command", + appRunInput: []string{"myCLI", "myCommand"}, + appCommands: []*Command{{ + Name: "myCommand", + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + }}, + expectedAnError: true, + }, + { + testCase: "error_case_empty_input_with_required_flag_on_subcommand", + appRunInput: []string{"myCLI", "myCommand", "mySubCommand"}, + appCommands: []*Command{{ + Name: "myCommand", + Subcommands: []*Command{{ + Name: "mySubCommand", + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }}, + expectedAnError: true, + }, + // assertion: inputing --help, when a required flag is present, does not error + { + testCase: "valid_case_help_input_with_required_flag_on_app", + appRunInput: []string{"myCLI", "--help"}, + appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + }, + { + testCase: "valid_case_help_input_with_required_flag_on_command", + appRunInput: []string{"myCLI", "myCommand", "--help"}, + appCommands: []*Command{{ + Name: "myCommand", + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }, + { + testCase: "valid_case_help_input_with_required_flag_on_subcommand", + appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--help"}, + appCommands: []*Command{{ + Name: "myCommand", + Subcommands: []*Command{{ + Name: "mySubCommand", + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }}, + }, + // assertion: giving optional input, when a required flag is present, errors + { + testCase: "error_case_optional_input_with_required_flag_on_app", + appRunInput: []string{"myCLI", "--optional", "cats"}, + appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}}, + expectedAnError: true, + }, + { + testCase: "error_case_optional_input_with_required_flag_on_command", + appRunInput: []string{"myCLI", "myCommand", "--optional", "cats"}, + appCommands: []*Command{{ + Name: "myCommand", + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}}, + }}, + expectedAnError: true, + }, + { + testCase: "error_case_optional_input_with_required_flag_on_subcommand", + appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--optional", "cats"}, + appCommands: []*Command{{ + Name: "myCommand", + Subcommands: []*Command{{ + Name: "mySubCommand", + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}}, + }}, + }}, + expectedAnError: true, + }, + // assertion: when a required flag is present, inputting that required flag does not error + { + testCase: "valid_case_required_flag_input_on_app", + appRunInput: []string{"myCLI", "--requiredFlag", "cats"}, + appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + }, + { + testCase: "valid_case_required_flag_input_on_command", + appRunInput: []string{"myCLI", "myCommand", "--requiredFlag", "cats"}, + appCommands: []*Command{{ + Name: "myCommand", + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }, + { + testCase: "valid_case_required_flag_input_on_subcommand", + appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--requiredFlag", "cats"}, + appCommands: []*Command{{ + Name: "myCommand", + Subcommands: []*Command{{ + Name: "mySubCommand", + Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, + }}, + }}, + }, + } + for _, test := range tdata { + t.Run(test.testCase, func(t *testing.T) { + // setup + app := NewApp() + app.Flags = test.appFlags + app.Commands = test.appCommands + + // logic under test + err := app.Run(test.appRunInput) + + // assertions + if test.expectedAnError && err == nil { + t.Errorf("expected an error, but there was none") + } + if _, ok := err.(requiredFlagsErr); test.expectedAnError && !ok { + t.Errorf("expected a requiredFlagsErr, but got: %s", err) + } + if !test.expectedAnError && err != nil { + t.Errorf("did not expected an error, but there was one: %s", err) + } + }) + } +} + func TestAppHelpPrinter(t *testing.T) { oldPrinter := HelpPrinter defer func() { @@ -830,7 +1241,7 @@ func TestAppHelpPrinter(t *testing.T) { } app := &App{} - app.Run([]string{"-h"}) + _ = app.Run([]string{"-h"}) if wasCalled == false { t.Errorf("Help printer expected to be called, but was not") @@ -876,7 +1287,7 @@ func TestApp_CommandNotFound(t *testing.T) { }, } - app.Run([]string{"command", "foo"}) + _ = app.Run([]string{"command", "foo"}) expect(t, counts.CommandNotFound, 1) expect(t, counts.SubCommand, 0) @@ -889,9 +1300,9 @@ func TestApp_OrderOfOperations(t *testing.T) { resetCounts := func() { counts = &opCounts{} } app := &App{ - EnableShellCompletion: true, - ShellComplete: func(c *Context) { - fmt.Fprintf(os.Stderr, "---> ShellComplete(%#v)\n", c) + EnableBashCompletion: true, + BashComplete: func(c *Context) { + _, _ = fmt.Fprintf(os.Stderr, "---> BashComplete(%#v)\n", c) counts.Total++ counts.ShellComplete = counts.Total }, @@ -956,7 +1367,7 @@ func TestApp_OrderOfOperations(t *testing.T) { resetCounts() - _ = app.Run([]string{"command", fmt.Sprintf("--%s", genCompName())}) + _ = app.Run([]string{"command", fmt.Sprintf("--%s", "--generate-bash-completion")}) expect(t, counts.ShellComplete, 1) expect(t, counts.Total, 1) @@ -1306,7 +1717,7 @@ func TestApp_Run_Categories(t *testing.T) { Writer: buf, } - app.Run([]string{"categories"}) + _ = app.Run([]string{"categories"}) expect := commandCategories([]*commandCategory{ { @@ -1569,6 +1980,18 @@ func (c *customBoolFlag) Names() []string { return []string{c.Nombre} } +func (c *customBoolFlag) TakesValue() bool { + return false +} + +func (c *customBoolFlag) GetValue() string { + return "value" +} + +func (c *customBoolFlag) GetUsage() string { + return "usage" +} + func (c *customBoolFlag) Apply(set *flag.FlagSet) { set.String(c.Nombre, c.Nombre, "") } @@ -1613,6 +2036,59 @@ func TestCustomHelpVersionFlags(t *testing.T) { } } +func TestHandleExitCoder_Default(t *testing.T) { + app := NewApp() + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + + ctx := NewContext(app, fs, nil) + app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42)) + + output := fakeErrWriter.String() + if !strings.Contains(output, "Default") { + t.Fatalf("Expected Default Behavior from Error Handler but got: %s", output) + } +} + +func TestHandleExitCoder_Custom(t *testing.T) { + app := NewApp() + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + + app.ExitErrHandler = func(_ *Context, _ error) { + _, _ = fmt.Fprintln(ErrWriter, "I'm a Custom error handler, I print what I want!") + } + + ctx := NewContext(app, fs, nil) + app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42)) + + output := fakeErrWriter.String() + if !strings.Contains(output, "Custom") { + t.Fatalf("Expected Custom Behavior from Error Handler but got: %s", output) + } +} + +func TestHandleAction_WithUnknownPanic(t *testing.T) { + defer func() { refute(t, recover(), nil) }() + + var fn ActionFunc + + app := NewApp() + app.Action = func(ctx *Context) error { + _ = fn(ctx) + return nil + } + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + _ = HandleAction(app.Action, NewContext(app, fs, nil)) +} + func TestShellCompletionForIncompleteFlags(t *testing.T) { app := &App{ Flags: []Flag{ @@ -1620,30 +2096,30 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) { Name: "test-completion", }, }, - EnableShellCompletion: true, - ShellComplete: func(ctx *Context) { + EnableBashCompletion: true, + BashComplete: func(ctx *Context) { for _, command := range ctx.App.Commands { if command.Hidden { continue } for _, name := range command.Names() { - fmt.Fprintln(ctx.App.Writer, name) + _, _ = fmt.Fprintln(ctx.App.Writer, name) } } for _, flag := range ctx.App.Flags { for _, name := range flag.Names() { - if name == genCompName() { + if name == BashCompletionFlag.Names()[0] { continue } switch name = strings.TrimSpace(name); len(name) { case 0: case 1: - fmt.Fprintln(ctx.App.Writer, "-"+name) + _, _ = fmt.Fprintln(ctx.App.Writer, "-"+name) default: - fmt.Fprintln(ctx.App.Writer, "--"+name) + _, _ = fmt.Fprintln(ctx.App.Writer, "--"+name) } } } @@ -1652,8 +2128,62 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) { return fmt.Errorf("should not get here") }, } - err := app.Run([]string{"", "--test-completion", "--" + genCompName()}) + err := app.Run([]string{"", "--test-completion", "--" + "generate-bash-completion"}) if err != nil { t.Errorf("app should not return an error: %s", err) } } + +func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) { + testCode := 104 + + app := NewApp() + app.Commands = []*Command{ + { + Name: "cmd", + Subcommands: []*Command{ + { + Name: "subcmd", + Action: func(c *Context) error { + return NewExitError("exit error", testCode) + }, + }, + }, + }, + } + + // set user function as ExitErrHandler + var exitCodeFromExitErrHandler int + app.ExitErrHandler = func(c *Context, err error) { + if exitErr, ok := err.(ExitCoder); ok { + t.Log(exitErr) + exitCodeFromExitErrHandler = exitErr.ExitCode() + } + } + + // keep and restore original OsExiter + origExiter := OsExiter + defer func() { + OsExiter = origExiter + }() + + // set user function as OsExiter + var exitCodeFromOsExiter int + OsExiter = func(exitCode int) { + exitCodeFromOsExiter = exitCode + } + + _ = app.Run([]string{ + "myapp", + "cmd", + "subcmd", + }) + + if exitCodeFromOsExiter != 0 { + t.Errorf("exitCodeFromOsExiter should not change, but its value is %v", exitCodeFromOsExiter) + } + + if exitCodeFromExitErrHandler != testCode { + t.Errorf("exitCodeFromOsExiter valeu should be %v, but its value is %v", testCode, exitCodeFromExitErrHandler) + } +} diff --git a/appveyor.yml b/appveyor.yml index 886f0e9..a8c05a1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,26 +11,17 @@ cache: environment: GOPATH: C:\gopath - GOVERSION: 1.8.x - PYTHON: C:\Python36-x64 - PYTHON_VERSION: 3.6.x - PYTHON_ARCH: 64 + GOVERSION: 1.11.x install: -- set PATH=%GOPATH%\bin;C:\go\bin;%PATH% -- go version -- go env -- go get github.com/urfave/gfmrun/... -- rmdir c:\gopath\src\gopkg.in\urfave\cli.v2 /s /q -- rmdir c:\gopath\pkg /s /q -- git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 -- go get -v -t ./... -- if not exist node_modules\.bin\markdown-toc npm install markdown-toc + - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% + - go version + - go env + - go get github.com/urfave/gfmrun/... + - go get -v -t ./... build_script: -- python runtests vet -- python runtests test -- python runtests gfmrun -- python cli-v1-to-v2 --selftest -- python runtests migrations -- python runtests toc + - go run build.go vet + - go run build.go test + - go run build.go gfmrun + diff --git a/autocomplete/bash_autocomplete b/autocomplete/bash_autocomplete index 37d9c14..f0f6241 100755 --- a/autocomplete/bash_autocomplete +++ b/autocomplete/bash_autocomplete @@ -3,14 +3,19 @@ : ${PROG:=$(basename ${BASH_SOURCE})} _cli_bash_autocomplete() { + if [[ "${COMP_WORDS[0]}" != "source" ]]; then local cur opts base COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + if [[ "$cur" == "-"* ]]; then + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) + else + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + fi COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 + fi } -complete -F _cli_bash_autocomplete $PROG - +complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG unset PROG diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete index 5430a18..8b747ae 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -1,5 +1,11 @@ -autoload -U compinit && compinit -autoload -U bashcompinit && bashcompinit +_cli_zsh_autocomplete() { -script_dir=$(dirname $0) -source ${script_dir}/bash_autocomplete + local -a opts + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") + + _describe 'values' opts + + return +} + +compdef _cli_zsh_autocomplete $PROG diff --git a/build.go b/build.go new file mode 100644 index 0000000..a828ae4 --- /dev/null +++ b/build.go @@ -0,0 +1,164 @@ +//+build ignore + +package main + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" + + "github.com/urfave/cli" +) + +var packages = []string{"cli", "altsrc"} + +func main() { + app := cli.NewApp() + + app.Name = "builder" + app.Usage = "Generates a new urfave/cli build!" + + app.Commands = cli.Commands{ + cli.Command{ + Name: "vet", + Action: VetActionFunc, + }, + cli.Command{ + Name: "test", + Action: TestActionFunc, + }, + cli.Command{ + Name: "gfmrun", + Action: GfmrunActionFunc, + }, + cli.Command{ + Name: "toc", + Action: TocActionFunc, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} + +func runCmd(arg string, args ...string) error { + cmd := exec.Command(arg, args...) + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() +} + +func VetActionFunc(_ *cli.Context) error { + return runCmd("go", "vet") +} + +func TestActionFunc(c *cli.Context) error { + for _, pkg := range packages { + var packageName string + + if pkg == "cli" { + packageName = "github.com/urfave/cli" + } else { + packageName = fmt.Sprintf("github.com/urfave/cli/%s", pkg) + } + + coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg) + + err := runCmd("go", "test", "-v", coverProfile, packageName) + if err != nil { + return err + } + } + + return testCleanup() +} + +func testCleanup() error { + var out bytes.Buffer + + for _, pkg := range packages { + file, err := os.Open(fmt.Sprintf("%s.coverprofile", pkg)) + if err != nil { + return err + } + + b, err := ioutil.ReadAll(file) + if err != nil { + return err + } + + out.Write(b) + err = file.Close() + if err != nil { + return err + } + + err = os.Remove(fmt.Sprintf("%s.coverprofile", pkg)) + if err != nil { + return err + } + } + + outFile, err := os.Create("coverage.txt") + if err != nil { + return err + } + + _, err = out.WriteTo(outFile) + if err != nil { + return err + } + + err = outFile.Close() + if err != nil { + return err + } + + return nil +} + +func GfmrunActionFunc(_ *cli.Context) error { + file, err := os.Open("README.md") + if err != nil { + return err + } + + var counter int + scanner := bufio.NewScanner(file) + for scanner.Scan() { + if strings.Contains(scanner.Text(), "package main") { + counter++ + } + } + + err = scanner.Err() + if err != nil { + return err + } + + return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", "README.md") +} + +func TocActionFunc(_ *cli.Context) error { + err := runCmd("node_modules/.bin/markdown-toc", "-i", "README.md") + if err != nil { + return err + } + + err = runCmd("git", "diff", "--exit-code") + if err != nil { + return err + } + + return nil +} diff --git a/category.go b/category.go index 3b405c0..64d84ec 100644 --- a/category.go +++ b/category.go @@ -15,7 +15,7 @@ func newCommandCategories() CommandCategories { } func (c *commandCategories) Less(i, j int) bool { - return (*c)[i].Name() < (*c)[j].Name() + return lexicographicLess((*c)[i].Name(), (*c)[j].Name() ) } func (c *commandCategories) Len() int { @@ -35,7 +35,7 @@ func (c *commandCategories) AddCommand(category string, command *Command) { } newVal := commandCategories(append(*c, &commandCategory{name: category, commands: []*Command{command}})) - (*c) = newVal + *c = newVal } func (c *commandCategories) Categories() []CommandCategory { @@ -75,7 +75,7 @@ func (c *commandCategory) VisibleCommands() []*Command { c.commands = []*Command{} } - ret := []*Command{} + var ret []*Command for _, command := range c.commands { if !command.Hidden { ret = append(ret, command) diff --git a/cli.go b/cli.go index 81fc7ab..62a5bc2 100644 --- a/cli.go +++ b/cli.go @@ -20,4 +20,4 @@ // } package cli -//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go +//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go diff --git a/command.go b/command.go index 05093b8..181091e 100644 --- a/command.go +++ b/command.go @@ -1,6 +1,7 @@ package cli import ( + "flag" "fmt" "io/ioutil" "sort" @@ -23,8 +24,8 @@ type Command struct { ArgsUsage string // The category the command is part of Category string - // The function to call when checking for shell command completions - ShellComplete ShellCompleteFunc + // The function to call when checking for bash command completions + BashComplete BashCompleteFunc // An action to execute before any sub-subcommands are run, but after the context is ready // If a non-nil error is returned, no sub-subcommands are run Before BeforeFunc @@ -45,6 +46,10 @@ type Command struct { HideHelp bool // Boolean to hide this command from help or completion Hidden bool + // Boolean to enable short-option handling so user can combine several + // single-character bool arguments into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool // Full name of command for help, defaults to full command name, including parent commands. HelpName string @@ -63,7 +68,7 @@ func (c CommandsByName) Len() int { } func (c CommandsByName) Less(i, j int) bool { - return c[i].Name < c[j].Name + return lexicographicLess(c[i].Name, c[j].Name) } func (c CommandsByName) Swap(i, j int) { @@ -90,10 +95,14 @@ func (c *Command) Run(ctx *Context) (err error) { c.appendFlag(HelpFlag) } - if ctx.App.EnableShellCompletion { - c.appendFlag(GenerateCompletionFlag) + if ctx.App.UseShortOptionHandling { + c.UseShortOptionHandling = true } + //if ctx.App.EnableShellCompletion { + // c.appendFlag(GenerateCompletionFlag) + //} + set, err := flagSet(c.Name, c.Flags) if err != nil { return err @@ -108,9 +117,9 @@ func (c *Command) Run(ctx *Context) (err error) { nerr := normalizeFlags(c.Flags, set) if nerr != nil { - fmt.Fprintln(ctx.App.Writer, nerr) - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) + _, _ = fmt.Fprintln(ctx.App.Writer, nerr) + _, _ = fmt.Fprintln(ctx.App.Writer) + _ = ShowCommandHelp(ctx, c.Name) return nerr } @@ -126,9 +135,9 @@ func (c *Command) Run(ctx *Context) (err error) { HandleExitCoder(err) return err } - fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) - fmt.Fprintln(context.App.Writer) - ShowCommandHelp(context, c.Name) + _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) + _, _ = fmt.Fprintln(context.App.Writer) + _ = ShowCommandHelp(context, c.Name) return err } @@ -136,6 +145,12 @@ func (c *Command) Run(ctx *Context) (err error) { return nil } + cerr := checkRequiredFlags(c.Flags, context) + if cerr != nil { + _ = ShowCommandHelp(context, c.Name) + return cerr + } + if c.After != nil { defer func() { afterErr := c.After(context) @@ -172,6 +187,14 @@ func (c *Command) Run(ctx *Context) (err error) { return err } +func (c *Command) newFlagSet() (*flag.FlagSet, error) { + return flagSet(c.Name, c.Flags) +} + +func (c *Command) useShortOptionHandling() bool { + return c.UseShortOptionHandling +} + // Names returns the names including short names and aliases. func (c *Command) Names() []string { return append([]string{c.Name}, c.Aliases...) @@ -217,6 +240,7 @@ func (c *Command) startApp(ctx *Context) error { app.Compiled = ctx.App.Compiled app.Writer = ctx.App.Writer app.ErrWriter = ctx.App.ErrWriter + app.UseShortOptionHandling = ctx.App.UseShortOptionHandling app.Categories = newCommandCategories() for _, command := range c.Subcommands { @@ -226,9 +250,9 @@ func (c *Command) startApp(ctx *Context) error { sort.Sort(app.Categories.(*commandCategories)) // bash completion - app.EnableShellCompletion = ctx.App.EnableShellCompletion - if c.ShellComplete != nil { - app.ShellComplete = c.ShellComplete + app.EnableBashCompletion = ctx.App.EnableBashCompletion + if c.BashComplete != nil { + app.BashComplete = c.BashComplete } // set the actions diff --git a/command_test.go b/command_test.go index 7fb3980..f2ccf9a 100644 --- a/command_test.go +++ b/command_test.go @@ -11,32 +11,40 @@ import ( func TestCommandFlagParsing(t *testing.T) { cases := []struct { - testArgs []string - skipFlagParsing bool - expectedErr error + testArgs []string + skipFlagParsing bool + skipArgReorder bool + expectedErr error + UseShortOptionHandling bool }{ // Test normal "not ignoring flags" flow - {[]string{"test-cmd", "-break", "blah", "blah"}, false, errors.New("flag provided but not defined: -break")}, + {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break"), false}, - {[]string{"test-cmd", "blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags - {[]string{"test-cmd", "blah", "-break"}, true, nil}, // Test SkipFlagParsing with random flag arg - {[]string{"test-cmd", "blah", "-help"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg + // Test no arg reorder + {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false}, + {[]string{"test-cmd", "blah", "blah", "-break", "ls", "-l"}, false, true, nil, true}, + + {[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags + {[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg + {[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg + {[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling } for _, c := range cases { app := &App{Writer: ioutil.Discard} set := flag.NewFlagSet("test", 0) - set.Parse(c.testArgs) + _ = set.Parse(c.testArgs) context := NewContext(app, set, nil) command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) error { return nil }, - SkipFlagParsing: c.skipFlagParsing, + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) error { return nil }, + SkipFlagParsing: c.skipFlagParsing, + UseShortOptionHandling: c.UseShortOptionHandling, } err := command.Run(context) @@ -46,6 +54,51 @@ func TestCommandFlagParsing(t *testing.T) { } } +func TestParseAndRunShortOpts(t *testing.T) { + cases := []struct { + testArgs []string + expectedErr error + expectedArgs []string + }{ + {[]string{"foo", "test", "-a"}, nil, []string{}}, + {[]string{"foo", "test", "-c", "arg1", "arg2"}, nil, []string{"arg1", "arg2"}}, + {[]string{"foo", "test", "-f"}, nil, []string{}}, + {[]string{"foo", "test", "-ac", "--fgh"}, nil, []string{}}, + {[]string{"foo", "test", "-af"}, nil, []string{}}, + {[]string{"foo", "test", "-cf"}, nil, []string{}}, + {[]string{"foo", "test", "-acf"}, nil, []string{}}, + {[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), []string{}}, + {[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}}, + } + + var args Args + cmd := Command{ + Name: "test", + Usage: "this is for testing", + Description: "testing", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + UseShortOptionHandling: true, + Flags: []Flag{ + &BoolFlag{Name: "abc", Aliases: []string{"a"}}, + &BoolFlag{Name: "cde", Aliases: []string{"c"}}, + &BoolFlag{Name: "fgh", Aliases: []string{"f"}}, + }, + } + + for _, c := range cases { + app := NewApp() + app.Commands = []Command{cmd} + + err := app.Run(c.testArgs) + + expect(t, err, c.expectedErr) + expect(t, args, c.expectedArgs) + } +} + func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := &App{ Commands: []*Command{ @@ -238,3 +291,77 @@ func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { t.Fatal(err) } } + +func TestCommandFlagReordering(t *testing.T) { + cases := []struct { + testArgs []string + expectedValue string + expectedArgs []string + expectedErr error + }{ + {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, "foo", []string{"some-arg"}, nil}, + {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, "foo", []string{"some-arg"}, nil}, + {[]string{"some-exec", "some-command", "--flag=foo", "some-arg"}, "foo", []string{"some-arg"}, nil}, + } + + for _, c := range cases { + value := "" + var args Args + app := &App{ + Commands: []*Command{ + { + Name: "some-command", + Flags: []Flag{ + &StringFlag{Name: "flag"}, + }, + Action: func(c *Context) error { + fmt.Printf("%+v\n", c.String("flag")) + value = c.String("flag") + args = c.Args() + return nil + }, + }, + }, + } + + err := app.Run(c.testArgs) + expect(t, err, c.expectedErr) + expect(t, value, c.expectedValue) + expect(t, args, c.expectedArgs) + } +} + +func TestCommandSkipFlagParsing(t *testing.T) { + cases := []struct { + testArgs []string + expectedArgs []string + expectedErr error + }{ + {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, []string{"some-arg", "--flag", "foo"}, nil}, + {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, []string{"some-arg", "--flag=foo"}, nil}, + } + + for _, c := range cases { + var args Args + app := &App{ + Commands: []*Command{ + { + SkipFlagParsing: true, + Name: "some-command", + Flags: []Flag{ + &StringFlag{Name: "flag"}, + }, + Action: func(c *Context) error { + fmt.Printf("%+v\n", c.String("flag")) + args = c.Args() + return nil + }, + }, + }, + } + + err := app.Run(c.testArgs) + expect(t, err, c.expectedErr) + expect(t, args, c.expectedArgs) + } +} diff --git a/context.go b/context.go index f1a01b4..ba477f6 100644 --- a/context.go +++ b/context.go @@ -4,6 +4,7 @@ import ( "context" "errors" "flag" + "fmt" "os" "os/signal" "reflect" @@ -20,7 +21,7 @@ type Context struct { App *App Command *Command shellComplete bool - + setFlags map[string]bool flagSet *flag.FlagSet parentContext *Context } @@ -65,41 +66,56 @@ func (c *Context) IsSet(name string) bool { isSet = true } }) - if isSet { - return true + + // XXX hack to support IsSet for flags with EnvVar + // + // There isn't an easy way to do this with the current implementation since + // whether a flag was set via an environment variable is very difficult to + // determine here. Instead, we intend to introduce a backwards incompatible + // change in version 2 to add `IsSet` to the Flag interface to push the + // responsibility closer to where the information required to determine + // whether a flag is set by non-standard means such as environment + // variables is available. + // + // See https://github.com/urfave/cli/issues/294 for additional discussion + flags := c.Command.Flags + if c.Command.Name == "" { // cannot == Command{} since it contains slice types + if c.App != nil { + flags = c.App.Flags + } } - } + for _, f := range flags { + for _, name := range f.Names() { + if isSet, ok := c.setFlags[name]; isSet || !ok { + continue + } - // XXX hack to support IsSet for flags with EnvVar - // - // There isn't an easy way to do this with the current implementation since - // whether a flag was set via an environment variable is very difficult to - // determine here. Instead, we intend to introduce a backwards incompatible - // change in version 2 to add `IsSet` to the Flag interface to push the - // responsibility closer to where the information required to determine - // whether a flag is set by non-standard means such as environment - // variables is avaliable. - // - // See https://github.com/urfave/cli/issues/294 for additional discussion - f := lookupFlag(name, c) - if f == nil { - return false - } + val := reflect.ValueOf(f) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } - val := reflect.ValueOf(f) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } + filePathValue := val.FieldByName("FilePath") + if filePathValue.IsValid() { + eachName(filePathValue.String(), func(filePath string) { + if _, err := os.Stat(filePath); err == nil { + c.setFlags[name] = true + return + } + }) + } - envVarValues := val.FieldByName("EnvVars") - if !envVarValues.IsValid() { - return false - } - - for _, envVar := range envVarValues.Interface().([]string) { - envVar = strings.TrimSpace(envVar) - if envVal := os.Getenv(envVar); envVal != "" { - return true + envVarValues := val.FieldByName("EnvVars") + if envVarValues.IsValid() { + for _, envVar := range envVarValues.Interface().([]string) { + envVar = strings.TrimSpace(envVar) + if _, ok := syscall.Getenv(envVar); ok { + c.setFlags[name] = true + continue + } + } + } + } } } @@ -108,7 +124,7 @@ func (c *Context) IsSet(name string) bool { // LocalFlagNames returns a slice of flag names used in this context. func (c *Context) LocalFlagNames() []string { - names := []string{} + var names []string c.flagSet.Visit(makeFlagNameVisitor(&names)) return names } @@ -116,17 +132,42 @@ func (c *Context) LocalFlagNames() []string { // FlagNames returns a slice of flag names used by the this context and all of // its parent contexts. func (c *Context) FlagNames() []string { - names := []string{} + var names []string for _, ctx := range c.Lineage() { ctx.flagSet.Visit(makeFlagNameVisitor(&names)) + } return names } +// FlagNames returns a slice of flag names used in this context. +//func (c *Context) FlagNames() (names []string) { +// for _, f := range c.Command.Flags { +// name := strings.Split(f.GetName(), ",")[0] +// if name == "help" { +// continue +// } +// names = append(names, name) +// } +// return +//} + +// GlobalFlagNames returns a slice of global flag names used by the app. +//func (c *Context) GlobalFlagNames() (names []string) { +// for _, f := range c.App.Flags { +// name := strings.Split(f.GetName(), ",")[0] +// if name == "help" || name == "version" { +// continue +// } +// names = append(names, name) +// } +// return names +//} + // Lineage returns *this* context and all of its ancestor contexts in order from // child to parent func (c *Context) Lineage() []*Context { - lineage := []*Context{} + var lineage []*Context for cur := c; cur != nil; cur = cur.parentContext { lineage = append(lineage, cur) @@ -191,10 +232,10 @@ func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { switch ff.Value.(type) { - case Serializeder: - set.Set(name, ff.Value.(Serializeder).Serialized()) + case Serializer: + _ = set.Set(name, ff.Value.(Serializer).Serialize()) default: - set.Set(name, ff.Value.String()) + _ = set.Set(name, ff.Value.String()) } } @@ -244,7 +285,58 @@ func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { } if name != "" { - (*names) = append(*names, name) + *names = append(*names, name) } } } + +type requiredFlagsErr interface { + error + getMissingFlags() []string +} + +type errRequiredFlags struct { + missingFlags []string +} + +func (e *errRequiredFlags) Error() string { + numberOfMissingFlags := len(e.missingFlags) + if numberOfMissingFlags == 1 { + return fmt.Sprintf("Required flag %q not set", e.missingFlags[0]) + } + joinedMissingFlags := strings.Join(e.missingFlags, ", ") + return fmt.Sprintf("Required flags %q not set", joinedMissingFlags) +} + +func (e *errRequiredFlags) getMissingFlags() []string { + return e.missingFlags +} + +func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { + var missingFlags []string + for _, f := range flags { + if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { + var flagPresent bool + var flagName string + for _, key := range f.Names() { + if len(key) > 1 { + flagName = key + } + + if context.IsSet(strings.TrimSpace(key)) { + flagPresent = true + } + } + + if !flagPresent && flagName != "" { + missingFlags = append(missingFlags, flagName) + } + } + } + + if len(missingFlags) != 0 { + return &errRequiredFlags{missingFlags: missingFlags} + } + + return nil +} diff --git a/context_test.go b/context_test.go index 7333ae0..c15528b 100644 --- a/context_test.go +++ b/context_test.go @@ -3,7 +3,12 @@ package cli import ( "context" "flag" + "sort" + + "os" + "strings" + "testing" "time" ) @@ -91,9 +96,11 @@ func TestContext_Float64(t *testing.T) { func TestContext_Duration(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Duration("myflag", 12*time.Second, "doc") + parentSet := flag.NewFlagSet("test", 0) parentSet.Duration("top-flag", 13*time.Second, "doc") parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) expect(t, c.Duration("myflag"), 12*time.Second) expect(t, c.Duration("top-flag"), 13*time.Second) @@ -136,7 +143,7 @@ func TestContext_Args(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") c := NewContext(nil, set, nil) - set.Parse([]string{"--myflag", "bat", "baz"}) + _ = set.Parse([]string{"--myflag", "bat", "baz"}) expect(t, c.Args().Len(), 2) expect(t, c.Bool("myflag"), true) } @@ -145,7 +152,7 @@ func TestContext_NArg(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") c := NewContext(nil, set, nil) - set.Parse([]string{"--myflag", "bat", "baz"}) + _ = set.Parse([]string{"--myflag", "bat", "baz"}) expect(t, c.NArg(), 2) } @@ -159,8 +166,8 @@ func TestContext_IsSet(t *testing.T) { parentCtx := NewContext(nil, parentSet, nil) ctx := NewContext(nil, set, parentCtx) - set.Parse([]string{"--one-flag", "--two-flag", "--three-flag", "frob"}) - parentSet.Parse([]string{"--top-flag"}) + _ = set.Parse([]string{"--one-flag", "--two-flag", "--three-flag", "frob"}) + _ = parentSet.Parse([]string{"--top-flag"}) expect(t, ctx.IsSet("one-flag"), true) expect(t, ctx.IsSet("two-flag"), true) @@ -169,6 +176,121 @@ func TestContext_IsSet(t *testing.T) { expect(t, ctx.IsSet("bogus"), false) } +// XXX Corresponds to hack in context.IsSet for flags with EnvVar field +// Should be moved to `flag_test` in v2 +func TestContext_IsSet_fromEnv(t *testing.T) { + var ( + timeoutIsSet, tIsSet bool + noEnvVarIsSet, nIsSet bool + passwordIsSet, pIsSet bool + unparsableIsSet, uIsSet bool + ) + + os.Clearenv() + _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + _ = os.Setenv("APP_PASSWORD", "") + a := App{ + Flags: []Flag{ + &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, + &StringFlag{Name: "password", Aliases: []string{"p"}, EnvVars: []string{"APP_PASSWORD"}}, + &Float64Flag{Name: "unparsable", Aliases: []string{"u"}, EnvVars: []string{"APP_UNPARSABLE"}}, + &Float64Flag{Name: "no-env-var", Aliases: []string{"n"}}, + }, + Action: func(ctx *Context) error { + timeoutIsSet = ctx.IsSet("timeout") + tIsSet = ctx.IsSet("t") + passwordIsSet = ctx.IsSet("password") + pIsSet = ctx.IsSet("p") + unparsableIsSet = ctx.IsSet("unparsable") + uIsSet = ctx.IsSet("u") + noEnvVarIsSet = ctx.IsSet("no-env-var") + nIsSet = ctx.IsSet("n") + return nil + }, + } + _ = a.Run([]string{"run"}) + expect(t, timeoutIsSet, true) + expect(t, tIsSet, true) + expect(t, passwordIsSet, true) + expect(t, pIsSet, true) + expect(t, noEnvVarIsSet, false) + expect(t, nIsSet, false) + + _ = os.Setenv("APP_UNPARSABLE", "foobar") + _ = a.Run([]string{"run"}) + expect(t, unparsableIsSet, false) + 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") @@ -177,8 +299,8 @@ func TestContext_NumFlags(t *testing.T) { globalSet.Bool("myflagGlobal", true, "doc") globalCtx := NewContext(nil, globalSet, nil) c := NewContext(nil, set, globalCtx) - set.Parse([]string{"--myflag", "--otherflag=foo"}) - globalSet.Parse([]string{"--myflagGlobal"}) + _ = set.Parse([]string{"--myflag", "--otherflag=foo"}) + _ = globalSet.Parse([]string{"--myflagGlobal"}) expect(t, c.NumFlags(), 2) } @@ -188,7 +310,7 @@ func TestContext_Set(t *testing.T) { c := NewContext(nil, set, nil) expect(t, c.IsSet("int"), false) - c.Set("int", "1") + _ = c.Set("int", "1") expect(t, c.Int("int"), 1) expect(t, c.IsSet("int"), true) } @@ -293,3 +415,153 @@ func TestContextPropagation(t *testing.T) { t.Fatal("expected context to not be nil even if the parent's context is nil") } } + +func TestCheckRequiredFlags(t *testing.T) { + tdata := []struct { + testCase string + parseInput []string + envVarInput [2]string + flags []Flag + expectedAnError bool + expectedErrorContents []string + }{ + { + testCase: "empty", + }, + { + testCase: "optional", + flags: []Flag{ + &StringFlag{Name: "optionalFlag"}, + }, + }, + { + testCase: "required", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true}, + }, + expectedAnError: true, + expectedErrorContents: []string{"requiredFlag"}, + }, + { + testCase: "required_and_present", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true}, + }, + parseInput: []string{"--requiredFlag", "myinput"}, + }, + { + testCase: "required_and_present_via_env_var", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true, EnvVar: "REQUIRED_FLAG"}, + }, + envVarInput: [2]string{"REQUIRED_FLAG", "true"}, + }, + { + testCase: "required_and_optional", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "optionalFlag"}, + }, + expectedAnError: true, + }, + { + testCase: "required_and_optional_and_optional_present", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "optionalFlag"}, + }, + parseInput: []string{"--optionalFlag", "myinput"}, + expectedAnError: true, + }, + { + testCase: "required_and_optional_and_optional_present_via_env_var", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "optionalFlag", EnvVar: "OPTIONAL_FLAG"}, + }, + envVarInput: [2]string{"OPTIONAL_FLAG", "true"}, + expectedAnError: true, + }, + { + testCase: "required_and_optional_and_required_present", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "optionalFlag"}, + }, + parseInput: []string{"--requiredFlag", "myinput"}, + }, + { + testCase: "two_required", + flags: []Flag{ + &StringFlag{Name: "requiredFlagOne", Required: true}, + &StringFlag{Name: "requiredFlagTwo", Required: true}, + }, + expectedAnError: true, + expectedErrorContents: []string{"requiredFlagOne", "requiredFlagTwo"}, + }, + { + testCase: "two_required_and_one_present", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "requiredFlagTwo", Required: true}, + }, + parseInput: []string{"--requiredFlag", "myinput"}, + expectedAnError: true, + }, + { + testCase: "two_required_and_both_present", + flags: []Flag{ + &StringFlag{Name: "requiredFlag", Required: true}, + &StringFlag{Name: "requiredFlagTwo", Required: true}, + }, + parseInput: []string{"--requiredFlag", "myinput", "--requiredFlagTwo", "myinput"}, + }, + { + testCase: "required_flag_with_short_name", + flags: []Flag{ + &StringSliceFlag{Name: "names, N", Required: true}, + }, + parseInput: []string{"-N", "asd", "-N", "qwe"}, + }, + { + testCase: "required_flag_with_multiple_short_names", + flags: []Flag{ + &StringSliceFlag{Name: "names, N, n", Required: true}, + }, + parseInput: []string{"-n", "asd", "-n", "qwe"}, + }, + } + 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]) + } + ctx := &Context{} + context := NewContext(ctx.App, set, ctx) + context.Command.Flags = test.flags + + // logic under test + err := checkRequiredFlags(test.flags, context) + + // assertions + if test.expectedAnError && err == nil { + t.Errorf("expected an error, but there was none") + } + if !test.expectedAnError && err != nil { + 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) + } + } + }) + } +} diff --git a/docs.go b/docs.go new file mode 100644 index 0000000..5b94566 --- /dev/null +++ b/docs.go @@ -0,0 +1,148 @@ +package cli + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" + "text/template" + + "github.com/cpuguy83/go-md2man/v2/md2man" +) + +// ToMarkdown creates a markdown string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToMarkdown() (string, error) { + var w bytes.Buffer + if err := a.writeDocTemplate(&w); err != nil { + return "", err + } + return w.String(), nil +} + +// ToMan creates a man page string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToMan() (string, error) { + var w bytes.Buffer + if err := a.writeDocTemplate(&w); err != nil { + return "", err + } + man := md2man.Render(w.Bytes()) + return string(man), nil +} + +type cliTemplate struct { + App *App + Commands []string + GlobalArgs []string + SynopsisArgs []string +} + +func (a *App) writeDocTemplate(w io.Writer) error { + const name = "cli" + t, err := template.New(name).Parse(MarkdownDocTemplate) + if err != nil { + return err + } + return t.ExecuteTemplate(w, name, &cliTemplate{ + App: a, + Commands: prepareCommands(a.Commands, 0), + GlobalArgs: prepareArgsWithValues(a.Flags), + SynopsisArgs: prepareArgsSynopsis(a.Flags), + }) +} + +func prepareCommands(commands []Command, level int) []string { + coms := []string{} + for i := range commands { + command := &commands[i] + if command.Hidden { + continue + } + usage := "" + if command.Usage != "" { + usage = command.Usage + } + + prepared := fmt.Sprintf("%s %s\n\n%s\n", + strings.Repeat("#", level+2), + strings.Join(command.Names(), ", "), + usage, + ) + + flags := prepareArgsWithValues(command.Flags) + if len(flags) > 0 { + prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) + } + + coms = append(coms, prepared) + + // recursevly iterate subcommands + if len(command.Subcommands) > 0 { + coms = append( + coms, + prepareCommands(command.Subcommands, level+1)..., + ) + } + } + + return coms +} + +func prepareArgsWithValues(flags []Flag) []string { + return prepareFlags(flags, ", ", "**", "**", `""`, true) +} + +func prepareArgsSynopsis(flags []Flag) []string { + return prepareFlags(flags, "|", "[", "]", "[value]", false) +} + +func prepareFlags( + flags []Flag, + sep, opener, closer, value string, + addDetails bool, +) []string { + args := []string{} + for _, f := range flags { + flag, ok := f.(DocGenerationFlag) + if !ok { + continue + } + modifiedArg := opener + for _, s := range strings.Split(flag.GetName(), ",") { + trimmed := strings.TrimSpace(s) + if len(modifiedArg) > len(opener) { + modifiedArg += sep + } + if len(trimmed) > 1 { + modifiedArg += fmt.Sprintf("--%s", trimmed) + } else { + modifiedArg += fmt.Sprintf("-%s", trimmed) + } + } + modifiedArg += closer + if flag.TakesValue() { + modifiedArg += fmt.Sprintf("=%s", value) + } + + if addDetails { + modifiedArg += flagDetails(flag) + } + + args = append(args, modifiedArg+"\n") + + } + sort.Strings(args) + return args +} + +// flagDetails returns a string containing the flags metadata +func flagDetails(flag DocGenerationFlag) string { + description := flag.GetUsage() + value := flag.GetValue() + if value != "" { + description += " (default: " + value + ")" + } + return ": " + description +} diff --git a/docs_test.go b/docs_test.go new file mode 100644 index 0000000..f18d12d --- /dev/null +++ b/docs_test.go @@ -0,0 +1,122 @@ +package cli + +import ( + "io/ioutil" + "testing" +) + +func testApp() *App { + app := NewApp() + app.Name = "greet" + app.Flags = []Flag{ + StringFlag{ + Name: "socket, s", + Usage: "some 'usage' text", + Value: "value", + TakesFile: true, + }, + StringFlag{Name: "flag, fl, f"}, + BoolFlag{ + Name: "another-flag, b", + Usage: "another usage text", + }, + } + app.Commands = []Command{{ + Aliases: []string{"c"}, + Flags: []Flag{ + StringFlag{ + Name: "flag, fl, f", + TakesFile: true, + }, + BoolFlag{ + Name: "another-flag, b", + Usage: "another usage text", + }, + }, + Name: "config", + Usage: "another usage test", + Subcommands: []Command{{ + Aliases: []string{"s", "ss"}, + Flags: []Flag{ + StringFlag{Name: "sub-flag, sub-fl, s"}, + BoolFlag{ + Name: "sub-command-flag, s", + Usage: "some usage text", + }, + }, + Name: "sub-config", + Usage: "another usage test", + }}, + }, { + Aliases: []string{"i", "in"}, + Name: "info", + Usage: "retrieve generic information", + }, { + Name: "some-command", + }, { + Name: "hidden-command", + Hidden: true, + }} + app.UsageText = "app [first_arg] [second_arg]" + app.Usage = "Some app" + app.Author = "Harrison" + app.Email = "harrison@lolwut.com" + app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + return app +} + +func expectFileContent(t *testing.T, file, expected string) { + data, err := ioutil.ReadFile(file) + expect(t, err, nil) + expect(t, string(data), expected) +} + +func TestToMarkdownFull(t *testing.T) { + // Given + app := testApp() + + // When + res, err := app.ToMarkdown() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-full.md", res) +} + +func TestToMarkdownNoFlags(t *testing.T) { + // Given + app := testApp() + app.Flags = nil + + // When + res, err := app.ToMarkdown() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-no-flags.md", res) +} + +func TestToMarkdownNoCommands(t *testing.T) { + // Given + app := testApp() + app.Commands = nil + + // When + res, err := app.ToMarkdown() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-no-commands.md", res) +} + +func TestToMan(t *testing.T) { + // Given + app := testApp() + + // When + res, err := app.ToMan() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-full.man", res) +} diff --git a/errors.go b/errors.go index 6259699..2f12338 100644 --- a/errors.go +++ b/errors.go @@ -59,25 +59,33 @@ type ExitCoder interface { ExitCode() int } -type exitError struct { +type ExitError struct { exitCode int message interface{} } -// Exit wraps a message and exit code into an ExitCoder suitable for handling by -// HandleExitCoder -func Exit(message interface{}, exitCode int) ExitCoder { - return &exitError{ +// NewExitError makes a new *ExitError +func NewExitError(message interface{}, exitCode int) *ExitError { + return &ExitError{ exitCode: exitCode, message: message, } } -func (ee *exitError) Error() string { +// Exit wraps a message and exit code into an ExitCoder suitable for handling by +// HandleExitCoder +func Exit(message interface{}, exitCode int) ExitCoder { + return &ExitError{ + exitCode: exitCode, + message: message, + } +} + +func (ee *ExitError) Error() string { return fmt.Sprintf("%v", ee.message) } -func (ee *exitError) ExitCode() int { +func (ee *ExitError) ExitCode() int { return ee.exitCode } diff --git a/fish.go b/fish.go new file mode 100644 index 0000000..cf183af --- /dev/null +++ b/fish.go @@ -0,0 +1,194 @@ +package cli + +import ( + "bytes" + "fmt" + "io" + "strings" + "text/template" +) + +// ToFishCompletion creates a fish completion string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToFishCompletion() (string, error) { + var w bytes.Buffer + if err := a.writeFishCompletionTemplate(&w); err != nil { + return "", err + } + return w.String(), nil +} + +type fishCompletionTemplate struct { + App *App + Completions []string + AllCommands []string +} + +func (a *App) writeFishCompletionTemplate(w io.Writer) error { + const name = "cli" + t, err := template.New(name).Parse(FishCompletionTemplate) + if err != nil { + return err + } + allCommands := []string{} + + // Add global flags + completions := a.prepareFishFlags(a.VisibleFlags(), allCommands) + + // Add help flag + if !a.HideHelp { + completions = append( + completions, + a.prepareFishFlags([]Flag{HelpFlag}, allCommands)..., + ) + } + + // Add version flag + if !a.HideVersion { + completions = append( + completions, + a.prepareFishFlags([]Flag{VersionFlag}, allCommands)..., + ) + } + + // Add commands and their flags + completions = append( + completions, + a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})..., + ) + + return t.ExecuteTemplate(w, name, &fishCompletionTemplate{ + App: a, + Completions: completions, + AllCommands: allCommands, + }) +} + +func (a *App) prepareFishCommands(commands []Command, allCommands *[]string, previousCommands []string) []string { + completions := []string{} + for i := range commands { + command := &commands[i] + + if command.Hidden { + continue + } + + var completion strings.Builder + completion.WriteString(fmt.Sprintf( + "complete -r -c %s -n '%s' -a '%s'", + a.Name, + a.fishSubcommandHelper(previousCommands), + strings.Join(command.Names(), " "), + )) + + if command.Usage != "" { + completion.WriteString(fmt.Sprintf(" -d '%s'", + escapeSingleQuotes(command.Usage))) + } + + if !command.HideHelp { + completions = append( + completions, + a.prepareFishFlags([]Flag{HelpFlag}, command.Names())..., + ) + } + + *allCommands = append(*allCommands, command.Names()...) + completions = append(completions, completion.String()) + completions = append( + completions, + a.prepareFishFlags(command.Flags, command.Names())..., + ) + + // recursevly iterate subcommands + if len(command.Subcommands) > 0 { + completions = append( + completions, + a.prepareFishCommands( + command.Subcommands, allCommands, command.Names(), + )..., + ) + } + } + + return completions +} + +func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string { + completions := []string{} + for _, f := range flags { + flag, ok := f.(DocGenerationFlag) + if !ok { + continue + } + + completion := &strings.Builder{} + completion.WriteString(fmt.Sprintf( + "complete -c %s -n '%s'", + a.Name, + a.fishSubcommandHelper(previousCommands), + )) + + fishAddFileFlag(f, completion) + + for idx, opt := range strings.Split(flag.GetName(), ",") { + if idx == 0 { + completion.WriteString(fmt.Sprintf( + " -l %s", strings.TrimSpace(opt), + )) + } else { + completion.WriteString(fmt.Sprintf( + " -s %s", strings.TrimSpace(opt), + )) + + } + } + + if flag.TakesValue() { + completion.WriteString(" -r") + } + + if flag.GetUsage() != "" { + completion.WriteString(fmt.Sprintf(" -d '%s'", + escapeSingleQuotes(flag.GetUsage()))) + } + + completions = append(completions, completion.String()) + } + + return completions +} + +func fishAddFileFlag(flag Flag, completion *strings.Builder) { + switch f := flag.(type) { + case GenericFlag: + if f.TakesFile { + return + } + case StringFlag: + if f.TakesFile { + return + } + case StringSliceFlag: + if f.TakesFile { + return + } + } + completion.WriteString(" -f") +} + +func (a *App) fishSubcommandHelper(allCommands []string) string { + fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name) + if len(allCommands) > 0 { + fishHelper = fmt.Sprintf( + "__fish_seen_subcommand_from %s", + strings.Join(allCommands, " "), + ) + } + return fishHelper + +} + +func escapeSingleQuotes(input string) string { + return strings.Replace(input, `'`, `\'`, -1) +} diff --git a/fish_test.go b/fish_test.go new file mode 100644 index 0000000..a4c1871 --- /dev/null +++ b/fish_test.go @@ -0,0 +1,17 @@ +package cli + +import ( + "testing" +) + +func TestFishCompletion(t *testing.T) { + // Given + app := testApp() + + // When + res, err := app.ToFishCompletion() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-fish-full.fish", res) +} diff --git a/flag-types.json b/flag-types.json deleted file mode 100644 index bd5ec3f..0000000 --- a/flag-types.json +++ /dev/null @@ -1,98 +0,0 @@ -[ - { - "name": "Bool", - "type": "bool", - "context_default": "false", - "parser": "strconv.ParseBool(f.Value.String())" - }, - { - "name": "Duration", - "type": "time.Duration", - "doctail": " (see https://golang.org/pkg/time/#ParseDuration)", - "context_default": "0", - "parser": "time.ParseDuration(f.Value.String())" - }, - { - "name": "Float64", - "type": "float64", - "context_default": "0", - "parser": "strconv.ParseFloat(f.Value.String(), 64)" - }, - { - "name": "Generic", - "type": "Generic", - "dest": false, - "context_default": "nil", - "context_type": "interface{}" - }, - { - "name": "Int64", - "type": "int64", - "context_default": "0", - "parser": "strconv.ParseInt(f.Value.String(), 0, 64)" - }, - { - "name": "Int", - "type": "int", - "context_default": "0", - "parser": "strconv.ParseInt(f.Value.String(), 0, 64)", - "parser_cast": "int(parsed)" - }, - { - "name": "IntSlice", - "type": "*IntSlice", - "dest": false, - "context_default": "nil", - "context_type": "[]int", - "parser": "(f.Value.(*IntSlice)).Value(), error(nil)" - }, - { - "name": "Int64Slice", - "type": "*Int64Slice", - "dest": false, - "context_default": "nil", - "context_type": "[]int64", - "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)" - }, - { - "name": "Float64Slice", - "type": "*Float64Slice", - "dest": false, - "context_default": "nil", - "context_type": "[]float64", - "parser": "(f.Value.(*Float64Slice)).Value(), error(nil)" - }, - { - "name": "String", - "type": "string", - "context_default": "\"\"", - "parser": "f.Value.String(), error(nil)" - }, - { - "name": "Path", - "type": "string", - "context_default": "\"\"", - "parser": "f.Value.String(), error(nil)" - }, - { - "name": "StringSlice", - "type": "*StringSlice", - "dest": false, - "context_default": "nil", - "context_type": "[]string", - "parser": "(f.Value.(*StringSlice)).Value(), error(nil)" - }, - { - "name": "Uint64", - "type": "uint64", - "context_default": "0", - "parser": "strconv.ParseUint(f.Value.String(), 0, 64)" - }, - { - "name": "Uint", - "type": "uint", - "context_default": "0", - "parser": "strconv.ParseUint(f.Value.String(), 0, 64)", - "parser_cast": "uint(parsed)" - } -] diff --git a/flag.go b/flag.go index 364362c..6a32574 100644 --- a/flag.go +++ b/flag.go @@ -1,9 +1,9 @@ package cli import ( - "encoding/json" "flag" "fmt" + "io/ioutil" "reflect" "regexp" "runtime" @@ -22,50 +22,66 @@ var ( ) // GenerateCompletionFlag enables completion for all commands and subcommands -var GenerateCompletionFlag Flag = &BoolFlag{ - Name: "generate-completion", +//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", 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'", -} - // VersionFlag prints the version for the application var VersionFlag Flag = &BoolFlag{ - Name: "version", - Aliases: []string{"v"}, - Usage: "print the version", + Name: "version, v", + 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", - Aliases: []string{"h"}, - Usage: "show help", + Name: "help, h", + Usage: "show help", } // FlagStringer converts a flag definition to a string. This is used by help // to display a flag. var FlagStringer FlagStringFunc = stringifyFlag -// Serializeder is used to circumvent the limitations of flag.FlagSet.Set -type Serializeder interface { - Serialized() string +// Serializer is used to circumvent the limitations of flag.FlagSet.Set +type Serializer interface { + Serialize() string } +// FlagNamePrefixer converts a full flag name and its placeholder into the help +// message flag prefix. This is used by the default FlagStringer. +var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames + +// FlagEnvHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagEnvHinter FlagEnvHintFunc = withEnvHint + +// FlagFileHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagFileHinter FlagFileHintFunc = withFileHint + // FlagsByName is a slice of Flag. type FlagsByName []Flag @@ -74,12 +90,16 @@ 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 } func (f FlagsByName) Swap(i, j int) { @@ -92,711 +112,59 @@ func (f FlagsByName) Swap(i, j int) { type Flag interface { fmt.Stringer // Apply Flag settings to the given flag set - Apply(*flag.FlagSet) + Apply(*flag.FlagSet) error Names() []string } -// errorableFlag is an interface that allows us to return errors during apply -// it allows flags defined in this library to return errors in a fashion backwards compatible -// TODO remove in v2 and modify the existing Flag interface to return errors -type errorableFlag interface { +// RequiredFlag is an interface that allows us to mark flags as required +// it allows flags required flags to be backwards compatible with the Flag interface +type RequiredFlag interface { Flag - ApplyWithError(*flag.FlagSet) error + IsRequired() bool +} + +// DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationFlag interface { + Flag + + // TakesValue returns true of the flag takes a value, otherwise false + TakesValue() bool + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string } func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { set := flag.NewFlagSet(name, flag.ContinueOnError) for _, f := range flags { - //TODO remove in v2 when errorableFlag is removed - if ef, ok := f.(errorableFlag); ok { - if err := ef.ApplyWithError(set); err != nil { - return nil, err - } - } else { - f.Apply(set) + if err := f.Apply(set); err != nil { + return nil, err } } + set.SetOutput(ioutil.Discard) return set, nil } -// Generic is a generic parseable type identified by a specific flag -type Generic interface { - Set(value string) error - String() string -} - -// Apply takes the flagset and calls Set on the generic flag with the value -// provided by the user for parsing by the flag -// Ignores parsing errors -func (f *GenericFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError takes the flagset and calls Set on the generic flag with the -// value provided by the user for parsing by the flag -func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error { - val := f.Value - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - val.Set(envVal) - break - } - } +func eachName(longName string, fn func(string)) { + parts := strings.Split(longName, ",") + for _, name := range parts { + name = strings.Trim(name, " ") + fn(name) } - - for _, name := range f.Names() { - set.Var(val, name, f.Usage) - } - return nil -} - -// StringSlice wraps a []string to satisfy flag.Value -type StringSlice struct { - slice []string - hasBeenSet bool -} - -// NewStringSlice creates a *StringSlice with default values -func NewStringSlice(defaults ...string) *StringSlice { - return &StringSlice{slice: append([]string{}, defaults...)} -} - -// Set appends the string value to the list of values -func (f *StringSlice) Set(value string) error { - if !f.hasBeenSet { - f.slice = []string{} - f.hasBeenSet = true - } - - if strings.HasPrefix(value, slPfx) { - // Deserializing assumes overwrite - _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) - f.hasBeenSet = true - return nil - } - - f.slice = append(f.slice, value) - return nil -} - -// String returns a readable representation of this value (for usage defaults) -func (f *StringSlice) String() string { - return fmt.Sprintf("%s", f.slice) -} - -// Serialized allows StringSlice to fulfill Serializeder -func (f *StringSlice) Serialized() string { - jsonBytes, _ := json.Marshal(f.slice) - return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) -} - -// Value returns the slice of strings set by this flag -func (f *StringSlice) Value() []string { - return f.slice -} - -// Get returns the slice of strings set by this flag -func (f *StringSlice) Get() interface{} { - return *f -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *StringSliceFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := NewStringSlice() - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %q as string value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break - } - } - } - - if f.Value == nil { - f.Value = NewStringSlice() - } - - for _, name := range f.Names() { - set.Var(f.Value, name, f.Usage) - } - return nil -} - -// IntSlice wraps an []int to satisfy flag.Value -type IntSlice struct { - slice []int - hasBeenSet bool -} - -// NewIntSlice makes an *IntSlice with default values -func NewIntSlice(defaults ...int) *IntSlice { - return &IntSlice{slice: append([]int{}, defaults...)} -} - -// NewInt64Slice makes an *Int64Slice with default values -func NewInt64Slice(defaults ...int64) *Int64Slice { - return &Int64Slice{slice: append([]int64{}, defaults...)} -} - -// SetInt directly adds an integer to the list of values -func (i *IntSlice) SetInt(value int) { - if !i.hasBeenSet { - i.slice = []int{} - i.hasBeenSet = true - } - - i.slice = append(i.slice, value) -} - -// Set parses the value into an integer and appends it to the list of values -func (i *IntSlice) Set(value string) error { - if !i.hasBeenSet { - i.slice = []int{} - i.hasBeenSet = true - } - - if strings.HasPrefix(value, slPfx) { - // Deserializing assumes overwrite - _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) - i.hasBeenSet = true - return nil - } - - tmp, err := strconv.ParseInt(value, 0, 64) - if err != nil { - return err - } - - i.slice = append(i.slice, int(tmp)) - return nil -} - -// String returns a readable representation of this value (for usage defaults) -func (f *IntSlice) String() string { - return fmt.Sprintf("%#v", f.slice) -} - -// Serialized allows IntSlice to fulfill Serializeder -func (i *IntSlice) Serialized() string { - jsonBytes, _ := json.Marshal(i.slice) - return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) -} - -// Value returns the slice of ints set by this flag -func (i *IntSlice) Value() []int { - return i.slice -} - -// Get returns the slice of ints set by this flag -func (f *IntSlice) Get() interface{} { - return *f -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *IntSliceFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := NewIntSlice() - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break - } - } - } - - if f.Value == nil { - f.Value = NewIntSlice() - } - - for _, name := range f.Names() { - set.Var(f.Value, name, f.Usage) - } - return nil -} - -// Int64Slice is an opaque type for []int to satisfy flag.Value -type Int64Slice struct { - slice []int64 - hasBeenSet bool -} - -// Set parses the value into an integer and appends it to the list of values -func (f *Int64Slice) Set(value string) error { - if !f.hasBeenSet { - f.slice = []int64{} - f.hasBeenSet = true - } - - if strings.HasPrefix(value, slPfx) { - // Deserializing assumes overwrite - _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) - f.hasBeenSet = true - return nil - } - - tmp, err := strconv.ParseInt(value, 0, 64) - if err != nil { - return err - } - - f.slice = append(f.slice, tmp) - return nil -} - -// String returns a readable representation of this value (for usage defaults) -func (f *Int64Slice) String() string { - return fmt.Sprintf("%#v", f.slice) -} - -// Serialized allows Int64Slice to fulfill Serializeder -func (f *Int64Slice) Serialized() string { - jsonBytes, _ := json.Marshal(f.slice) - return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) -} - -// Value returns the slice of ints set by this flag -func (f *Int64Slice) Value() []int64 { - return f.slice -} - -// Get returns the slice of ints set by this flag -func (f *Int64Slice) Get() interface{} { - return *f -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := NewInt64Slice() - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break - } - } - } - - if f.Value == nil { - f.Value = NewInt64Slice() - } - - for _, name := range f.Names() { - set.Var(f.Value, name, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *BoolFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - f.Value = false - break - } - - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %q as bool value for flag %s: %s", envVal, f.Name, err) - } - f.Value = envValBool - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.BoolVar(f.Destination, name, f.Value, f.Usage) - continue - } - set.Bool(name, f.Value, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *StringFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - f.Value = envVal - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.StringVar(f.Destination, name, f.Value, f.Usage) - continue - } - set.String(name, f.Value, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *PathFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *PathFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - f.Value = envVal - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.StringVar(f.Destination, name, f.Value, f.Usage) - continue - } - set.String(name, f.Value, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *IntFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %q as int value for flag %s: %s", envVal, f.Name, err) - } - f.Value = int(envValInt) - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.IntVar(f.Destination, name, f.Value, f.Usage) - continue - } - set.Int(name, f.Value, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *Int64Flag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %q as int value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValInt - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.Int64Var(f.Destination, name, f.Value, f.Usage) - return nil - } - set.Int64(name, f.Value, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *UintFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %q as uint value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint(envValInt) - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.UintVar(f.Destination, name, f.Value, f.Usage) - return nil - } - set.Uint(name, f.Value, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *Uint64Flag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint64(envValInt) - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.Uint64Var(f.Destination, name, f.Value, f.Usage) - return nil - } - set.Uint64(name, f.Value, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *DurationFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - envValDuration, err := time.ParseDuration(envVal) - if err != nil { - return fmt.Errorf("could not parse %q as duration for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValDuration - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.DurationVar(f.Destination, name, f.Value, f.Usage) - continue - } - set.Duration(name, f.Value, f.Usage) - } - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *Float64Flag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - envValFloat, err := strconv.ParseFloat(envVal, 10) - if err != nil { - return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = float64(envValFloat) - break - } - } - } - - for _, name := range f.Names() { - if f.Destination != nil { - set.Float64Var(f.Destination, name, f.Value, f.Usage) - continue - } - set.Float64(name, f.Value, f.Usage) - } - return nil -} - -// NewFloat64Slice makes a *Float64Slice with default values -func NewFloat64Slice(defaults ...float64) *Float64Slice { - return &Float64Slice{slice: append([]float64{}, defaults...)} -} - -// Float64Slice is an opaque type for []float64 to satisfy flag.Value -type Float64Slice struct { - slice []float64 - hasBeenSet bool -} - -// Set parses the value into a float64 and appends it to the list of values -func (f *Float64Slice) Set(value string) error { - if !f.hasBeenSet { - f.slice = []float64{} - f.hasBeenSet = true - } - - if strings.HasPrefix(value, slPfx) { - // Deserializing assumes overwrite - _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) - f.hasBeenSet = true - return nil - } - - tmp, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } - - f.slice = append(f.slice, tmp) - return nil -} - -// String returns a readable representation of this value (for usage defaults) -func (f *Float64Slice) String() string { - return fmt.Sprintf("%#v", f.slice) -} - -// Serialized allows Float64Slice to fulfill Serializeder -func (f *Float64Slice) Serialized() string { - jsonBytes, _ := json.Marshal(f.slice) - return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) -} - -// Value returns the slice of float64s set by this flag -func (f *Float64Slice) Value() []float64 { - return f.slice -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f *Float64SliceFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f *Float64SliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVars != nil { - for _, envVar := range f.EnvVars { - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := NewFloat64Slice() - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - err := newVal.Set(s) - if err != nil { - fmt.Fprintf(ErrWriter, err.Error()) - } - } - f.Value = newVal - break - } - } - } - - if f.Value == nil { - f.Value = NewFloat64Slice() - } - - for _, name := range f.Names() { - set.Var(f.Value, name, f.Usage) - } - return nil } func visibleFlags(fl []Flag) []Flag { - visible := []Flag{} - for _, flag := range fl { - field := flagValue(flag).FieldByName("Hidden") + var visible []Flag + for _, f := range fl { + field := flagValue(f).FieldByName("Hidden") if !field.IsValid() || !field.Bool() { - visible = append(visible, flag) + visible = append(visible, f) } } return visible @@ -858,6 +226,7 @@ func withEnvHint(envVars []string, str string) string { suffix = "%" sep = "%, %" } + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix) } return str + envText @@ -902,6 +271,14 @@ func flagStringField(f Flag, name string) string { return "" } +func withFileHint(filePath, str string) string { + fileText := "" + if filePath != "" { + fileText = fmt.Sprintf(" [%s]", filePath) + } + return str + fileText +} + func flagValue(f Flag) reflect.Value { fv := reflect.ValueOf(f) for fv.Kind() == reflect.Ptr { @@ -956,14 +333,14 @@ func stringifyFlag(f Flag) string { placeholder = defaultPlaceholder } - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) + usageWithDefault := strings.TrimSpace(usage + defaultValueString) return withEnvHint(flagStringSliceField(f, "EnvVars"), fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } func stringifyIntSliceFlag(f *IntSliceFlag) string { - defaultVals := []string{} + var defaultVals []string if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) @@ -974,10 +351,10 @@ func stringifyIntSliceFlag(f *IntSliceFlag) string { } func stringifyInt64SliceFlag(f *Int64SliceFlag) string { - defaultVals := []string{} + var defaultVals []string if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) } } @@ -985,7 +362,8 @@ func stringifyInt64SliceFlag(f *Int64SliceFlag) string { } func stringifyFloat64SliceFlag(f *Float64SliceFlag) string { - defaultVals := []string{} + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) @@ -996,11 +374,12 @@ func stringifyFloat64SliceFlag(f *Float64SliceFlag) string { } func stringifyStringSliceFlag(f *StringSliceFlag) string { - defaultVals := []string{} + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { for _, s := range f.Value.Value() { if len(s) > 0 { - defaultVals = append(defaultVals, fmt.Sprintf("%q", s)) + defaultVals = append(defaultVals, strconv.Quote(s)) } } } @@ -1032,3 +411,18 @@ func hasFlag(flags []Flag, fl Flag) bool { return false } + +func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) { + for _, envVar := range envVars { + envVar = strings.TrimSpace(envVar) + if val, ok := syscall.Getenv(envVar); ok { + return val, true + } + } + for _, fileVar := range strings.Split(filePath, ",") { + if data, err := ioutil.ReadFile(fileVar); err == nil { + return string(data), true + } + } + return "", false +} diff --git a/flag_bool.go b/flag_bool.go new file mode 100644 index 0000000..665a8ac --- /dev/null +++ b/flag_bool.go @@ -0,0 +1,107 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// BoolFlag is a flag with type bool +type BoolFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value bool + DefaultText string + Destination *bool +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *BoolFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *BoolFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *BoolFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *BoolFlag) TakesValue() bool { + return false +} + +// GetUsage returns the usage string for the flag +func (f *BoolFlag) 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 (f *BoolFlag) GetValue() string { + return "" +} + +// Apply populates the flag given the flag set and environment +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 %s as bool value for flag %s: %s", val, f.Name, err) + } + f.Value = valBool + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.BoolVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.Bool(name, f.Value, f.Usage) + } + + return nil +} + +// Bool looks up the value of a local BoolFlag, returns +// false if not found +func (c *Context) Bool(name string) bool { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupBool(name, fs) + } + 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 { + parsed, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return false + } + return parsed + } + return false +} diff --git a/flag_duration.go b/flag_duration.go new file mode 100644 index 0000000..e3ccd18 --- /dev/null +++ b/flag_duration.go @@ -0,0 +1,104 @@ +package cli + +import ( + "flag" + "fmt" + "time" +) + +// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration) +type DurationFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value time.Duration + DefaultText string + Destination *time.Duration +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *DurationFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *DurationFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *DurationFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *DurationFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *DurationFlag) 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 (f *DurationFlag) GetValue() string { + return f.Value.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 != ""{ + valDuration, err := time.ParseDuration(val) + if err != nil { + return fmt.Errorf("could not parse %s as duration value for flag %s: %s", val, f.Name, err) + } + f.Value = valDuration + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.DurationVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.Duration(name, f.Value, f.Usage) + } + return nil +} + +// Duration looks up the value of a local DurationFlag, returns +// 0 if not found +func (c *Context) Duration(name string) time.Duration { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupDuration(name, fs) + } + 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) + if f != nil { + parsed, err := time.ParseDuration(f.Value.String()) + if err != nil { + return 0 + } + return parsed + } + return 0 +} diff --git a/flag_float64.go b/flag_float64.go new file mode 100644 index 0000000..641cb0b --- /dev/null +++ b/flag_float64.go @@ -0,0 +1,106 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// Float64Flag is a flag with type float64 +type Float64Flag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value float64 + DefaultText string + Destination *float64 +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Float64Flag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Float64Flag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Float64Flag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Float64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Float64Flag) 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 (f *Float64Flag) GetValue() string { + return fmt.Sprintf("%f", f.Value) +} + +// Apply populates the flag given the flag set and environment +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 %s as float64 value for flag %s: %s", val, f.Name, err) + } + f.Value = valFloat + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.Float64Var(f.Destination, name, f.Value, f.Usage) + continue + } + set.Float64(name, f.Value, f.Usage) + } + + return nil +} + +// Float64 looks up the value of a local Float64Flag, returns +// 0 if not found +func (c *Context) Float64(name string) float64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64(name, fs) + } + 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 { + parsed, err := strconv.ParseFloat(f.Value.String(), 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} diff --git a/flag_float64_slice.go b/flag_float64_slice.go new file mode 100644 index 0000000..8331c43 --- /dev/null +++ b/flag_float64_slice.go @@ -0,0 +1,164 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// Float64Slice wraps []float64 to satisfy flag.Value +type Float64Slice struct { + slice []float64 + hasBeenSet bool +} + +// NewFloat64Slice makes a *Float64Slice with default values +func NewFloat64Slice(defaults ...float64) *Float64Slice { + return &Float64Slice{slice: append([]float64{}, defaults...)} +} + +// Set parses the value into a float64 and appends it to the list of values +func (f *Float64Slice) Set(value string) error { + if !f.hasBeenSet { + f.slice = []float64{} + f.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) + f.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + + f.slice = append(f.slice, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Float64Slice) String() string { + return fmt.Sprintf("%#v", f.slice) +} + +// Serialize allows Float64Slice to fulfill Serializer +func (f *Float64Slice) Serialize() string { + jsonBytes, _ := json.Marshal(f.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of float64s set by this flag +func (f *Float64Slice) Value() []float64 { + return f.slice +} + +// Get returns the slice of float64s set by this flag +func (f *Float64Slice) Get() interface{} { + return *f +} + +// Float64SliceFlag is a flag with type *Float64Slice +type Float64SliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *Float64Slice + DefaultText string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Float64SliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Float64SliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Float64SliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Float64SliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Float64SliceFlag) 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 (f *Float64SliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// 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{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %s as float64 slice value for flag %s: %s", f.Value, f.Name, err) + } + } + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &Float64Slice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// Float64Slice looks up the value of a local Float64SliceFlag, returns +// nil if not found +func (c *Context) Float64Slice(name string) []float64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64Slice(name, fs) + } + 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 { + parsed, err := (f.Value.(*Float64Slice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/flag_generated.go b/flag_generated.go deleted file mode 100644 index a958150..0000000 --- a/flag_generated.go +++ /dev/null @@ -1,629 +0,0 @@ -package cli - -import ( - "flag" - "strconv" - "time" -) - -// WARNING: This file is generated! - -// BoolFlag is a flag with type bool -type BoolFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value bool - DefaultText string - - Destination *bool -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *BoolFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *BoolFlag) Names() []string { - return flagNames(f) -} - -// Bool looks up the value of a local BoolFlag, returns -// false if not found -func (c *Context) Bool(name string) bool { - if fs := lookupFlagSet(name, c); fs != nil { - return lookupBool(name, fs) - } - return false -} - -func lookupBool(name string, set *flag.FlagSet) bool { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseBool(f.Value.String()) - if err != nil { - return false - } - return parsed - } - return false -} - -// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration) -type DurationFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value time.Duration - DefaultText string - - Destination *time.Duration -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *DurationFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *DurationFlag) Names() []string { - return flagNames(f) -} - -// Duration looks up the value of a local DurationFlag, returns -// 0 if not found -func (c *Context) Duration(name string) time.Duration { - if fs := lookupFlagSet(name, c); fs != nil { - return lookupDuration(name, fs) - } - return 0 -} - -func lookupDuration(name string, set *flag.FlagSet) time.Duration { - f := set.Lookup(name) - if f != nil { - parsed, err := time.ParseDuration(f.Value.String()) - if err != nil { - return 0 - } - return parsed - } - return 0 -} - -// Float64Flag is a flag with type float64 -type Float64Flag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value float64 - DefaultText string - - Destination *float64 -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *Float64Flag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *Float64Flag) Names() []string { - return flagNames(f) -} - -// Float64 looks up the value of a local Float64Flag, returns -// 0 if not found -func (c *Context) Float64(name string) float64 { - if fs := lookupFlagSet(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 { - parsed, err := strconv.ParseFloat(f.Value.String(), 64) - if err != nil { - return 0 - } - return parsed - } - return 0 -} - -// GenericFlag is a flag with type Generic -type GenericFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value Generic - DefaultText string -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *GenericFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *GenericFlag) Names() []string { - return flagNames(f) -} - -// Generic looks up the value of a local GenericFlag, returns -// nil if not found -func (c *Context) Generic(name string) interface{} { - if fs := lookupFlagSet(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 { - parsed, err := f.Value, error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// Int64Flag is a flag with type int64 -type Int64Flag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value int64 - DefaultText string - - Destination *int64 -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *Int64Flag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *Int64Flag) Names() []string { - return flagNames(f) -} - -// Int64 looks up the value of a local Int64Flag, returns -// 0 if not found -func (c *Context) Int64(name string) int64 { - if fs := lookupFlagSet(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 { - parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) - if err != nil { - return 0 - } - return parsed - } - return 0 -} - -// IntFlag is a flag with type int -type IntFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value int - DefaultText string - - Destination *int -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *IntFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *IntFlag) Names() []string { - return flagNames(f) -} - -// Int looks up the value of a local IntFlag, returns -// 0 if not found -func (c *Context) Int(name string) int { - if fs := lookupFlagSet(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 { - parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) - if err != nil { - return 0 - } - return int(parsed) - } - return 0 -} - -// IntSliceFlag is a flag with type *IntSlice -type IntSliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value *IntSlice - DefaultText string -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *IntSliceFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *IntSliceFlag) Names() []string { - return flagNames(f) -} - -// IntSlice looks up the value of a local IntSliceFlag, returns -// nil if not found -func (c *Context) IntSlice(name string) []int { - if fs := lookupFlagSet(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 { - parsed, err := (f.Value.(*IntSlice)).Value(), error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// Int64SliceFlag is a flag with type *Int64Slice -type Int64SliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value *Int64Slice - DefaultText string -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *Int64SliceFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *Int64SliceFlag) Names() []string { - return flagNames(f) -} - -// Int64Slice looks up the value of a local Int64SliceFlag, returns -// nil if not found -func (c *Context) Int64Slice(name string) []int64 { - if fs := lookupFlagSet(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 { - parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// Float64SliceFlag is a flag with type *Float64Slice -type Float64SliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value *Float64Slice - DefaultText string -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *Float64SliceFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *Float64SliceFlag) Names() []string { - return flagNames(f) -} - -// Float64Slice looks up the value of a local Float64SliceFlag, returns -// nil if not found -func (c *Context) Float64Slice(name string) []float64 { - if fs := lookupFlagSet(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 { - parsed, err := (f.Value.(*Float64Slice)).Value(), error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// StringFlag is a flag with type string -type StringFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value string - DefaultText string - - Destination *string -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *StringFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *StringFlag) Names() []string { - return flagNames(f) -} - -// String looks up the value of a local StringFlag, returns -// "" if not found -func (c *Context) String(name string) string { - if fs := lookupFlagSet(name, c); fs != nil { - return lookupString(name, fs) - } - return "" -} - -func lookupString(name string, set *flag.FlagSet) string { - f := set.Lookup(name) - if f != nil { - parsed, err := f.Value.String(), error(nil) - if err != nil { - return "" - } - return parsed - } - return "" -} - -// PathFlag is a flag with type string -type PathFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value string - DefaultText string - - Destination *string -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *PathFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *PathFlag) Names() []string { - return flagNames(f) -} - -// Path looks up the value of a local PathFlag, returns -// "" if not found -func (c *Context) Path(name string) string { - if fs := lookupFlagSet(name, c); fs != nil { - return lookupPath(name, fs) - } - return "" -} - -func lookupPath(name string, set *flag.FlagSet) string { - f := set.Lookup(name) - if f != nil { - parsed, err := f.Value.String(), error(nil) - if err != nil { - return "" - } - return parsed - } - return "" -} - -// StringSliceFlag is a flag with type *StringSlice -type StringSliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value *StringSlice - DefaultText string -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *StringSliceFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *StringSliceFlag) Names() []string { - return flagNames(f) -} - -// StringSlice looks up the value of a local StringSliceFlag, returns -// nil if not found -func (c *Context) StringSlice(name string) []string { - if fs := lookupFlagSet(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 { - parsed, err := (f.Value.(*StringSlice)).Value(), error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// Uint64Flag is a flag with type uint64 -type Uint64Flag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value uint64 - DefaultText string - - Destination *uint64 -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *Uint64Flag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *Uint64Flag) Names() []string { - return flagNames(f) -} - -// Uint64 looks up the value of a local Uint64Flag, returns -// 0 if not found -func (c *Context) Uint64(name string) uint64 { - if fs := lookupFlagSet(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 { - parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) - if err != nil { - return 0 - } - return parsed - } - return 0 -} - -// UintFlag is a flag with type uint -type UintFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value uint - DefaultText string - - Destination *uint -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *UintFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *UintFlag) Names() []string { - return flagNames(f) -} - -// Uint looks up the value of a local UintFlag, returns -// 0 if not found -func (c *Context) Uint(name string) uint { - if fs := lookupFlagSet(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 { - parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) - if err != nil { - return 0 - } - return uint(parsed) - } - return 0 -} diff --git a/flag_generic.go b/flag_generic.go new file mode 100644 index 0000000..169c364 --- /dev/null +++ b/flag_generic.go @@ -0,0 +1,104 @@ +package cli + +import ( + "flag" + "fmt" +) + +// Generic is a generic parseable type identified by a specific flag +type Generic interface { + Set(value string) error + String() string +} + +// GenericFlag is a flag with type Generic +type GenericFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value Generic + DefaultText string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *GenericFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *GenericFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *GenericFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *GenericFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *GenericFlag) 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 (f *GenericFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply takes the flagset and calls Set on the generic flag with the value +// 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 %s as value for flag %s: %s", val, f.Name, err) + } + } + + for _, name := range f.Names() { + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// 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) +} + +// 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 { + parsed, err := f.Value, error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/flag_int.go b/flag_int.go new file mode 100644 index 0000000..f026650 --- /dev/null +++ b/flag_int.go @@ -0,0 +1,103 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// IntFlag is a flag with type int +type IntFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value int + DefaultText string + Destination *int +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *IntFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *IntFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *IntFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *IntFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *IntFlag) 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 (f *IntFlag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + +// Apply populates the flag given the flag set and environment +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 %s as int value for flag %s: %s", val, f.Name, err) + } + f.Value = int(valInt) + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.IntVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.Int(name, f.Value, f.Usage) + } + + return nil +} + +// Int looks up the value of a local IntFlag, returns +// 0 if not found +func (c *Context) Int(name string) int { + return lookupInt(name, c.flagSet) +} + +// 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 { + parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return int(parsed) + } + return 0 +} diff --git a/flag_int64.go b/flag_int64.go new file mode 100644 index 0000000..60ef026 --- /dev/null +++ b/flag_int64.go @@ -0,0 +1,103 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// Int64Flag is a flag with type int64 +type Int64Flag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value int64 + DefaultText string + Destination *int64 +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Int64Flag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Int64Flag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Int64Flag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Int64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Int64Flag) 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 (f *Int64Flag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + +// Apply populates the flag given the flag set and environment +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 %s as int value for flag %s: %s", val, f.Name, err) + } + f.Value = valInt + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.Int64Var(f.Destination, name, f.Value, f.Usage) + continue + } + set.Int64(name, f.Value, f.Usage) + } + + return nil +} + +// Int64 looks up the value of a local Int64Flag, returns +// 0 if not found +func (c *Context) Int64(name string) int64 { + return lookupInt64(name, c.flagSet) +} + +// 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 { + parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} diff --git a/flag_int64_slice.go b/flag_int64_slice.go new file mode 100644 index 0000000..2c82329 --- /dev/null +++ b/flag_int64_slice.go @@ -0,0 +1,164 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// Int64Slice wraps []int64 to satisfy flag.Value +type Int64Slice struct { + slice []int64 + hasBeenSet bool +} + +// NewInt64Slice makes an *Int64Slice with default values +func NewInt64Slice(defaults ...int64) *Int64Slice { + return &Int64Slice{slice: append([]int64{}, defaults...)} +} + +// Set parses the value into an integer and appends it to the list of values +func (i *Int64Slice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []int64{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return err + } + + i.slice = append(i.slice, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (i *Int64Slice) String() string { + return fmt.Sprintf("%#v", i.slice) +} + +// Serialize allows Int64Slice to fulfill Serializer +func (i *Int64Slice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (i *Int64Slice) Value() []int64 { + return i.slice +} + +// Get returns the slice of ints set by this flag +func (i *Int64Slice) Get() interface{} { + return *i +} + +// Int64SliceFlag is a flag with type *Int64Slice +type Int64SliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *Int64Slice + DefaultText string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Int64SliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Int64SliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Int64SliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Int64SliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f Int64SliceFlag) 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 (f *Int64SliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = &Int64Slice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", f.Value, f.Name, err) + } + } + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &Int64Slice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// Int64Slice looks up the value of a local Int64SliceFlag, returns +// nil if not found +func (c *Context) Int64Slice(name string) []int64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt64Slice(name, fs) + } + 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 { + parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/flag_int_slice.go b/flag_int_slice.go new file mode 100644 index 0000000..c885dd3 --- /dev/null +++ b/flag_int_slice.go @@ -0,0 +1,175 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// IntSlice wraps []int to satisfy flag.Value +type IntSlice struct { + slice []int + hasBeenSet bool +} + +// NewIntSlice makes an *IntSlice with default values +func NewIntSlice(defaults ...int) *IntSlice { + return &IntSlice{slice: append([]int{}, defaults...)} +} + +// TODO: Consistently have specific Set function for Int64 and Float64 ? +// SetInt directly adds an integer to the list of values +func (i *IntSlice) SetInt(value int) { + if !i.hasBeenSet { + i.slice = []int{} + i.hasBeenSet = true + } + + i.slice = append(i.slice, value) +} + +// Set parses the value into an integer and appends it to the list of values +func (i *IntSlice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []int{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return err + } + + i.slice = append(i.slice, int(tmp)) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (i *IntSlice) String() string { + return fmt.Sprintf("%#v", i.slice) +} + +// Serialize allows IntSlice to fulfill Serializer +func (i *IntSlice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (i *IntSlice) Value() []int { + return i.slice +} + +// Get returns the slice of ints set by this flag +func (i *IntSlice) Get() interface{} { + return *i +} + +// IntSliceFlag is a flag with type *IntSlice +type IntSliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *IntSlice + DefaultText string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *IntSliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *IntSliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *IntSliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *IntSliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f IntSliceFlag) 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 (f *IntSliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = &IntSlice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", val, f.Name, err) + } + } + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &IntSlice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// IntSlice looks up the value of a local IntSliceFlag, returns +// nil if not found +func (c *Context) IntSlice(name string) []int { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupIntSlice(name, c.flagSet) + } + 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 { + parsed, err := (f.Value.(*IntSlice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/flag_path.go b/flag_path.go new file mode 100644 index 0000000..0e7fd01 --- /dev/null +++ b/flag_path.go @@ -0,0 +1,85 @@ +package cli + +import "flag" + +type PathFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + EnvVar string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value string + DefaultText string + Destination *string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *PathFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *PathFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *PathFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *PathFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *PathFlag) 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 (f *PathFlag) GetValue() string { + return f.Value +} + +// Apply populates the flag given the flag set and environment +func (f *PathFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = val + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) + return + } + set.String(name, f.Value, f.Usage) + } + + return nil +} + +// String looks up the value of a local PathFlag, returns +// "" if not found +func (c *Context) Path(name string) string { + return lookupPath(name, c.flagSet) +} + +func lookupPath(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + parsed, err := f.Value.String(), error(nil) + if err != nil { + return "" + } + return parsed + } + return "" +} diff --git a/flag_string.go b/flag_string.go new file mode 100644 index 0000000..fccc670 --- /dev/null +++ b/flag_string.go @@ -0,0 +1,95 @@ +package cli + +import "flag" + +// StringFlag is a flag with type string +type StringFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + EnvVar string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value string + DefaultText string + Destination *string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (s *StringFlag) String() string { + return FlagStringer(s) +} + +// Names returns the names of the flag +func (s *StringFlag) Names() []string { + return flagNames(s) +} + +// IsRequired returns whether or not the flag is required +func (s *StringFlag) IsRequired() bool { + return s.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (s *StringFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (s *StringFlag) GetUsage() string { + return s.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 +} + +// 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 + } + + for _, name := range s.Names() { + if s.Destination != nil { + set.StringVar(s.Destination, name, s.Value, s.Usage) + return + } + set.String(name, s.Value, s.Usage) + } + + return nil +} + +// String looks up the value of a local StringFlag, returns +// "" if not found +func (c *Context) String(name string) string { + return lookupString(name, c.flagSet) +} + +// 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 { + parsed, err := f.Value.String(), error(nil) + if err != nil { + return "" + } + return parsed + } + return "" +} diff --git a/flag_string_slice.go b/flag_string_slice.go new file mode 100644 index 0000000..604fab4 --- /dev/null +++ b/flag_string_slice.go @@ -0,0 +1,155 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strings" +) + +// StringSlice wraps a []string to satisfy flag.Value +type StringSlice struct { + slice []string + hasBeenSet bool +} + +// NewStringSlice creates a *StringSlice with default values +func NewStringSlice(defaults ...string) *StringSlice { + return &StringSlice{slice: append([]string{}, defaults...)} +} + +// Set appends the string value to the list of values +func (s *StringSlice) Set(value string) error { + if !s.hasBeenSet { + s.slice = []string{} + s.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &s.slice) + s.hasBeenSet = true + return nil + } + + s.slice = append(s.slice, value) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (s *StringSlice) String() string { + return fmt.Sprintf("%s", s.slice) +} + +// Serialize allows StringSlice to fulfill Serializer +func (s *StringSlice) Serialize() string { + jsonBytes, _ := json.Marshal(s.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of strings set by this flag +func (s *StringSlice) Value() []string { + return s.slice +} + +// Get returns the slice of strings set by this flag +func (s *StringSlice) Get() interface{} { + return *s +} + +// StringSliceFlag is a flag with type *StringSlice +type StringSliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value *StringSlice + DefaultText string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *StringSliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *StringSliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *StringSliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *StringSliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *StringSliceFlag) 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 (f *StringSliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +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 %s as string value for flag %s: %s", val, f.Name, err) + } + } + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &StringSlice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// StringSlice looks up the value of a local StringSliceFlag, returns +// nil if not found +func (c *Context) StringSlice(name string) []string { + return lookupStringSlice(name, c.flagSet) +} + +// 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 { + parsed, err := (f.Value.(*StringSlice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/flag_test.go b/flag_test.go index 6ed3a76..d097776 100644 --- a/flag_test.go +++ b/flag_test.go @@ -3,6 +3,8 @@ package cli import ( "flag" "fmt" + "io" + "io/ioutil" "os" "reflect" "regexp" @@ -111,10 +113,16 @@ func TestFlagsFromEnv(t *testing.T) { {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } +//<<<<<<< HEAD +// for i, test := range flagTests { +// os.Clearenv() +// envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) +// os.Setenv(envVarSlice.Index(0).String(), test.input) +//======= for i, test := range flagTests { - clearenv() - envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) - os.Setenv(envVarSlice.Index(0).String(), test.input) + os.Clearenv() + _ = os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input) +//>>>>>>> master a := App{ Flags: []Flag{test.flag}, Action: func(ctx *Context) error { @@ -180,8 +188,10 @@ func TestStringFlagDefaultText(t *testing.T) { } func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_FOO", "derp") + + os.Clearenv() + _ = os.Setenv("APP_FOO", "derp") + for _, test := range stringFlagTests { flag := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} output := flag.String() @@ -196,6 +206,48 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { } } +var prefixStringFlagTests = []struct { + name string + usage string + value string + prefixer FlagNamePrefixFunc + expected string +}{ + {"foo", "", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: foo, ph: value\t"}, + {"f", "", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: f, ph: value\t"}, + {"f", "The total `foo` desired", "all", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: f, ph: foo\tThe total foo desired (default: \"all\")"}, + {"test", "", "Something", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: test, ph: value\t(default: \"Something\")"}, + {"config,c", "Load configuration from `FILE`", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: config,c, ph: FILE\tLoad configuration from FILE"}, + {"config,c", "Load configuration from `CONFIG`", "config.json", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, +} + +func TestFlagNamePrefixer(t *testing.T) { + defer func() { + FlagNamePrefixer = prefixedNames + }() + + for _, test := range prefixStringFlagTests { + FlagNamePrefixer = test.prefixer + flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} + output := flag.String() + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + func TestStringFlagApply_SetsAllNames(t *testing.T) { v := "mmm" fl := StringFlag{Name: "hay", Aliases: []string{"H", "hayyy"}, Destination: &v} @@ -223,6 +275,7 @@ func TestPathFlagHelpOutput(t *testing.T) { flag := &PathFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} output := flag.String() + if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) } @@ -230,7 +283,7 @@ func TestPathFlagHelpOutput(t *testing.T) { } func TestPathFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() + os.Clearenv() os.Setenv("APP_PATH", "/path/to/file") for _, test := range pathFlagTests { flag := &PathFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_PATH"}} @@ -257,17 +310,54 @@ func TestPathFlagApply_SetsAllNames(t *testing.T) { expect(t, v, "/path/to/file/PATH") } +var envHintFlagTests = []struct { + name string + env string + hinter FlagEnvHintFunc + expected string +}{ + {"foo", "", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: , str: --foo value\t"}, + {"f", "", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: , str: -f value\t"}, + {"foo", "ENV_VAR", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: ENV_VAR, str: --foo value\t"}, + {"f", "ENV_VAR", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: ENV_VAR, str: -f value\t"}, +} + +func TestFlagEnvHinter(t *testing.T) { + defer func() { + FlagEnvHinter = withEnvHint + }() + + for _, test := range envHintFlagTests { + FlagEnvHinter = test.hinter + flag := StringFlag{Name: test.name, EnvVar: test.env} + output := flag.String() + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + var stringSliceFlagTests = []struct { name string aliases []string value *StringSlice expected string }{ + {"foo", nil, NewStringSlice(""), "--foo value\t"}, {"f", nil, NewStringSlice(""), "-f value\t"}, {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, + } func TestStringSliceFlagHelpOutput(t *testing.T) { @@ -282,8 +372,9 @@ func TestStringSliceFlagHelpOutput(t *testing.T) { } func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_QWWX", "11,4") + os.Clearenv() + _ = os.Setenv("APP_QWWX", "11,4") + for _, test := range stringSliceFlagTests { flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} output := flag.String() @@ -327,8 +418,10 @@ func TestIntFlagHelpOutput(t *testing.T) { } func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() + os.Clearenv() os.Setenv("APP_BAR", "2") + _ = os.Setenv("APP_BAR", "2") + for _, test := range intFlagTests { flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -374,8 +467,9 @@ func TestInt64FlagHelpOutput(t *testing.T) { } func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_BAR", "2") + os.Clearenv() + _ = os.Setenv("APP_BAR", "2") + for _, test := range int64FlagTests { flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -410,8 +504,9 @@ func TestUintFlagHelpOutput(t *testing.T) { } func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_BAR", "2") + os.Clearenv() + _ = os.Setenv("APP_BAR", "2") + for _, test := range uintFlagTests { flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -446,8 +541,9 @@ func TestUint64FlagHelpOutput(t *testing.T) { } func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_BAR", "2") + os.Clearenv() + _ = os.Setenv("APP_BAR", "2") + for _, test := range uint64FlagTests { flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -482,8 +578,9 @@ func TestDurationFlagHelpOutput(t *testing.T) { } func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_BAR", "2h3m6s") + os.Clearenv() + _ = os.Setenv("APP_BAR", "2h3m6s") + for _, test := range durationFlagTests { flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := flag.String() @@ -532,8 +629,9 @@ func TestIntSliceFlagHelpOutput(t *testing.T) { } func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_SMURF", "42,3") + os.Clearenv() + _ = os.Setenv("APP_SMURF", "42,3") + for _, test := range intSliceFlagTests { flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() @@ -581,8 +679,9 @@ func TestInt64SliceFlagHelpOutput(t *testing.T) { } func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_SMURF", "42,17179869184") + os.Clearenv() + _ = os.Setenv("APP_SMURF", "42,17179869184") + for _, test := range int64SliceFlagTests { flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := flag.String() @@ -607,8 +706,8 @@ var float64FlagTests = []struct { func TestFloat64FlagHelpOutput(t *testing.T) { for _, test := range float64FlagTests { - flag := &Float64Flag{Name: test.name, Value: float64(0.1)} - output := flag.String() + f := &Float64Flag{Name: test.name, Value: 0.1} + output := f.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -617,8 +716,9 @@ func TestFloat64FlagHelpOutput(t *testing.T) { } func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_BAZ", "99.4") + os.Clearenv() + _ = os.Setenv("APP_BAZ", "99.4") + for _, test := range float64FlagTests { flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} output := flag.String() @@ -634,7 +734,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { } func TestFloat64FlagApply_SetsAllNames(t *testing.T) { - v := float64(99.1) + v := 99.1 fl := Float64Flag{Name: "noodles", Aliases: []string{"N", "nurbles"}, Destination: &v} set := flag.NewFlagSet("test", 0) fl.Apply(set) @@ -668,7 +768,7 @@ func TestFloat64SliceFlagHelpOutput(t *testing.T) { } func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() + os.Clearenv() os.Setenv("APP_SMURF", "0.1234,-10.5") for _, test := range float64SliceFlagTests { flag := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} @@ -705,8 +805,9 @@ func TestGenericFlagHelpOutput(t *testing.T) { } func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { - clearenv() - os.Setenv("APP_ZAP", "3") + os.Clearenv() + _ = os.Setenv("APP_ZAP", "3") + for _, test := range genericFlagTests { flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} output := flag.String() @@ -731,7 +832,7 @@ func TestGenericFlagApply_SetsAllNames(t *testing.T) { } func TestParseMultiString(t *testing.T) { - (&App{ + _ = (&App{ Flags: []Flag{ &StringFlag{Name: "serve", Aliases: []string{"s"}}, }, @@ -749,7 +850,7 @@ func TestParseMultiString(t *testing.T) { func TestParseDestinationString(t *testing.T) { var dest string - a := App{ + _ = (&App{ Flags: []Flag{ &StringFlag{ Name: "dest", @@ -762,14 +863,13 @@ func TestParseDestinationString(t *testing.T) { } return nil }, - } - a.Run([]string{"run", "--dest", "10"}) + }).Run([]string{"run", "--dest", "10"}) } func TestParseMultiStringFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_COUNT", "20") - (&App{ + os.Clearenv() + _ = os.Setenv("APP_COUNT", "20") + _ = (&App{ Flags: []Flag{ &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}}, }, @@ -786,9 +886,9 @@ func TestParseMultiStringFromEnv(t *testing.T) { } func TestParseMultiStringFromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_COUNT", "20") - (&App{ + os.Clearenv() + _ = os.Setenv("APP_COUNT", "20") + _ = (&App{ Flags: []Flag{ &StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}}, }, @@ -805,7 +905,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) { } func TestParseMultiStringSlice(t *testing.T) { - (&App{ + _ = (&App{ Flags: []Flag{ &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice()}, }, @@ -823,7 +923,7 @@ func TestParseMultiStringSlice(t *testing.T) { } func TestParseMultiStringSliceWithDefaults(t *testing.T) { - (&App{ + _ = (&App{ Flags: []Flag{ &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, @@ -841,7 +941,7 @@ func TestParseMultiStringSliceWithDefaults(t *testing.T) { } func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { - (&App{ + _ = (&App{ Flags: []Flag{ &StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewStringSlice("9", "2")}, }, @@ -858,10 +958,10 @@ func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { } func TestParseMultiStringSliceFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") - (&App{ + _ = (&App{ Flags: []Flag{ &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"APP_INTERVALS"}}, }, @@ -878,10 +978,10 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { } func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") - (&App{ + _ = (&App{ Flags: []Flag{ &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"APP_INTERVALS"}}, }, @@ -898,10 +998,10 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { } func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") - (&App{ + _ = (&App{ Flags: []Flag{ &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, @@ -918,10 +1018,10 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { } func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") - (&App{ + _ = (&App{ Flags: []Flag{ &StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewStringSlice("1", "2", "5"), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, @@ -938,7 +1038,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { } func TestParseMultiInt(t *testing.T) { - a := App{ + _ = (&App{ Flags: []Flag{ &IntFlag{Name: "serve", Aliases: []string{"s"}}, }, @@ -951,13 +1051,12 @@ func TestParseMultiInt(t *testing.T) { } return nil }, - } - a.Run([]string{"run", "-s", "10"}) + }).Run([]string{"run", "-s", "10"}) } func TestParseDestinationInt(t *testing.T) { var dest int - a := App{ + _ = (&App{ Flags: []Flag{ &IntFlag{ Name: "dest", @@ -970,14 +1069,13 @@ func TestParseDestinationInt(t *testing.T) { } return nil }, - } - a.Run([]string{"run", "--dest", "10"}) + }).Run([]string{"run", "--dest", "10"}) } func TestParseMultiIntFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_TIMEOUT_SECONDS", "10") - a := App{ + os.Clearenv() + _ = os.Setenv("APP_TIMEOUT_SECONDS", "10") + _ = (&App{ Flags: []Flag{ &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, @@ -990,14 +1088,13 @@ func TestParseMultiIntFromEnv(t *testing.T) { } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } func TestParseMultiIntFromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_TIMEOUT_SECONDS", "10") - a := App{ + os.Clearenv() + _ = os.Setenv("APP_TIMEOUT_SECONDS", "10") + _ = (&App{ Flags: []Flag{ &IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, @@ -1010,12 +1107,11 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) { } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } func TestParseMultiIntSlice(t *testing.T) { - (&App{ + _ = (&App{ Flags: []Flag{ &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice()}, }, @@ -1032,7 +1128,7 @@ func TestParseMultiIntSlice(t *testing.T) { } func TestParseMultiIntSliceWithDefaults(t *testing.T) { - (&App{ + _ = (&App{ Flags: []Flag{ &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, @@ -1049,7 +1145,7 @@ func TestParseMultiIntSliceWithDefaults(t *testing.T) { } func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { - (&App{ + _ = (&App{ Flags: []Flag{ &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewIntSlice(9, 2)}, }, @@ -1066,10 +1162,10 @@ func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { } func TestParseMultiIntSliceFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") - (&App{ + _ = (&App{ Flags: []Flag{ &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"APP_INTERVALS"}}, }, @@ -1086,10 +1182,10 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { } func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") - (&App{ + _ = (&App{ Flags: []Flag{ &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(1, 2, 5), EnvVars: []string{"APP_INTERVALS"}}, }, @@ -1106,10 +1202,10 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { } func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,40") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") - (&App{ + _ = (&App{ Flags: []Flag{ &IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewIntSlice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, @@ -1126,7 +1222,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { } func TestParseMultiInt64Slice(t *testing.T) { - (&App{ + _ = (&App{ Flags: []Flag{ &Int64SliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewInt64Slice()}, }, @@ -1143,10 +1239,10 @@ func TestParseMultiInt64Slice(t *testing.T) { } func TestParseMultiInt64SliceFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,17179869184") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,17179869184") - (&App{ + _ = (&App{ Flags: []Flag{ &Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewInt64Slice(), EnvVars: []string{"APP_INTERVALS"}}, }, @@ -1163,10 +1259,10 @@ func TestParseMultiInt64SliceFromEnv(t *testing.T) { } func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "20,30,17179869184") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,17179869184") - (&App{ + _ = (&App{ Flags: []Flag{ &Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewInt64Slice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, @@ -1183,7 +1279,7 @@ func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { } func TestParseMultiFloat64(t *testing.T) { - a := App{ + _ = (&App{ Flags: []Flag{ &Float64Flag{Name: "serve", Aliases: []string{"s"}}, }, @@ -1196,13 +1292,12 @@ func TestParseMultiFloat64(t *testing.T) { } return nil }, - } - a.Run([]string{"run", "-s", "10.2"}) + }).Run([]string{"run", "-s", "10.2"}) } func TestParseDestinationFloat64(t *testing.T) { var dest float64 - a := App{ + _ = (&App{ Flags: []Flag{ &Float64Flag{ Name: "dest", @@ -1215,14 +1310,13 @@ func TestParseDestinationFloat64(t *testing.T) { } return nil }, - } - a.Run([]string{"run", "--dest", "10.2"}) + }).Run([]string{"run", "--dest", "10.2"}) } func TestParseMultiFloat64FromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_TIMEOUT_SECONDS", "15.5") - a := App{ + os.Clearenv() + _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + _ = (&App{ Flags: []Flag{ &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}}, }, @@ -1235,14 +1329,13 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } func TestParseMultiFloat64FromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_TIMEOUT_SECONDS", "15.5") - a := App{ + os.Clearenv() + _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + _ = (&App{ Flags: []Flag{ &Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}}, }, @@ -1255,15 +1348,14 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } func TestParseMultiFloat64SliceFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "0.1,-10.5") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "0.1,-10.5") - (&App{ + _ = (&App{ Flags: []Flag{ &Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewFloat64Slice(), EnvVars: []string{"APP_INTERVALS"}}, }, @@ -1280,10 +1372,10 @@ func TestParseMultiFloat64SliceFromEnv(t *testing.T) { } func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_INTERVALS", "0.1234,-10.5") + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "0.1234,-10.5") - (&App{ + _ = (&App{ Flags: []Flag{ &Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewFloat64Slice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, }, @@ -1300,7 +1392,7 @@ func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { } func TestParseMultiBool(t *testing.T) { - a := App{ + _ = (&App{ Flags: []Flag{ &BoolFlag{Name: "serve", Aliases: []string{"s"}}, }, @@ -1313,13 +1405,36 @@ func TestParseMultiBool(t *testing.T) { } return nil }, - } - a.Run([]string{"run", "--serve"}) + }).Run([]string{"run", "--serve"}) +} + +func TestParseBoolShortOptionHandle(t *testing.T) { + _ = (&App{ + Commands: []*Command{ + { + Name: "foobar", + UseShortOptionHandling: true, + Action: func(ctx *Context) error { + if ctx.Bool("serve") != true { + t.Errorf("main name not set") + } + if ctx.Bool("option") != true { + t.Errorf("short name not set") + } + return nil + }, + Flags: []Flag{ + BoolFlag{Name: "serve, s"}, + BoolFlag{Name: "option, o"}, + }, + }, + }, + }).Run([]string{"run", "foobar", "-so"}) } func TestParseDestinationBool(t *testing.T) { var dest bool - a := App{ + _ = (&App{ Flags: []Flag{ &BoolFlag{ Name: "dest", @@ -1332,14 +1447,13 @@ func TestParseDestinationBool(t *testing.T) { } return nil }, - } - a.Run([]string{"run", "--dest"}) + }).Run([]string{"run", "--dest"}) } func TestParseMultiBoolFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_DEBUG", "1") - a := App{ + os.Clearenv() + _ = os.Setenv("APP_DEBUG", "1") + _ = (&App{ Flags: []Flag{ &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}}, }, @@ -1352,14 +1466,13 @@ func TestParseMultiBoolFromEnv(t *testing.T) { } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } func TestParseMultiBoolFromEnvCascade(t *testing.T) { - clearenv() + os.Clearenv() os.Setenv("APP_DEBUG", "1") - a := App{ + _ = (&App{ Flags: []Flag{ &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}}, }, @@ -1372,97 +1485,93 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } -func TestParseMultiBoolTrue(t *testing.T) { - a := App{ - Flags: []Flag{ - &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, - }, - Action: func(ctx *Context) error { - if ctx.Bool("implode") { - t.Errorf("main name not set") - } - if ctx.Bool("i") { - t.Errorf("short name not set") - } - return nil - }, +func TestParseBoolTFromEnv(t *testing.T) { + var boolTFlagTests = []struct { + input string + output bool + }{ + {"", false}, + {"1", true}, + {"false", false}, + {"true", true}, } - a.Run([]string{"run", "--implode=false"}) -} -func TestParseDestinationBoolTrue(t *testing.T) { - dest := true - - a := App{ - Flags: []Flag{ - &BoolFlag{ - Name: "dest", - Value: true, - Destination: &dest, + for _, test := range boolTFlagTests { + os.Clearenv() + _ = os.Setenv("DEBUG", test.input) + _ = (&App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "DEBUG"}, }, - }, - Action: func(ctx *Context) error { - if dest { - t.Errorf("expected destination Bool false") - } - return nil - }, + Action: func(ctx *Context) error { + if ctx.Bool("debug") != test.output { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("debug")) + } + if ctx.Bool("d") != test.output { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("d")) + } + return nil + }, + }).Run([]string{"run"}) } - a.Run([]string{"run", "--dest=false"}) } -func TestParseMultiBoolTrueFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_DEBUG", "0") - a := App{ - Flags: []Flag{ - &BoolFlag{ - Name: "debug", - Aliases: []string{"d"}, - Value: true, - EnvVars: []string{"APP_DEBUG"}, +func TestParseMultiBoolT(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, }, + Action: func(ctx *Context) error { + if ctx.Bool("implode") { + t.Errorf("main name not set") + } + if ctx.Bool("i") { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "--implode=false"}) +} + +func TestParseMultiBoolTFromEnv(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_DEBUG", "0") + _ = (&App{ + Flags: []Flag{ + &BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, }, Action: func(ctx *Context) error { - if ctx.Bool("debug") { + if ctx.BoolT("debug") != false { t.Errorf("main name not set from env") } - if ctx.Bool("d") { + if ctx.BoolT("d") != false { t.Errorf("short name not set from env") } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } -func TestParseMultiBoolTrueFromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_DEBUG", "0") - a := App{ +func TestParseMultiBoolTFromEnvCascade(t *testing.T) { + os.Clearenv() + _ = os.Setenv("APP_DEBUG", "0") + _ = (&App{ Flags: []Flag{ - &BoolFlag{ - Name: "debug", - Aliases: []string{"d"}, - Value: true, - EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}, - }, + &BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, }, Action: func(ctx *Context) error { - if ctx.Bool("debug") { + if ctx.BoolT("debug") != false { t.Errorf("main name not set from env") } - if ctx.Bool("d") { + if ctx.BoolT("d") != false { t.Errorf("short name not set from env") } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } type Parser [2]string @@ -1488,7 +1597,7 @@ func (p *Parser) Get() interface{} { } func TestParseGeneric(t *testing.T) { - a := App{ + _ = (&App{ Flags: []Flag{ &GenericFlag{Name: "serve", Aliases: []string{"s"}, Value: &Parser{}}, }, @@ -1501,14 +1610,14 @@ func TestParseGeneric(t *testing.T) { } return nil }, - } - a.Run([]string{"run", "-s", "10,20"}) + }).Run([]string{"run", "-s", "10,20"}) } func TestParseGenericFromEnv(t *testing.T) { - clearenv() - os.Setenv("APP_SERVE", "20,30") - a := App{ + os.Clearenv() + _ = os.Setenv("APP_SERVE", "20,30") + _ = (&App{ + Flags: []Flag{ &GenericFlag{ Name: "serve", @@ -1526,14 +1635,13 @@ func TestParseGenericFromEnv(t *testing.T) { } return nil }, - } - a.Run([]string{"run"}) + }).Run([]string{"run"}) } func TestParseGenericFromEnvCascade(t *testing.T) { - clearenv() - os.Setenv("APP_FOO", "99,2000") - a := App{ + os.Clearenv() + _ = os.Setenv("APP_FOO", "99,2000") + _ = (&App{ Flags: []Flag{ &GenericFlag{ Name: "foos", @@ -1547,13 +1655,47 @@ func TestParseGenericFromEnvCascade(t *testing.T) { } return nil }, + }).Run([]string{"run"}) +} + +func TestFlagFromFile(t *testing.T) { + os.Clearenv() + os.Setenv("APP_FOO", "123") + + temp, err := ioutil.TempFile("", "urfave_cli_test") + if err != nil { + t.Error(err) + return + } + _, _ = io.WriteString(temp, "abc") + _ = temp.Close() + defer func() { + _ = os.Remove(temp.Name()) + }() + + var filePathTests = []struct { + path string + name string + expected string + }{ + {"file-does-not-exist", "APP_BAR", ""}, + {"file-does-not-exist", "APP_FOO", "123"}, + {"file-does-not-exist", "APP_FOO,APP_BAR", "123"}, + {temp.Name(), "APP_FOO", "123"}, + {temp.Name(), "APP_BAR", "abc"}, + } + + for _, filePathTest := range filePathTests { + got, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path) + if want := filePathTest.expected; got != want { + t.Errorf("Did not expect %v - Want %v", got, want) + } } - a.Run([]string{"run"}) } func TestStringSlice_Serialized_Set(t *testing.T) { sl0 := NewStringSlice("a", "b") - ser0 := sl0.Serialized() + ser0 := sl0.Serialize() if len(ser0) < len(slPfx) { t.Fatalf("serialized shorter than expected: %q", ser0) @@ -1569,7 +1711,7 @@ func TestStringSlice_Serialized_Set(t *testing.T) { func TestIntSlice_Serialized_Set(t *testing.T) { sl0 := NewIntSlice(1, 2) - ser0 := sl0.Serialized() + ser0 := sl0.Serialize() if len(ser0) < len(slPfx) { t.Fatalf("serialized shorter than expected: %q", ser0) @@ -1585,7 +1727,7 @@ func TestIntSlice_Serialized_Set(t *testing.T) { func TestInt64Slice_Serialized_Set(t *testing.T) { sl0 := NewInt64Slice(int64(1), int64(2)) - ser0 := sl0.Serialized() + ser0 := sl0.Serialize() if len(ser0) < len(slPfx) { t.Fatalf("serialized shorter than expected: %q", ser0) diff --git a/flag_uint.go b/flag_uint.go new file mode 100644 index 0000000..7a3e943 --- /dev/null +++ b/flag_uint.go @@ -0,0 +1,104 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// UintFlag is a flag with type uint +type UintFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value uint + DefaultText string + Destination *uint +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *UintFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *UintFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *UintFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *UintFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *UintFlag) GetUsage() string { + return f.Usage +} + +// Apply populates the flag given the flag set and environment +func (f *UintFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseUint(val, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint value for flag %s: %s", val, f.Name, err) + } + + f.Value = uint(valInt) + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.UintVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.Uint(name, f.Value, f.Usage) + } + + return nil +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *UintFlag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + +// Uint looks up the value of a local UintFlag, returns +// 0 if not found +func (c *Context) Uint(name string) uint { + return lookupUint(name, c.flagSet) +} + +// 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 { + parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return uint(parsed) + } + return 0 +} diff --git a/flag_uint64.go b/flag_uint64.go new file mode 100644 index 0000000..65534c7 --- /dev/null +++ b/flag_uint64.go @@ -0,0 +1,104 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// Uint64Flag is a flag with type uint64 +type Uint64Flag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value uint64 + DefaultText string + Destination *uint64 +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Uint64Flag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Uint64Flag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Uint64Flag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Uint64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Uint64Flag) GetUsage() string { + return f.Usage +} + +// Apply populates the flag given the flag set and environment +func (f *Uint64Flag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseUint(val, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", val, f.Name, err) + } + + f.Value = valInt + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.Uint64Var(f.Destination, name, f.Value, f.Usage) + continue + } + set.Uint64(name, f.Value, f.Usage) + } + + return nil +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Uint64Flag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + +// Uint64 looks up the value of a local Uint64Flag, returns +// 0 if not found +func (c *Context) Uint64(name string) uint64 { + return lookupUint64(name, c.flagSet) +} + +// 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 { + parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} diff --git a/funcs.go b/funcs.go index fa7d502..9ffe246 100644 --- a/funcs.go +++ b/funcs.go @@ -1,7 +1,7 @@ package cli -// ShellCompleteFunc is an action to execute when the shell completion flag is set -type ShellCompleteFunc func(*Context) +// BashCompleteFunc is an action to execute when the shell completion flag is set +type BashCompleteFunc func(*Context) // BeforeFunc is an action to execute before any subcommands are run, but after // the context is ready if a non-nil error is returned, no subcommands are run @@ -23,6 +23,22 @@ type CommandNotFoundFunc func(*Context, string) // is displayed and the execution is interrupted. type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error +// ExitErrHandlerFunc is executed if provided in order to handle ExitError values +// returned by Actions and Before/After functions. +type ExitErrHandlerFunc func(context *Context, err error) + // FlagStringFunc is used by the help generation to display a flag, which is // expected to be a single line. type FlagStringFunc func(Flag) string + +// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix +// text for a flag's full name. +type FlagNamePrefixFunc func(fullName []string, placeholder string) string + +// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help +// with the environment variable details. +type FlagEnvHintFunc func(envVars []string, str string) string + +// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help +// with the file path details. +type FlagFileHintFunc func(filePath, str string) string diff --git a/generate-flag-types b/generate-flag-types deleted file mode 100755 index d77d470..0000000 --- a/generate-flag-types +++ /dev/null @@ -1,255 +0,0 @@ -#!/usr/bin/env python -""" -The flag types that ship with the cli library have many things in common, and -so we can take advantage of the `go generate` command to create much of the -source code from a list of definitions. These definitions attempt to cover -the parts that vary between flag types, and should evolve as needed. - -An example of the minimum definition needed is: - - { - "name": "SomeType", - "type": "sometype", - "context_default": "nil" - } - -In this example, the code generated for the `cli` package will include a type -named `SomeTypeFlag` that is expected to wrap a value of type `sometype`. -Fetching values by name via `*cli.Context` will default to a value of `nil`. - -A more complete, albeit somewhat redundant, example showing all available -definition keys is: - - { - "name": "VeryMuchType", - "type": "*VeryMuchType", - "value": true, - "dest": false, - "doctail": " which really only wraps a []float64, oh well!", - "context_type": "[]float64", - "context_default": "nil", - "parser": "parseVeryMuchType(f.Value.String())", - "parser_cast": "[]float64(parsed)" - } - -The meaning of each field is as follows: - - name (string) - The type "name", which will be suffixed with - `Flag` when generating the type definition - for `cli` and the wrapper type for `altsrc` - type (string) - The type that the generated `Flag` type for - `cli` is expected to "contain" as its `.Value` - member - value (bool) - Should the generated `cli` type have a `Value` - member? - dest (bool) - Should the generated `cli` type support a - destination pointer? - doctail (string) - Additional docs for the `cli` flag type comment - context_type (string) - The literal type used in the `*cli.Context` - reader func signature - context_default (string) - The literal value used as the default by the - `*cli.Context` reader funcs when no value is - present - parser (string) - Literal code used to parse the flag `f`, - expected to have a return signature of - (value, error) - parser_cast (string) - Literal code used to cast the `parsed` value - returned from the `parser` code -""" - -from __future__ import print_function, unicode_literals - -import argparse -import json -import os -import subprocess -import sys -import tempfile -import textwrap - - -_PY3 = sys.version_info.major == 3 - - -class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter, - argparse.RawDescriptionHelpFormatter): - pass - - -def main(sysargs=sys.argv[:]): - parser = argparse.ArgumentParser( - description='Generate flag type code!', - formatter_class=_FancyFormatter) - parser.add_argument( - 'package', - type=str, default='cli', choices=_WRITEFUNCS.keys(), - help='Package for which flag types will be generated' - ) - parser.add_argument( - '-i', '--in-json', - type=argparse.FileType('r'), - default=sys.stdin, - help='Input JSON file which defines each type to be generated' - ) - parser.add_argument( - '-o', '--out-go', - type=argparse.FileType('w'), - default=sys.stdout, - help='Output file/stream to which generated source will be written' - ) - parser.epilog = __doc__ - - args = parser.parse_args(sysargs[1:]) - _generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json) - return 0 - - -def _generate_flag_types(writefunc, output_go, input_json): - types = json.load(input_json) - - tmp = _get_named_tmp_go() - writefunc(tmp, types) - tmp.close() - - new_content = subprocess.check_output( - ['goimports', tmp.name] - ).decode('utf-8') - - print(new_content, file=output_go, end='') - output_go.flush() - os.remove(tmp.name) - - -def _get_named_tmp_go(): - tmp_args = dict(suffix='.go', mode='w', delete=False) - if _PY3: - tmp_args['encoding'] = 'utf-8' - return tempfile.NamedTemporaryFile(**tmp_args) - - -def _set_typedef_defaults(typedef): - typedef.setdefault('doctail', '') - typedef.setdefault('context_type', typedef['type']) - typedef.setdefault('dest', True) - typedef.setdefault('parser', 'f.Value, error(nil)') - typedef.setdefault('parser_cast', 'parsed') - - -def _write_cli_flag_types(outfile, types): - _fwrite(outfile, """\ - package cli - - // WARNING: This file is generated! - - """) - - for typedef in types: - _set_typedef_defaults(typedef) - - _fwrite(outfile, """\ - // {name}Flag is a flag with type {type}{doctail} - type {name}Flag struct {{ - Name string - Aliases []string - Usage string - EnvVars []string - Hidden bool - Value {type} - DefaultText string - """.format(**typedef)) - - if typedef['dest']: - _fwrite(outfile, """\ - Destination *{type} - """.format(**typedef)) - - _fwrite(outfile, "\n}\n\n") - - _fwrite(outfile, """\ - // String returns a readable representation of this value - // (for usage defaults) - func (f *{name}Flag) String() string {{ - return FlagStringer(f) - }} - - // Names returns the names of the flag - func (f *{name}Flag) Names() []string {{ - return flagNames(f) - }} - - // {name} looks up the value of a local {name}Flag, returns - // {context_default} if not found - func (c *Context) {name}(name string) {context_type} {{ - if fs := lookupFlagSet(name, c); fs != nil {{ - return lookup{name}(name, fs) - }} - return {context_default} - }} - - func lookup{name}(name string, set *flag.FlagSet) {context_type} {{ - f := set.Lookup(name) - if f != nil {{ - parsed, err := {parser} - if err != nil {{ - return {context_default} - }} - return {parser_cast} - }} - return {context_default} - }} - """.format(**typedef)) - - -def _write_altsrc_flag_types(outfile, types): - _fwrite(outfile, """\ - package altsrc - - import "gopkg.in/urfave/cli.v2" - - // WARNING: This file is generated! - - """) - - for typedef in types: - _set_typedef_defaults(typedef) - - _fwrite(outfile, """\ - // {name}Flag is the flag type that wraps cli.{name}Flag to allow - // for other values to be specified - type {name}Flag struct {{ - *cli.{name}Flag - set *flag.FlagSet - }} - - // New{name}Flag creates a new {name}Flag - func New{name}Flag(fl *cli.{name}Flag) *{name}Flag {{ - return &{name}Flag{{{name}Flag: fl, set: nil}} - }} - - // Apply saves the flagSet for later usage calls, then calls the - // wrapped {name}Flag.Apply - func (f *{name}Flag) Apply(set *flag.FlagSet) {{ - f.set = set - f.{name}Flag.Apply(set) - }} - - // ApplyWithError saves the flagSet for later usage calls, then calls the - // wrapped {name}Flag.ApplyWithError - func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{ - f.set = set - return f.{name}Flag.ApplyWithError(set) - }} - """.format(**typedef)) - - -def _fwrite(outfile, text): - print(textwrap.dedent(text), end=None, file=outfile) - - -_WRITEFUNCS = { - 'cli': _write_cli_flag_types, - 'altsrc': _write_altsrc_flag_types -} - -if __name__ == '__main__': - sys.exit(main()) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c38d41c --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/urfave/cli/v2 + +go 1.11 + +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ef121ff --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/help.go b/help.go index 02fcf33..155654f 100644 --- a/help.go +++ b/help.go @@ -7,6 +7,7 @@ import ( "strings" "text/tabwriter" "text/template" + "unicode/utf8" ) // AppHelpTemplate is the text template for the Default help topic. @@ -89,7 +90,7 @@ var helpCommand = &Command{ return ShowCommandHelp(c, args.First()) } - ShowAppHelp(c) + _ = ShowAppHelp(c) return nil }, } @@ -129,7 +130,7 @@ var VersionPrinter = printVersion // ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code. func ShowAppHelpAndExit(c *Context, exitCode int) { - ShowAppHelp(c) + _ = ShowAppHelp(c) os.Exit(exitCode) } @@ -153,19 +154,94 @@ func ShowAppHelp(c *Context) (err error) { // DefaultAppComplete prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { - for _, command := range c.App.Commands { + DefaultCompleteWithFlags(nil)(c) +} + +func printCommandSuggestions(commands []Command, writer io.Writer) { + for _, command := range commands { if command.Hidden { continue } - for _, name := range command.Names() { - fmt.Fprintln(c.App.Writer, name) + if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { + for _, name := range command.Names() { + _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) + } + } else { + for _, name := range command.Names() { + _, _ = fmt.Fprintf(writer, "%s\n", name) + } + } + } +} + +func cliArgContains(flagName string) bool { + for _, name := range strings.Split(flagName, ",") { + name = strings.TrimSpace(name) + count := utf8.RuneCountInString(name) + if count > 2 { + count = 2 + } + flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) + for _, a := range os.Args { + if a == flag { + return true + } + } + } + return false +} + +func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { + cur := strings.TrimPrefix(lastArg, "-") + cur = strings.TrimPrefix(cur, "-") + for _, flag := range flags { + if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden { + continue + } + for _, name := range strings.Split(flag.GetName(), ",") { + name = strings.TrimSpace(name) + // this will get total count utf8 letters in flag name + count := utf8.RuneCountInString(name) + if count > 2 { + count = 2 // resuse this count to generate single - or -- in flag completion + } + // if flag name has more than one utf8 letter and last argument in cli has -- prefix then + // skip flag completion for short flags example -v or -x + if strings.HasPrefix(lastArg, "--") && count == 1 { + continue + } + // match if last argument matches this flag and it is not repeated + if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) { + flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) + _, _ = fmt.Fprintln(writer, flagCompletion) + } + } + } +} + +func DefaultCompleteWithFlags(cmd *Command) func(c *Context) { + return func(c *Context) { + if len(os.Args) > 2 { + lastArg := os.Args[len(os.Args)-2] + if strings.HasPrefix(lastArg, "-") { + printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer) + if cmd != nil { + printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer) + } + return + } + } + if cmd != nil { + printCommandSuggestions(cmd.Subcommands, c.App.Writer) + } else { + printCommandSuggestions(c.App.Commands, c.App.Writer) } } } // ShowCommandHelpAndExit - exits with code after showing help func ShowCommandHelpAndExit(c *Context, command string, code int) { - ShowCommandHelp(c, command) + _ = ShowCommandHelp(c, command) os.Exit(code) } @@ -215,49 +291,57 @@ func ShowVersion(c *Context) { } func printVersion(c *Context) { - fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) + _, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) } // ShowCompletions prints the lists of commands within a given context func ShowCompletions(c *Context) { a := c.App - if a != nil && a.ShellComplete != nil { - a.ShellComplete(c) + if a != nil && a.BashComplete != nil { + a.BashComplete(c) } } // ShowCommandCompletions prints the custom completions for a given command func ShowCommandCompletions(ctx *Context, command string) { c := ctx.App.Command(command) + //TODO: Resolve +//<<<<<<< HEAD if c != nil && c.ShellComplete != nil { c.ShellComplete(ctx) +//======= +// if c != nil { +// if c.BashComplete != nil { +// c.BashComplete(ctx) +// } else { +// DefaultCompleteWithFlags(c)(ctx) +// } +//>>>>>>> master } + } func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) { funcMap := template.FuncMap{ "join": strings.Join, } - if customFunc != nil { - for key, value := range customFunc { - funcMap[key] = value - } + for key, value := range customFunc { + funcMap[key] = value } w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) - errDebug := os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" - err := t.Execute(w, data) if err != nil { - if errDebug { - fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) + // If the writer is closed, t.Execute will fail, and there's nothing + // we can do to recover. + if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { + _, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) } return } - - w.Flush() + _ = w.Flush() } func printHelp(out io.Writer, templ string, data interface{}) { @@ -286,7 +370,7 @@ func checkHelp(c *Context) bool { func checkCommandHelp(c *Context, name string) bool { if c.Bool("h") || c.Bool("help") { - ShowCommandHelp(c, name) + _ = ShowCommandHelp(c, name) return true } @@ -295,7 +379,7 @@ func checkCommandHelp(c *Context, name string) bool { func checkSubcommandHelp(c *Context) bool { if c.Bool("h") || c.Bool("help") { - ShowSubcommandHelp(c) + _ = ShowSubcommandHelp(c) return true } diff --git a/help_test.go b/help_test.go index 375b28e..6d3a6a9 100644 --- a/help_test.go +++ b/help_test.go @@ -15,9 +15,9 @@ func Test_ShowAppHelp_NoAuthor(t *testing.T) { c := NewContext(app, nil, nil) - ShowAppHelp(c) + _ = ShowAppHelp(c) - if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 { + if bytes.Contains(output.Bytes(), []byte("AUTHOR(S):")) { t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):") } } @@ -30,9 +30,9 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) { c := NewContext(app, nil, nil) - ShowAppHelp(c) + _ = ShowAppHelp(c) - if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { + if bytes.Contains(output.Bytes(), []byte("VERSION:")) { t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") } } @@ -45,9 +45,9 @@ func Test_ShowAppHelp_HideVersion(t *testing.T) { c := NewContext(app, nil, nil) - ShowAppHelp(c) + _ = ShowAppHelp(c) - if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { + if bytes.Contains(output.Bytes(), []byte("VERSION:")) { t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") } } @@ -77,7 +77,7 @@ func Test_Help_Custom_Flags(t *testing.T) { } output := new(bytes.Buffer) app.Writer = output - app.Run([]string{"test", "-h"}) + _ = app.Run([]string{"test", "-h"}) if output.Len() > 0 { t.Errorf("unexpected output: %s", output.String()) } @@ -108,7 +108,7 @@ func Test_Version_Custom_Flags(t *testing.T) { } output := new(bytes.Buffer) app.Writer = output - app.Run([]string{"test", "-v"}) + _ = app.Run([]string{"test", "-v"}) if output.Len() > 0 { t.Errorf("unexpected output: %s", output.String()) } @@ -118,7 +118,7 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { app := &App{} set := flag.NewFlagSet("test", 0) - set.Parse([]string{"foo"}) + _ = set.Parse([]string{"foo"}) c := NewContext(app, set, nil) @@ -128,9 +128,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*exitError) + exitErr, ok := err.(*ExitError) if !ok { - t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *ExitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { @@ -146,7 +146,7 @@ func Test_helpCommand_InHelpOutput(t *testing.T) { app := &App{} output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"test", "--help"}) + _ = app.Run([]string{"test", "--help"}) s := output.String() @@ -163,7 +163,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { app := &App{} set := flag.NewFlagSet("test", 0) - set.Parse([]string{"foo"}) + _ = set.Parse([]string{"foo"}) c := NewContext(app, set, nil) @@ -173,9 +173,9 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*exitError) + exitErr, ok := err.(*ExitError) if !ok { - t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *ExitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { @@ -202,7 +202,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "--help"}) + _ = app.Run([]string{"foo", "--help"}) if !strings.Contains(output.String(), "frobbly, fr, frob") { t.Errorf("expected output to include all command aliases; got: %q", output.String()) @@ -224,7 +224,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "help", "fr"}) + _ = app.Run([]string{"foo", "help", "fr"}) if !strings.Contains(output.String(), "frobbly") { t.Errorf("expected output to include command name; got: %q", output.String()) @@ -250,7 +250,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "help"}) + _ = app.Run([]string{"foo", "help"}) if !strings.Contains(output.String(), "frobbly, fr, frob, bork") { t.Errorf("expected output to include all command aliases; got: %q", output.String()) @@ -284,7 +284,7 @@ EXAMPLES: } output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "help", "frobbly"}) + _ = app.Run([]string{"foo", "help", "frobbly"}) if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") { t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String()) @@ -312,7 +312,7 @@ func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "frobbly", "--help"}) + _ = app.Run([]string{"foo", "frobbly", "--help"}) if !strings.Contains(output.String(), "this is usage text") { t.Errorf("expected output to include usage text; got: %q", output.String()) @@ -336,7 +336,7 @@ func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"foo", "frobbly", "bobbly", "--help"}) + _ = app.Run([]string{"foo", "frobbly", "bobbly", "--help"}) if !strings.Contains(output.String(), "this is usage text") { t.Errorf("expected output to include usage text; got: %q", output.String()) @@ -364,7 +364,7 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) { output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"app", "--help"}) + _ = app.Run([]string{"app", "--help"}) if strings.Contains(output.String(), "secretfrob") { t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) @@ -422,7 +422,7 @@ VERSION: output := &bytes.Buffer{} app.Writer = output - app.Run([]string{"app", "--help"}) + _ = app.Run([]string{"app", "--help"}) if strings.Contains(output.String(), "secretfrob") { t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) diff --git a/helpers_unix_test.go b/helpers_unix_test.go deleted file mode 100644 index ae27fc5..0000000 --- a/helpers_unix_test.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build darwin dragonfly freebsd linux netbsd openbsd solaris - -package cli - -import "os" - -func clearenv() { - os.Clearenv() -} diff --git a/helpers_windows_test.go b/helpers_windows_test.go deleted file mode 100644 index 4eb84f9..0000000 --- a/helpers_windows_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package cli - -import ( - "os" - "syscall" -) - -// os.Clearenv() doesn't actually unset variables on Windows -// See: https://github.com/golang/go/issues/17902 -func clearenv() { - for _, s := range os.Environ() { - for j := 1; j < len(s); j++ { - if s[j] == '=' { - keyp, _ := syscall.UTF16PtrFromString(s[0:j]) - syscall.SetEnvironmentVariable(keyp, nil) - break - } - } - } -} diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..865accf --- /dev/null +++ b/parse.go @@ -0,0 +1,80 @@ +package cli + +import ( + "flag" + "strings" +) + +type iterativeParser interface { + newFlagSet() (*flag.FlagSet, error) + useShortOptionHandling() bool +} + +// To enable short-option handling (e.g., "-it" vs "-i -t") we have to +// iteratively catch parsing errors. This way we achieve LR parsing without +// transforming any arguments. Otherwise, there is no way we can discriminate +// combined short options from common arguments that should be left untouched. +func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) { + for { + set, err := ip.newFlagSet() + if err != nil { + return nil, err + } + + err = set.Parse(args) + if !ip.useShortOptionHandling() || err == nil { + return set, err + } + + errStr := err.Error() + trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: ") + if errStr == trimmed { + return nil, err + } + + // regenerate the initial args with the split short opts + newArgs := []string{} + for i, arg := range args { + if arg != trimmed { + newArgs = append(newArgs, arg) + continue + } + + shortOpts := splitShortOptions(set, trimmed) + if len(shortOpts) == 1 { + return nil, err + } + + // add each short option and all remaining arguments + newArgs = append(newArgs, shortOpts...) + newArgs = append(newArgs, args[i+1:]...) + args = newArgs + } + } +} + +func splitShortOptions(set *flag.FlagSet, arg string) []string { + shortFlagsExist := func(s string) bool { + for _, c := range s[1:] { + if f := set.Lookup(string(c)); f == nil { + return false + } + } + return true + } + + if !isSplittable(arg) || !shortFlagsExist(arg) { + return []string{arg} + } + + separated := make([]string, 0, len(arg)-1) + for _, flagChar := range arg[1:] { + separated = append(separated, "-"+string(flagChar)) + } + + return separated +} + +func isSplittable(flagArg string) bool { + return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2 +} diff --git a/runtests b/runtests deleted file mode 100755 index d2c5ea6..0000000 --- a/runtests +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function, unicode_literals - -import argparse -import codecs -import glob -import os -import platform -import shutil -import sys -import tempfile - -from subprocess import check_call, check_output - - -_PY3 = sys.version_info.major == 3 -_WINDOWS = platform.system().lower() == 'windows' -_PACKAGE_NAME = os.environ.get( - 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' -) -_TARGETS = {} - - -def main(sysargs=sys.argv[:]): - parser = argparse.ArgumentParser() - parser.add_argument( - 'target', nargs='?', choices=tuple(_TARGETS.keys()), default='test' - ) - args = parser.parse_args(sysargs[1:]) - - _TARGETS[args.target]() - return 0 - - -def _target(func): - _TARGETS[func.__name__.strip('_')] = func - return func - - -@_target -def _test(): - if _go_version() < 'go1.2': - _run('go test -v .') - return - - coverprofiles = [] - for subpackage in ['', 'altsrc']: - coverprofile = 'cli.coverprofile' - if subpackage != '': - coverprofile = '{}.coverprofile'.format(subpackage) - - coverprofiles.append(coverprofile) - - _run('go test -v'.split() + [ - '-coverprofile={}'.format(coverprofile), - ('{}/{}'.format(_PACKAGE_NAME, subpackage)).rstrip('/') - ]) - - combined_name = _combine_coverprofiles(coverprofiles) - _run('go tool cover -func={}'.format(combined_name)) - os.remove(combined_name) - - -@_target -def _gfmrun(): - go_version = _go_version() - if go_version < 'go1.3': - print('runtests: skip on {}'.format(go_version), file=sys.stderr) - return - _run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md']) - - -@_target -def _vet(): - _run('go vet ./...') - - -@_target -def _migrations(): - go_version = _go_version() - if go_version < 'go1.3': - print('runtests: skip on {}'.format(go_version), file=sys.stderr) - return - - migration_script = os.path.abspath( - os.environ.get('V1TOV2', './cli-v1-to-v2') - ) - v1_readme_url = os.environ.get( - 'V1README', - 'https://raw.githubusercontent.com/urfave/cli/v1/README.md' - ) - - tmpdir = tempfile.mkdtemp() - try: - os.chdir(tmpdir) - _run('curl -sSL -o README.md {}'.format(v1_readme_url).split()) - _run('gfmrun extract -o .'.split()) - - for gofile in glob.glob('*.go'): - for i in (0, 1): - _run(['python', migration_script, '-w', gofile]) - _run('go build -o tmp.out {}'.format(gofile).split()) - finally: - if os.environ.get('NOCLEAN', '') == '': - shutil.rmtree(tmpdir, ignore_errors=True) - - -@_target -def _toc(): - exe = ['bash'] if _WINDOWS else [] - _run(exe + [ - os.path.join('node_modules', '.bin', 'markdown-toc'), - '-i', 'README.md' - ]) - _run('git diff --exit-code') - - -@_target -def _gen(): - go_version = _go_version() - if go_version < 'go1.5': - print('runtests: skip on {}'.format(go_version), file=sys.stderr) - return - - _run('go generate ./...') - _run('git diff --exit-code') - - -def _run(command): - if hasattr(command, 'split'): - command = command.split() - print('runtests: {}'.format(' '.join(command)), file=sys.stderr) - sys.stderr.flush() - check_call(command) - - -def _gfmrun_count(): - with codecs.open('README.md', 'r', 'utf-8') as infile: - lines = infile.read().splitlines() - return len(list(filter(_is_go_runnable, lines))) - - -def _is_go_runnable(line): - return line.startswith('package main') - - -def _go_version(): - return check_output('go version'.split()).decode('utf-8').split()[2] - - -def _combine_coverprofiles(coverprofiles): - tmp_args = dict(suffix='.coverprofile', mode='w', delete=False) - if _PY3: - tmp_args['encoding'] = 'utf-8' - - combined = tempfile.NamedTemporaryFile(**tmp_args) - combined.write('mode: set\n') - - for coverprofile in coverprofiles: - with codecs.open(coverprofile, 'r', 'utf-8') as infile: - for line in infile.readlines(): - if not line.startswith('mode: '): - combined.write(line) - - combined.flush() - name = combined.name - combined.close() - return name - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/sort.go b/sort.go new file mode 100644 index 0000000..23d1c2f --- /dev/null +++ b/sort.go @@ -0,0 +1,29 @@ +package cli + +import "unicode" + +// lexicographicLess compares strings alphabetically considering case. +func lexicographicLess(i, j string) bool { + iRunes := []rune(i) + jRunes := []rune(j) + + lenShared := len(iRunes) + if lenShared > len(jRunes) { + lenShared = len(jRunes) + } + + for index := 0; index < lenShared; index++ { + ir := iRunes[index] + jr := jRunes[index] + + if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr { + return lir < ljr + } + + if ir != jr { + return ir < jr + } + } + + return i < j +} diff --git a/sort_test.go b/sort_test.go new file mode 100644 index 0000000..662ef9b --- /dev/null +++ b/sort_test.go @@ -0,0 +1,30 @@ +package cli + +import "testing" + +var lexicographicLessTests = []struct { + i string + j string + expected bool +}{ + {"", "a", true}, + {"a", "", false}, + {"a", "a", false}, + {"a", "A", false}, + {"A", "a", true}, + {"aa", "a", false}, + {"a", "aa", true}, + {"a", "b", true}, + {"a", "B", true}, + {"A", "b", true}, + {"A", "B", true}, +} + +func TestLexicographicLess(t *testing.T) { + for _, test := range lexicographicLessTests { + actual := lexicographicLess(test.i, test.j) + if test.expected != actual { + t.Errorf(`expected string "%s" to come before "%s"`, test.i, test.j) + } + } +} diff --git a/template.go b/template.go new file mode 100644 index 0000000..c631fb9 --- /dev/null +++ b/template.go @@ -0,0 +1,121 @@ +package cli + +// AppHelpTemplate is the text template for the Default help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var AppHelpTemplate = `NAME: + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + +USAGE: + {{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}} + +VERSION: + {{.Version}}{{end}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if len .Authors}} + +AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +GLOBAL OPTIONS: + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + +COPYRIGHT: + {{.Copyright}}{{end}} +` + +// CommandHelpTemplate is the text template for the command help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var CommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + +// SubcommandHelpTemplate is the text template for the subcommand help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var SubcommandHelpTemplate = `NAME: + {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + +var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }} + +% {{ .App.Author }} + +# NAME + +{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} + +# SYNOPSIS + +{{ .App.Name }} +{{ if .SynopsisArgs }} +` + "```" + ` +{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` +{{ end }}{{ if .App.UsageText }} +# DESCRIPTION + +{{ .App.UsageText }} +{{ end }} +**Usage**: + +` + "```" + ` +{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +` + "```" + ` +{{ if .GlobalArgs }} +# GLOBAL OPTIONS +{{ range $v := .GlobalArgs }} +{{ $v }}{{ end }} +{{ end }}{{ if .Commands }} +# COMMANDS +{{ range $v := .Commands }} +{{ $v }}{{ end }}{{ end }}` + +var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion + +function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet' + for i in (commandline -opc) + if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }} + return 1 + end + end + return 0 +end + +{{ range $v := .Completions }}{{ $v }} +{{ end }}` diff --git a/testdata/expected-doc-full.man b/testdata/expected-doc-full.man new file mode 100644 index 0000000..5190698 --- /dev/null +++ b/testdata/expected-doc-full.man @@ -0,0 +1,80 @@ +.nh +.TH greet(8) + +.SH Harrison + +.SH NAME +.PP +greet \- Some app + + +.SH SYNOPSIS +.PP +greet + +.PP +.RS + +.nf +[\-\-another\-flag|\-b] +[\-\-flag|\-\-fl|\-f]=[value] +[\-\-socket|\-s]=[value] + +.fi +.RE + + +.SH DESCRIPTION +.PP +app [first\_arg] [second\_arg] + +.PP +\fBUsage\fP: + +.PP +.RS + +.nf +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] + +.fi +.RE + + +.SH GLOBAL OPTIONS +.PP +\fB\-\-another\-flag, \-b\fP: another usage text + +.PP +\fB\-\-flag, \-\-fl, \-f\fP="": + +.PP +\fB\-\-socket, \-s\fP="": some 'usage' text (default: value) + + +.SH COMMANDS +.SH config, c +.PP +another usage test + +.PP +\fB\-\-another\-flag, \-b\fP: another usage text + +.PP +\fB\-\-flag, \-\-fl, \-f\fP="": + +.SS sub\-config, s, ss +.PP +another usage test + +.PP +\fB\-\-sub\-command\-flag, \-s\fP: some usage text + +.PP +\fB\-\-sub\-flag, \-\-sub\-fl, \-s\fP="": + +.SH info, i, in +.PP +retrieve generic information + +.SH some\-command \ No newline at end of file diff --git a/testdata/expected-doc-full.md b/testdata/expected-doc-full.md new file mode 100644 index 0000000..23d7c23 --- /dev/null +++ b/testdata/expected-doc-full.md @@ -0,0 +1,62 @@ +% greet(8) + +% Harrison + +# NAME + +greet - Some app + +# SYNOPSIS + +greet + +``` +[--another-flag|-b] +[--flag|--fl|-f]=[value] +[--socket|-s]=[value] +``` + +# DESCRIPTION + +app [first_arg] [second_arg] + +**Usage**: + +``` +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +``` + +# GLOBAL OPTIONS + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +**--socket, -s**="": some 'usage' text (default: value) + + +# COMMANDS + +## config, c + +another usage test + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-config, s, ss + +another usage test + +**--sub-command-flag, -s**: some usage text + +**--sub-flag, --sub-fl, -s**="": + +## info, i, in + +retrieve generic information + +## some-command + + diff --git a/testdata/expected-doc-no-commands.md b/testdata/expected-doc-no-commands.md new file mode 100644 index 0000000..18d8e35 --- /dev/null +++ b/testdata/expected-doc-no-commands.md @@ -0,0 +1,36 @@ +% greet(8) + +% Harrison + +# NAME + +greet - Some app + +# SYNOPSIS + +greet + +``` +[--another-flag|-b] +[--flag|--fl|-f]=[value] +[--socket|-s]=[value] +``` + +# DESCRIPTION + +app [first_arg] [second_arg] + +**Usage**: + +``` +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +``` + +# GLOBAL OPTIONS + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +**--socket, -s**="": some 'usage' text (default: value) + diff --git a/testdata/expected-doc-no-flags.md b/testdata/expected-doc-no-flags.md new file mode 100644 index 0000000..8ce60fd --- /dev/null +++ b/testdata/expected-doc-no-flags.md @@ -0,0 +1,47 @@ +% greet(8) + +% Harrison + +# NAME + +greet - Some app + +# SYNOPSIS + +greet + +# DESCRIPTION + +app [first_arg] [second_arg] + +**Usage**: + +``` +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +``` + +# COMMANDS + +## config, c + +another usage test + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-config, s, ss + +another usage test + +**--sub-command-flag, -s**: some usage text + +**--sub-flag, --sub-fl, -s**="": + +## info, i, in + +retrieve generic information + +## some-command + + diff --git a/testdata/expected-fish-full.fish b/testdata/expected-fish-full.fish new file mode 100644 index 0000000..b18d51e --- /dev/null +++ b/testdata/expected-fish-full.fish @@ -0,0 +1,28 @@ +# greet fish shell completion + +function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet' + for i in (commandline -opc) + if contains -- $i config c sub-config s ss info i in some-command + return 1 + end + end + return 0 +end + +complete -c greet -n '__fish_greet_no_subcommand' -l socket -s s -r -d 'some \'usage\' text' +complete -c greet -n '__fish_greet_no_subcommand' -f -l flag -s fl -s f -r +complete -c greet -n '__fish_greet_no_subcommand' -f -l another-flag -s b -d 'another usage text' +complete -c greet -n '__fish_greet_no_subcommand' -f -l help -s h -d 'show help' +complete -c greet -n '__fish_greet_no_subcommand' -f -l version -s v -d 'print the version' +complete -c greet -n '__fish_seen_subcommand_from config c' -f -l help -s h -d 'show help' +complete -r -c greet -n '__fish_greet_no_subcommand' -a 'config c' -d 'another usage test' +complete -c greet -n '__fish_seen_subcommand_from config c' -l flag -s fl -s f -r +complete -c greet -n '__fish_seen_subcommand_from config c' -f -l another-flag -s b -d 'another usage text' +complete -c greet -n '__fish_seen_subcommand_from sub-config s ss' -f -l help -s h -d 'show help' +complete -r -c greet -n '__fish_seen_subcommand_from config c' -a 'sub-config s ss' -d 'another usage test' +complete -c greet -n '__fish_seen_subcommand_from sub-config s ss' -f -l sub-flag -s sub-fl -s s -r +complete -c greet -n '__fish_seen_subcommand_from sub-config s ss' -f -l sub-command-flag -s s -d 'some usage text' +complete -c greet -n '__fish_seen_subcommand_from info i in' -f -l help -s h -d 'show help' +complete -r -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information' +complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help' +complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command' From 75b7f09a46fe2414aa5f2e230edfc77f401f767b Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Fri, 13 Sep 2019 07:42:23 +0530 Subject: [PATCH 131/158] All altsrc tests passing --- altsrc/flag_test.go | 2 +- altsrc/yaml_command_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index b5fcbf5..80d20ee 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -319,7 +319,7 @@ func runTest(t *testing.T, test testApplyInputSource) *cli.Context { defer os.Setenv(test.EnvVarName, "") } - test.Flag.Apply(set) + _ = test.Flag.Apply(set) if test.ContextValue != nil { f := set.Lookup(test.FlagName) f.Value = test.ContextValue diff --git a/altsrc/yaml_command_test.go b/altsrc/yaml_command_test.go index 2190c2b..8659951 100644 --- a/altsrc/yaml_command_test.go +++ b/altsrc/yaml_command_test.go @@ -7,10 +7,10 @@ package altsrc import ( "flag" + "github.com/urfave/cli/v2" "io/ioutil" "os" "testing" - "github.com/urfave/cli/v2" ) func TestCommandYamlFileTest(t *testing.T) { From 3515ca97b65fc728f60b00f06a49b9bd5430225f Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Fri, 13 Sep 2019 09:22:12 +0530 Subject: [PATCH 132/158] Make app tests pass --- app.go | 25 ++++------ app_test.go | 137 +++++++++++++++++++++++++--------------------------- 2 files changed, 77 insertions(+), 85 deletions(-) diff --git a/app.go b/app.go index 229254b..90b1254 100644 --- a/app.go +++ b/app.go @@ -246,23 +246,18 @@ 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 done, cerr := checkInitCompletion(context); done { + // if cerr != nil { + // err = cerr + // } else { + // return nil + // } + //} if err != nil { if a.OnUsageError != nil { - //<<<<<<< HEAD - err = a.OnUsageError(context, err, false) - HandleExitCoder(err) - //======= - // err := a.OnUsageError(context, err, false) - // a.handleExitCoder(context, err) - //>>>>>>> master + err := a.OnUsageError(context, err, false) + a.handleExitCoder(context, err) return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) @@ -357,7 +352,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } - newCmds := []*Command{} + var newCmds []*Command for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) diff --git a/app_test.go b/app_test.go index e4bbbcb..fdac11e 100644 --- a/app_test.go +++ b/app_test.go @@ -446,61 +446,61 @@ func TestApp_Setup_defaultsWriter(t *testing.T) { } -func TestApp_CommandWithArgBeforeFlags(t *testing.T) { - var parsedOption, firstArg string - - app := NewApp() - command := &Command{ - Name: "cmd", - Flags: []Flag{ - &StringFlag{Name: "option", Value: "", Usage: "some option"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - firstArg = c.Args().First() - return nil - }, - } - app.Commands = []*Command{command} - - _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) - - expect(t, parsedOption, "my-option") - expect(t, firstArg, "my-arg") -} - -func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) { - var parsedOption, parsedSecondOption, firstArg string - var parsedBool, parsedSecondBool bool - - app := NewApp() - command := &Command{ - Name: "cmd", - Flags: []Flag{ - &StringFlag{Name: "option", Value: "", Usage: "some option"}, - &StringFlag{Name: "secondOption", Value: "", Usage: "another option"}, - &BoolFlag{Name: "boolflag", Usage: "some bool"}, - &BoolFlag{Name: "b", Usage: "another bool"}, - }, - Action: func(c *Context) error { - parsedOption = c.String("option") - parsedSecondOption = c.String("secondOption") - parsedBool = c.Bool("boolflag") - parsedSecondBool = c.Bool("b") - firstArg = c.Args().First() - return nil - }, - } - app.Commands = []*Command{command} - - _ = app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"}) - - expect(t, parsedOption, "my-option") - expect(t, parsedSecondOption, "fancy-option") - expect(t, parsedBool, true) - expect(t, parsedSecondBool, true) - expect(t, firstArg, "my-arg") -} +//func TestApp_CommandWithArgBeforeFlags(t *testing.T) { +// var parsedOption, firstArg string +// +// app := NewApp() +// command := &Command{ +// Name: "cmd", +// Flags: []Flag{ +// &StringFlag{Name: "option", Value: "", Usage: "some option"}, +// }, +// Action: func(c *Context) error { +// parsedOption = c.String("option") +// firstArg = c.Args().First() +// return nil +// }, +// } +// app.Commands = []*Command{command} +// +// _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) +// +// expect(t, parsedOption, "my-option") +// expect(t, firstArg, "my-arg") +//} +// +//func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) { +// var parsedOption, parsedSecondOption, firstArg string +// var parsedBool, parsedSecondBool bool +// +// app := NewApp() +// command := &Command{ +// Name: "cmd", +// Flags: []Flag{ +// &StringFlag{Name: "option", Value: "", Usage: "some option"}, +// &StringFlag{Name: "secondOption", Value: "", Usage: "another option"}, +// &BoolFlag{Name: "boolflag", Usage: "some bool"}, +// &BoolFlag{Name: "b", Usage: "another bool"}, +// }, +// Action: func(c *Context) error { +// parsedOption = c.String("option") +// parsedSecondOption = c.String("secondOption") +// parsedBool = c.Bool("boolflag") +// parsedSecondBool = c.Bool("b") +// firstArg = c.Args().First() +// return nil +// }, +// } +// app.Commands = []*Command{command} +// +// _ = app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"}) +// +// expect(t, parsedOption, "my-option") +// expect(t, parsedSecondOption, "fancy-option") +// expect(t, parsedBool, true) +// expect(t, parsedSecondBool, true) +// expect(t, firstArg, "my-arg") +//} func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context @@ -568,7 +568,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { }, } - _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) + _ = app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"}) expect(t, parsedOption, "my-option") expect(t, args.Get(0), "my-arg") @@ -748,7 +748,8 @@ func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { return nil }, } - app.Commands = []*Command{command, subCommand} + command.Subcommands = []*Command{subCommand} + app.Commands = []*Command{command} err := app.Run([]string{"", "cmd", "sub", "-on", expected}) expect(t, err, nil) @@ -775,7 +776,6 @@ func TestApp_Float64Flag(t *testing.T) { } func TestApp_ParseSliceFlags(t *testing.T) { - var parsedOption, firstArg string var parsedIntSlice []int var parsedStringSlice []string @@ -790,17 +790,13 @@ func TestApp_ParseSliceFlags(t *testing.T) { Action: func(c *Context) error { parsedIntSlice = c.IntSlice("p") parsedStringSlice = c.StringSlice("ip") - parsedOption = c.String("option") - firstArg = c.Args().First() return nil }, }, }, } - var _ = parsedOption - var _ = firstArg - _ = app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) + _ = app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) IntsEquals := func(a, b []int) bool { if len(a) != len(b) { @@ -858,7 +854,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { }, } - _ = app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) + _ = app.Run([]string{"", "cmd", "-a", "2", "-str", "A"}) var expectedIntSlice = []int{2} var expectedStringSlice = []string{"A"} @@ -1367,7 +1363,7 @@ func TestApp_OrderOfOperations(t *testing.T) { resetCounts() - _ = app.Run([]string{"command", fmt.Sprintf("--%s", "--generate-bash-completion")}) + _ = app.Run([]string{"command", fmt.Sprintf("--%s", "generate-bash-completion")}) expect(t, counts.ShellComplete, 1) expect(t, counts.Total, 1) @@ -1473,7 +1469,7 @@ func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { } output := buf.String() - t.Logf("output: %q\n", buf.Bytes()) + //t.Logf("output: %q\n", buf.Bytes()) if strings.Contains(output, "No help topic for") { t.Errorf("expect a help topic, got none: \n%q", output) @@ -1992,8 +1988,9 @@ func (c *customBoolFlag) GetUsage() string { return "usage" } -func (c *customBoolFlag) Apply(set *flag.FlagSet) { +func (c *customBoolFlag) Apply(set *flag.FlagSet) error { set.String(c.Nombre, c.Nombre, "") + return nil } func TestCustomFlagsUnused(t *testing.T) { @@ -2108,8 +2105,8 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) { } } - for _, flag := range ctx.App.Flags { - for _, name := range flag.Names() { + for _, fl := range ctx.App.Flags { + for _, name := range fl.Names() { if name == BashCompletionFlag.Names()[0] { continue } @@ -2180,7 +2177,7 @@ func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) { }) if exitCodeFromOsExiter != 0 { - t.Errorf("exitCodeFromOsExiter should not change, but its value is %v", exitCodeFromOsExiter) + t.Errorf("exitCodeFromExitErrHandler should not change, but its value is %v", exitCodeFromOsExiter) } if exitCodeFromExitErrHandler != testCode { From f29d98aa2d521acfaad396c3b464f4638516e15c Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 15 Sep 2019 10:39:40 +0530 Subject: [PATCH 133/158] Make command tests pass --- command.go | 63 ++++++++++++++++------------- command_test.go | 105 ++++++++++++++---------------------------------- 2 files changed, 64 insertions(+), 104 deletions(-) diff --git a/command.go b/command.go index 181091e..90c51e4 100644 --- a/command.go +++ b/command.go @@ -3,7 +3,6 @@ package cli import ( "flag" "fmt" - "io/ioutil" "sort" "strings" ) @@ -99,29 +98,7 @@ func (c *Command) Run(ctx *Context) (err error) { c.UseShortOptionHandling = true } - //if ctx.App.EnableShellCompletion { - // c.appendFlag(GenerateCompletionFlag) - //} - - set, err := flagSet(c.Name, c.Flags) - if err != nil { - return err - } - set.SetOutput(ioutil.Discard) - - if c.SkipFlagParsing { - err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) - } else { - err = set.Parse(ctx.Args().Tail()) - } - - nerr := normalizeFlags(c.Flags, set) - if nerr != nil { - _, _ = fmt.Fprintln(ctx.App.Writer, nerr) - _, _ = fmt.Fprintln(ctx.App.Writer) - _ = ShowCommandHelp(ctx, c.Name) - return nerr - } + set, err := c.parseFlags(ctx.Args()) context := NewContext(ctx.App, set, ctx) context.Command = c @@ -132,7 +109,7 @@ func (c *Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { err = c.OnUsageError(context, err, false) - HandleExitCoder(err) + context.App.handleExitCoder(context, err) return err } _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) @@ -155,7 +132,7 @@ func (c *Command) Run(ctx *Context) (err error) { defer func() { afterErr := c.After(context) if afterErr != nil { - HandleExitCoder(err) + context.App.handleExitCoder(context, err) if err != nil { err = newMultiError(err, afterErr) } else { @@ -168,8 +145,8 @@ func (c *Command) Run(ctx *Context) (err error) { if c.Before != nil { err = c.Before(context) if err != nil { - ShowCommandHelp(context, c.Name) - HandleExitCoder(err) + _ = ShowCommandHelp(context, c.Name) + context.App.handleExitCoder(context, err) return err } } @@ -182,7 +159,7 @@ func (c *Command) Run(ctx *Context) (err error) { err = c.Action(context) if err != nil { - HandleExitCoder(err) + context.App.handleExitCoder(context, err) } return err } @@ -195,6 +172,33 @@ func (c *Command) useShortOptionHandling() bool { return c.UseShortOptionHandling } +func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { + if c.SkipFlagParsing { + set, err := c.newFlagSet() + if err != nil { + return nil, err + } + + return set, set.Parse(append([]string{"--"}, args.Tail()...)) + } + + //if !c.SkipArgReorder { + // args = reorderArgs(args) + //} + + set, err := parseIter(c, args.Tail()) + if err != nil { + return nil, err + } + + err = normalizeFlags(c.Flags, set) + if err != nil { + return nil, err + } + + return set, nil +} + // Names returns the names including short names and aliases. func (c *Command) Names() []string { return append([]string{c.Name}, c.Aliases...) @@ -240,6 +244,7 @@ func (c *Command) startApp(ctx *Context) error { app.Compiled = ctx.App.Compiled app.Writer = ctx.App.Writer app.ErrWriter = ctx.App.ErrWriter + app.ExitErrHandler = ctx.App.ExitErrHandler app.UseShortOptionHandling = ctx.App.UseShortOptionHandling app.Categories = newCommandCategories() diff --git a/command_test.go b/command_test.go index f2ccf9a..5d91031 100644 --- a/command_test.go +++ b/command_test.go @@ -13,38 +13,32 @@ func TestCommandFlagParsing(t *testing.T) { cases := []struct { testArgs []string skipFlagParsing bool - skipArgReorder bool + useShortOptionHandling bool expectedErr error - UseShortOptionHandling bool }{ // Test normal "not ignoring flags" flow - {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break"), false}, + {testArgs: []string{"test-cmd", "-break", "blah", "blah"}, skipFlagParsing: false, useShortOptionHandling: false, expectedErr: errors.New("flag provided but not defined: -break")}, - // Test no arg reorder - {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false}, - {[]string{"test-cmd", "blah", "blah", "-break", "ls", "-l"}, false, true, nil, true}, - - {[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags - {[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg - {[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg - {[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling + {testArgs: []string{"test-cmd", "blah", "blah"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing without any args that look like flags + {testArgs: []string{"test-cmd", "blah", "-break"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing with random flag arg + {testArgs: []string{"test-cmd", "blah", "-help"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing with "special" help flag arg + {testArgs: []string{"test-cmd", "blah", "-h"}, skipFlagParsing: false, useShortOptionHandling: true, expectedErr: nil}, // Test UseShortOptionHandling } for _, c := range cases { app := &App{Writer: ioutil.Discard} set := flag.NewFlagSet("test", 0) - _ = set.Parse(c.testArgs) + set.Parse(c.testArgs) context := NewContext(app, set, nil) command := Command{ - Name: "test-cmd", - Aliases: []string{"tc"}, - Usage: "this is for testing", - Description: "testing", - Action: func(_ *Context) error { return nil }, - SkipFlagParsing: c.skipFlagParsing, - UseShortOptionHandling: c.UseShortOptionHandling, + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) error { return nil }, + SkipFlagParsing: c.skipFlagParsing, } err := command.Run(context) @@ -56,23 +50,23 @@ func TestCommandFlagParsing(t *testing.T) { func TestParseAndRunShortOpts(t *testing.T) { cases := []struct { - testArgs []string + testArgs args expectedErr error - expectedArgs []string + expectedArgs *args }{ - {[]string{"foo", "test", "-a"}, nil, []string{}}, - {[]string{"foo", "test", "-c", "arg1", "arg2"}, nil, []string{"arg1", "arg2"}}, - {[]string{"foo", "test", "-f"}, nil, []string{}}, - {[]string{"foo", "test", "-ac", "--fgh"}, nil, []string{}}, - {[]string{"foo", "test", "-af"}, nil, []string{}}, - {[]string{"foo", "test", "-cf"}, nil, []string{}}, - {[]string{"foo", "test", "-acf"}, nil, []string{}}, - {[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), []string{}}, - {[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}}, + {testArgs: args{"foo", "test", "-a"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-c", "arg1", "arg2"}, expectedErr: nil, expectedArgs: &args{"arg1", "arg2"}}, + {testArgs: args{"foo", "test", "-f"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-ac", "--fgh"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-af"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-cf"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-acf"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-acf", "arg1", "-invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "-invalid"}}, } var args Args - cmd := Command{ + cmd := &Command{ Name: "test", Usage: "this is for testing", Description: "testing", @@ -90,7 +84,7 @@ func TestParseAndRunShortOpts(t *testing.T) { for _, c := range cases { app := NewApp() - app.Commands = []Command{cmd} + app.Commands = []*Command{cmd} err := app.Run(c.testArgs) @@ -292,53 +286,14 @@ func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { } } -func TestCommandFlagReordering(t *testing.T) { - cases := []struct { - testArgs []string - expectedValue string - expectedArgs []string - expectedErr error - }{ - {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, "foo", []string{"some-arg"}, nil}, - {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, "foo", []string{"some-arg"}, nil}, - {[]string{"some-exec", "some-command", "--flag=foo", "some-arg"}, "foo", []string{"some-arg"}, nil}, - } - - for _, c := range cases { - value := "" - var args Args - app := &App{ - Commands: []*Command{ - { - Name: "some-command", - Flags: []Flag{ - &StringFlag{Name: "flag"}, - }, - Action: func(c *Context) error { - fmt.Printf("%+v\n", c.String("flag")) - value = c.String("flag") - args = c.Args() - return nil - }, - }, - }, - } - - err := app.Run(c.testArgs) - expect(t, err, c.expectedErr) - expect(t, value, c.expectedValue) - expect(t, args, c.expectedArgs) - } -} - func TestCommandSkipFlagParsing(t *testing.T) { cases := []struct { - testArgs []string - expectedArgs []string + testArgs args + expectedArgs *args expectedErr error }{ - {[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, []string{"some-arg", "--flag", "foo"}, nil}, - {[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, []string{"some-arg", "--flag=foo"}, nil}, + {testArgs: args{"some-exec", "some-command", "some-arg", "--flag", "foo"}, expectedArgs: &args{"some-arg", "--flag", "foo"}, expectedErr: nil}, + {testArgs: args{"some-exec", "some-command", "some-arg", "--flag=foo"}, expectedArgs: &args{"some-arg", "--flag=foo"}, expectedErr: nil}, } for _, c := range cases { From 2024bed22a3489a19768000198ffaa7ec3cfd41e Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 15 Sep 2019 11:01:20 +0530 Subject: [PATCH 134/158] Fix test cases for context, docs, errors, fish, flags and funcs --- context.go | 73 ++++++------- context_test.go | 16 +-- docs.go | 9 +- docs_test.go | 43 ++++---- errors.go | 17 ++- fish.go | 14 ++- flag.go | 6 +- flag_int.go | 5 +- flag_int64.go | 5 +- flag_path.go | 9 +- flag_string.go | 8 +- flag_string_slice.go | 5 +- flag_test.go | 241 +++++++++++++++++++++---------------------- flag_uint.go | 5 +- flag_uint64.go | 5 +- funcs.go | 2 +- 16 files changed, 235 insertions(+), 228 deletions(-) diff --git a/context.go b/context.go index ba477f6..897ca19 100644 --- a/context.go +++ b/context.go @@ -33,6 +33,9 @@ func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { c.Context = parentCtx.Context c.shellComplete = parentCtx.shellComplete } + + c.Command = &Command{} + if c.Context == nil { ctx, cancel := context.WithCancel(context.Background()) go func() { @@ -60,12 +63,17 @@ func (c *Context) Set(name, value string) error { // IsSet determines if the flag was actually set func (c *Context) IsSet(name string) bool { if fs := lookupFlagSet(name, c); fs != nil { - isSet := false - fs.Visit(func(f *flag.Flag) { - if f.Name == name { - isSet = true + if fs := lookupFlagSet(name, c); fs != nil { + isSet := false + fs.Visit(func(f *flag.Flag) { + if f.Name == name { + isSet = true + } + }) + if isSet { + return true } - }) + } // XXX hack to support IsSet for flags with EnvVar // @@ -78,45 +86,28 @@ func (c *Context) IsSet(name string) bool { // variables is available. // // See https://github.com/urfave/cli/issues/294 for additional discussion - flags := c.Command.Flags - if c.Command.Name == "" { // cannot == Command{} since it contains slice types - if c.App != nil { - flags = c.App.Flags - } + f := lookupFlag(name, c) + if f == nil { + return false } - for _, f := range flags { - for _, name := range f.Names() { - if isSet, ok := c.setFlags[name]; isSet || !ok { - continue - } - val := reflect.ValueOf(f) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - - filePathValue := val.FieldByName("FilePath") - if filePathValue.IsValid() { - eachName(filePathValue.String(), func(filePath string) { - if _, err := os.Stat(filePath); err == nil { - c.setFlags[name] = true - return - } - }) - } - - envVarValues := val.FieldByName("EnvVars") - if envVarValues.IsValid() { - for _, envVar := range envVarValues.Interface().([]string) { - envVar = strings.TrimSpace(envVar) - if _, ok := syscall.Getenv(envVar); ok { - c.setFlags[name] = true - continue - } - } - } - } + 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 } return false diff --git a/context_test.go b/context_test.go index c15528b..1dc48e1 100644 --- a/context_test.go +++ b/context_test.go @@ -452,7 +452,7 @@ func TestCheckRequiredFlags(t *testing.T) { { testCase: "required_and_present_via_env_var", flags: []Flag{ - &StringFlag{Name: "requiredFlag", Required: true, EnvVar: "REQUIRED_FLAG"}, + &StringFlag{Name: "requiredFlag", Required: true, EnvVars: []string{"REQUIRED_FLAG"}}, }, envVarInput: [2]string{"REQUIRED_FLAG", "true"}, }, @@ -477,7 +477,7 @@ func TestCheckRequiredFlags(t *testing.T) { testCase: "required_and_optional_and_optional_present_via_env_var", flags: []Flag{ &StringFlag{Name: "requiredFlag", Required: true}, - &StringFlag{Name: "optionalFlag", EnvVar: "OPTIONAL_FLAG"}, + &StringFlag{Name: "optionalFlag", EnvVars: []string{"OPTIONAL_FLAG"}}, }, envVarInput: [2]string{"OPTIONAL_FLAG", "true"}, expectedAnError: true, @@ -519,14 +519,14 @@ func TestCheckRequiredFlags(t *testing.T) { { testCase: "required_flag_with_short_name", flags: []Flag{ - &StringSliceFlag{Name: "names, N", Required: true}, + &StringSliceFlag{Name: "names", Aliases: []string{"N"}, Required: true}, }, parseInput: []string{"-N", "asd", "-N", "qwe"}, }, { testCase: "required_flag_with_multiple_short_names", flags: []Flag{ - &StringSliceFlag{Name: "names, N, n", Required: true}, + &StringSliceFlag{Name: "names", Aliases: []string{"N", "n"}, Required: true}, }, parseInput: []string{"-n", "asd", "-n", "qwe"}, }, @@ -543,12 +543,12 @@ func TestCheckRequiredFlags(t *testing.T) { os.Clearenv() _ = os.Setenv(test.envVarInput[0], test.envVarInput[1]) } - ctx := &Context{} - context := NewContext(ctx.App, set, ctx) - context.Command.Flags = test.flags + c := &Context{} + ctx := NewContext(c.App, set, c) + ctx.Command.Flags = test.flags // logic under test - err := checkRequiredFlags(test.flags, context) + err := checkRequiredFlags(test.flags, ctx) // assertions if test.expectedAnError && err == nil { diff --git a/docs.go b/docs.go index 5b94566..517a7ec 100644 --- a/docs.go +++ b/docs.go @@ -53,10 +53,9 @@ func (a *App) writeDocTemplate(w io.Writer) error { }) } -func prepareCommands(commands []Command, level int) []string { - coms := []string{} - for i := range commands { - command := &commands[i] +func prepareCommands(commands []*Command, level int) []string { + var coms []string + for _, command := range commands { if command.Hidden { continue } @@ -110,7 +109,7 @@ func prepareFlags( continue } modifiedArg := opener - for _, s := range strings.Split(flag.GetName(), ",") { + for _, s := range flag.Names() { trimmed := strings.TrimSpace(s) if len(modifiedArg) > len(opener) { modifiedArg += sep diff --git a/docs_test.go b/docs_test.go index f18d12d..3352338 100644 --- a/docs_test.go +++ b/docs_test.go @@ -9,39 +9,44 @@ func testApp() *App { app := NewApp() app.Name = "greet" app.Flags = []Flag{ - StringFlag{ - Name: "socket, s", + &StringFlag{ + Name: "socket", + Aliases: []string{"s"}, Usage: "some 'usage' text", Value: "value", TakesFile: true, }, - StringFlag{Name: "flag, fl, f"}, - BoolFlag{ - Name: "another-flag, b", - Usage: "another usage text", + &StringFlag{Name: "flag", Aliases: []string{" fl", "f"}}, + &BoolFlag{ + Name: "another-flag", + Aliases: []string{"b"}, + Usage: "another usage text", }, } - app.Commands = []Command{{ + app.Commands = []*Command{{ Aliases: []string{"c"}, Flags: []Flag{ - StringFlag{ - Name: "flag, fl, f", + &StringFlag{ + Name: "flag", + Aliases: []string{" fl", "f"}, TakesFile: true, }, - BoolFlag{ - Name: "another-flag, b", - Usage: "another usage text", + &BoolFlag{ + Name: "another-flag", + Aliases: []string{"b"}, + Usage: "another usage text", }, }, Name: "config", Usage: "another usage test", - Subcommands: []Command{{ + Subcommands: []*Command{{ Aliases: []string{"s", "ss"}, Flags: []Flag{ - StringFlag{Name: "sub-flag, sub-fl, s"}, - BoolFlag{ - Name: "sub-command-flag, s", - Usage: "some usage text", + &StringFlag{Name: "sub-flag", Aliases: []string{"sub-fl", "s"}}, + &BoolFlag{ + Name: "sub-command-flag", + Aliases: []string{"s"}, + Usage: "some usage text", }, }, Name: "sub-config", @@ -59,9 +64,7 @@ func testApp() *App { }} app.UsageText = "app [first_arg] [second_arg]" app.Usage = "Some app" - app.Author = "Harrison" - app.Email = "harrison@lolwut.com" - app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Authors = []*Author{{Name: "Harrison", Email: "harrison@lolwut.com"}, {Name: "Oliver Allen", Email: "oliver@toyshop.com"}} return app } diff --git a/errors.go b/errors.go index 2f12338..8ec6f89 100644 --- a/errors.go +++ b/errors.go @@ -59,33 +59,30 @@ type ExitCoder interface { ExitCode() int } -type ExitError struct { +type exitError struct { exitCode int message interface{} } -// NewExitError makes a new *ExitError -func NewExitError(message interface{}, exitCode int) *ExitError { - return &ExitError{ - exitCode: exitCode, - message: message, - } +// NewExitError makes a new *exitError +func NewExitError(message interface{}, exitCode int) ExitCoder { + return Exit(message, exitCode) } // Exit wraps a message and exit code into an ExitCoder suitable for handling by // HandleExitCoder func Exit(message interface{}, exitCode int) ExitCoder { - return &ExitError{ + return &exitError{ exitCode: exitCode, message: message, } } -func (ee *ExitError) Error() string { +func (ee *exitError) Error() string { return fmt.Sprintf("%v", ee.message) } -func (ee *ExitError) ExitCode() int { +func (ee *exitError) ExitCode() int { return ee.exitCode } diff --git a/fish.go b/fish.go index cf183af..67122c9 100644 --- a/fish.go +++ b/fish.go @@ -64,11 +64,9 @@ func (a *App) writeFishCompletionTemplate(w io.Writer) error { }) } -func (a *App) prepareFishCommands(commands []Command, allCommands *[]string, previousCommands []string) []string { +func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, previousCommands []string) []string { completions := []string{} - for i := range commands { - command := &commands[i] - + for _, command := range commands { if command.Hidden { continue } @@ -131,7 +129,7 @@ func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string fishAddFileFlag(f, completion) - for idx, opt := range strings.Split(flag.GetName(), ",") { + for idx, opt := range flag.Names() { if idx == 0 { completion.WriteString(fmt.Sprintf( " -l %s", strings.TrimSpace(opt), @@ -161,15 +159,15 @@ func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string func fishAddFileFlag(flag Flag, completion *strings.Builder) { switch f := flag.(type) { - case GenericFlag: + case *GenericFlag: if f.TakesFile { return } - case StringFlag: + case *StringFlag: if f.TakesFile { return } - case StringSliceFlag: + case *StringSliceFlag: if f.TakesFile { return } diff --git a/flag.go b/flag.go index 6a32574..86c619d 100644 --- a/flag.go +++ b/flag.go @@ -49,7 +49,8 @@ var BashCompletionFlag Flag = &BoolFlag{ // VersionFlag prints the version for the application var VersionFlag Flag = &BoolFlag{ - Name: "version, v", + Name: "version", + Aliases: []string{"v"}, Usage: "print the version", } @@ -57,7 +58,8 @@ var VersionFlag Flag = &BoolFlag{ // 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, h", + Name: "help", + Aliases: []string{"h"}, Usage: "show help", } diff --git a/flag_int.go b/flag_int.go index f026650..76a7b9e 100644 --- a/flag_int.go +++ b/flag_int.go @@ -78,7 +78,10 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error { // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { - return lookupInt(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt(name, fs) + } + return 0 } // GlobalInt looks up the value of a global IntFlag, returns diff --git a/flag_int64.go b/flag_int64.go index 60ef026..be2879b 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -78,7 +78,10 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { - return lookupInt64(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt64(name, fs) + } + return 0 } // GlobalInt64 looks up the value of a global Int64Flag, returns diff --git a/flag_path.go b/flag_path.go index 0e7fd01..89cd99c 100644 --- a/flag_path.go +++ b/flag_path.go @@ -7,7 +7,6 @@ type PathFlag struct { Aliases []string Usage string EnvVars []string - EnvVar string FilePath string Required bool Hidden bool @@ -58,7 +57,7 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error { for _, name := range f.Names() { if f.Destination != nil { set.StringVar(f.Destination, name, f.Value, f.Usage) - return + continue } set.String(name, f.Value, f.Usage) } @@ -69,7 +68,11 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error { // String looks up the value of a local PathFlag, returns // "" if not found func (c *Context) Path(name string) string { - return lookupPath(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupPath(name, fs) + } + + return "" } func lookupPath(name string, set *flag.FlagSet) string { diff --git a/flag_string.go b/flag_string.go index fccc670..3e15066 100644 --- a/flag_string.go +++ b/flag_string.go @@ -8,7 +8,6 @@ type StringFlag struct { Aliases []string Usage string EnvVars []string - EnvVar string FilePath string Required bool Hidden bool @@ -59,7 +58,7 @@ func (s *StringFlag) Apply(set *flag.FlagSet) error { for _, name := range s.Names() { if s.Destination != nil { set.StringVar(s.Destination, name, s.Value, s.Usage) - return + continue } set.String(name, s.Value, s.Usage) } @@ -70,7 +69,10 @@ func (s *StringFlag) Apply(set *flag.FlagSet) error { // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { - return lookupString(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupString(name, fs) + } + return "" } // GlobalString looks up the value of a global StringFlag, returns diff --git a/flag_string_slice.go b/flag_string_slice.go index 604fab4..895328f 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -130,7 +130,10 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { - return lookupStringSlice(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupStringSlice(name, fs) + } + return nil } // GlobalStringSlice looks up the value of a global StringSliceFlag, returns diff --git a/flag_test.go b/flag_test.go index d097776..ea8d59d 100644 --- a/flag_test.go +++ b/flag_test.go @@ -24,8 +24,8 @@ var boolFlagTests = []struct { func TestBoolFlagHelpOutput(t *testing.T) { for _, test := range boolFlagTests { - flag := &BoolFlag{Name: test.name} - output := flag.String() + fl := &BoolFlag{Name: test.name} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -37,7 +37,7 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) { v := false fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--wat", "-W", "--huh"}) expect(t, err, nil) @@ -113,16 +113,16 @@ func TestFlagsFromEnv(t *testing.T) { {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } -//<<<<<<< HEAD -// for i, test := range flagTests { -// os.Clearenv() -// envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) -// os.Setenv(envVarSlice.Index(0).String(), test.input) -//======= + //<<<<<<< HEAD + // for i, test := range flagTests { + // os.Clearenv() + // envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) + // os.Setenv(envVarSlice.Index(0).String(), test.input) + //======= for i, test := range flagTests { os.Clearenv() _ = os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input) -//>>>>>>> master + //>>>>>>> master a := App{ Flags: []Flag{test.flag}, Action: func(ctx *Context) error { @@ -168,8 +168,8 @@ var stringFlagTests = []struct { func TestStringFlagHelpOutput(t *testing.T) { for _, test := range stringFlagTests { - flag := &StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} - output := flag.String() + fl := &StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -178,9 +178,9 @@ func TestStringFlagHelpOutput(t *testing.T) { } func TestStringFlagDefaultText(t *testing.T) { - flag := &StringFlag{Name: "foo", Aliases: nil, Usage: "amount of `foo` requested", Value: "none", DefaultText: "all of it"} + fl := &StringFlag{Name: "foo", Aliases: nil, Usage: "amount of `foo` requested", Value: "none", DefaultText: "all of it"} expected := "--foo foo\tamount of foo requested (default: all of it)" - output := flag.String() + output := fl.String() if output != expected { t.Errorf("%q does not match %q", output, expected) @@ -193,8 +193,8 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_FOO", "derp") for _, test := range stringFlagTests { - flag := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} - output := flag.String() + fl := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} + output := fl.String() expectedSuffix := " [$APP_FOO]" if runtime.GOOS == "windows" { @@ -213,22 +213,22 @@ var prefixStringFlagTests = []struct { prefixer FlagNamePrefixFunc expected string }{ - {"foo", "", "", func(a, b string) string { + {"foo", "", "", func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) }, "name: foo, ph: value\t"}, - {"f", "", "", func(a, b string) string { + {"f", "", "", func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) }, "name: f, ph: value\t"}, - {"f", "The total `foo` desired", "all", func(a, b string) string { + {"f", "The total `foo` desired", "all", func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) }, "name: f, ph: foo\tThe total foo desired (default: \"all\")"}, - {"test", "", "Something", func(a, b string) string { + {"test", "", "Something", func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) }, "name: test, ph: value\t(default: \"Something\")"}, - {"config,c", "Load configuration from `FILE`", "", func(a, b string) string { + {"config,c", "Load configuration from `FILE`", "", func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) }, "name: config,c, ph: FILE\tLoad configuration from FILE"}, - {"config,c", "Load configuration from `CONFIG`", "config.json", func(a, b string) string { + {"config,c", "Load configuration from `CONFIG`", "config.json", func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) }, "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, } @@ -240,8 +240,8 @@ func TestFlagNamePrefixer(t *testing.T) { for _, test := range prefixStringFlagTests { FlagNamePrefixer = test.prefixer - flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} - output := flag.String() + fl := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) } @@ -252,7 +252,7 @@ func TestStringFlagApply_SetsAllNames(t *testing.T) { v := "mmm" fl := StringFlag{Name: "hay", Aliases: []string{"H", "hayyy"}, Destination: &v} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--hay", "u", "-H", "yuu", "--hayyy", "YUUUU"}) expect(t, err, nil) @@ -272,9 +272,8 @@ var pathFlagTests = []struct { func TestPathFlagHelpOutput(t *testing.T) { for _, test := range pathFlagTests { - flag := &PathFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} - output := flag.String() - + fl := &PathFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -284,10 +283,10 @@ func TestPathFlagHelpOutput(t *testing.T) { func TestPathFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() - os.Setenv("APP_PATH", "/path/to/file") + _ = os.Setenv("APP_PATH", "/path/to/file") for _, test := range pathFlagTests { - flag := &PathFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_PATH"}} - output := flag.String() + fl := &PathFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_PATH"}} + output := fl.String() expectedSuffix := " [$APP_PATH]" if runtime.GOOS == "windows" { @@ -303,7 +302,7 @@ func TestPathFlagApply_SetsAllNames(t *testing.T) { v := "mmm" fl := PathFlag{Name: "path", Aliases: []string{"p", "PATH"}, Destination: &v} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--path", "/path/to/file/path", "-p", "/path/to/file/p", "--PATH", "/path/to/file/PATH"}) expect(t, err, nil) @@ -316,16 +315,16 @@ var envHintFlagTests = []struct { hinter FlagEnvHintFunc expected string }{ - {"foo", "", func(a, b string) string { + {"foo", "", func(a []string, b string) string { return fmt.Sprintf("env: %s, str: %s", a, b) }, "env: , str: --foo value\t"}, - {"f", "", func(a, b string) string { + {"f", "", func(a []string, b string) string { return fmt.Sprintf("env: %s, str: %s", a, b) }, "env: , str: -f value\t"}, - {"foo", "ENV_VAR", func(a, b string) string { + {"foo", "ENV_VAR", func(a []string, b string) string { return fmt.Sprintf("env: %s, str: %s", a, b) }, "env: ENV_VAR, str: --foo value\t"}, - {"f", "ENV_VAR", func(a, b string) string { + {"f", "ENV_VAR", func(a []string, b string) string { return fmt.Sprintf("env: %s, str: %s", a, b) }, "env: ENV_VAR, str: -f value\t"}, } @@ -337,8 +336,8 @@ func TestFlagEnvHinter(t *testing.T) { for _, test := range envHintFlagTests { FlagEnvHinter = test.hinter - flag := StringFlag{Name: test.name, EnvVar: test.env} - output := flag.String() + fl := StringFlag{Name: test.name, EnvVars: []string{test.env}} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) } @@ -357,13 +356,12 @@ var stringSliceFlagTests = []struct { {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, - } func TestStringSliceFlagHelpOutput(t *testing.T) { for _, test := range stringSliceFlagTests { - flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} - output := flag.String() + f := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := f.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -376,8 +374,8 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_QWWX", "11,4") for _, test := range stringSliceFlagTests { - flag := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} - output := flag.String() + fl := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} + output := fl.String() expectedSuffix := " [$APP_QWWX]" if runtime.GOOS == "windows" { @@ -392,7 +390,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { func TestStringSliceFlagApply_SetsAllNames(t *testing.T) { fl := StringSliceFlag{Name: "goat", Aliases: []string{"G", "gooots"}} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--goat", "aaa", "-G", "bbb", "--gooots", "eeeee"}) expect(t, err, nil) @@ -408,8 +406,8 @@ var intFlagTests = []struct { func TestIntFlagHelpOutput(t *testing.T) { for _, test := range intFlagTests { - flag := &IntFlag{Name: test.name, Value: 9} - output := flag.String() + fl := &IntFlag{Name: test.name, Value: 9} + output := fl.String() if output != test.expected { t.Errorf("%s does not match %s", output, test.expected) @@ -419,12 +417,11 @@ func TestIntFlagHelpOutput(t *testing.T) { func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() - os.Setenv("APP_BAR", "2") _ = os.Setenv("APP_BAR", "2") for _, test := range intFlagTests { - flag := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} - output := flag.String() + fl := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -440,7 +437,7 @@ func TestIntFlagApply_SetsAllNames(t *testing.T) { v := 3 fl := IntFlag{Name: "banana", Aliases: []string{"B", "banannanana"}, Destination: &v} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--banana", "1", "-B", "2", "--banannanana", "5"}) expect(t, err, nil) @@ -457,8 +454,8 @@ var int64FlagTests = []struct { func TestInt64FlagHelpOutput(t *testing.T) { for _, test := range int64FlagTests { - flag := Int64Flag{Name: test.name, Value: 8589934592} - output := flag.String() + fl := Int64Flag{Name: test.name, Value: 8589934592} + output := fl.String() if output != test.expected { t.Errorf("%s does not match %s", output, test.expected) @@ -471,8 +468,8 @@ func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_BAR", "2") for _, test := range int64FlagTests { - flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} - output := flag.String() + fl := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -494,8 +491,8 @@ var uintFlagTests = []struct { func TestUintFlagHelpOutput(t *testing.T) { for _, test := range uintFlagTests { - flag := UintFlag{Name: test.name, Value: 41} - output := flag.String() + fl := UintFlag{Name: test.name, Value: 41} + output := fl.String() if output != test.expected { t.Errorf("%s does not match %s", output, test.expected) @@ -508,8 +505,8 @@ func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_BAR", "2") for _, test := range uintFlagTests { - flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} - output := flag.String() + fl := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -531,8 +528,8 @@ var uint64FlagTests = []struct { func TestUint64FlagHelpOutput(t *testing.T) { for _, test := range uint64FlagTests { - flag := Uint64Flag{Name: test.name, Value: 8589934582} - output := flag.String() + fl := Uint64Flag{Name: test.name, Value: 8589934582} + output := fl.String() if output != test.expected { t.Errorf("%s does not match %s", output, test.expected) @@ -545,8 +542,8 @@ func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_BAR", "2") for _, test := range uint64FlagTests { - flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} - output := flag.String() + fl := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -568,8 +565,8 @@ var durationFlagTests = []struct { func TestDurationFlagHelpOutput(t *testing.T) { for _, test := range durationFlagTests { - flag := &DurationFlag{Name: test.name, Value: 1 * time.Second} - output := flag.String() + fl := &DurationFlag{Name: test.name, Value: 1 * time.Second} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -582,8 +579,8 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_BAR", "2h3m6s") for _, test := range durationFlagTests { - flag := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} - output := flag.String() + fl := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := fl.String() expectedSuffix := " [$APP_BAR]" if runtime.GOOS == "windows" { @@ -599,7 +596,7 @@ func TestDurationFlagApply_SetsAllNames(t *testing.T) { v := time.Second * 20 fl := DurationFlag{Name: "howmuch", Aliases: []string{"H", "whyyy"}, Destination: &v} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--howmuch", "30s", "-H", "5m", "--whyyy", "30h"}) expect(t, err, nil) @@ -619,8 +616,8 @@ var intSliceFlagTests = []struct { func TestIntSliceFlagHelpOutput(t *testing.T) { for _, test := range intSliceFlagTests { - flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} - output := flag.String() + fl := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -633,8 +630,8 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_SMURF", "42,3") for _, test := range intSliceFlagTests { - flag := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} - output := flag.String() + fl := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() expectedSuffix := " [$APP_SMURF]" if runtime.GOOS == "windows" { @@ -649,7 +646,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { fl := IntSliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) expect(t, err, nil) @@ -669,8 +666,8 @@ var int64SliceFlagTests = []struct { func TestInt64SliceFlagHelpOutput(t *testing.T) { for _, test := range int64SliceFlagTests { - flag := Int64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} - output := flag.String() + fl := Int64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -683,8 +680,8 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_SMURF", "42,17179869184") for _, test := range int64SliceFlagTests { - flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} - output := flag.String() + fl := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() expectedSuffix := " [$APP_SMURF]" if runtime.GOOS == "windows" { @@ -720,8 +717,8 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_BAZ", "99.4") for _, test := range float64FlagTests { - flag := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} - output := flag.String() + fl := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} + output := fl.String() expectedSuffix := " [$APP_BAZ]" if runtime.GOOS == "windows" { @@ -737,7 +734,7 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) { v := 99.1 fl := Float64Flag{Name: "noodles", Aliases: []string{"N", "nurbles"}, Destination: &v} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--noodles", "1.3", "-N", "11", "--nurbles", "43.33333"}) expect(t, err, nil) @@ -752,14 +749,14 @@ var float64SliceFlagTests = []struct { }{ {"heads", nil, NewFloat64Slice(), "--heads value\t"}, {"H", nil, NewFloat64Slice(), "-H value\t"}, - {"heads", []string{"H"}, NewFloat64Slice(float64(0.1234), float64(-10.5)), + {"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5), "--heads value, -H value\t(default: 0.1234, -10.5)"}, } func TestFloat64SliceFlagHelpOutput(t *testing.T) { for _, test := range float64SliceFlagTests { - flag := Float64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} - output := flag.String() + fl := Float64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -769,10 +766,10 @@ func TestFloat64SliceFlagHelpOutput(t *testing.T) { func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { os.Clearenv() - os.Setenv("APP_SMURF", "0.1234,-10.5") + _ = os.Setenv("APP_SMURF", "0.1234,-10.5") for _, test := range float64SliceFlagTests { - flag := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} - output := flag.String() + fl := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() expectedSuffix := " [$APP_SMURF]" if runtime.GOOS == "windows" { @@ -795,8 +792,8 @@ var genericFlagTests = []struct { func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { - flag := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} - output := flag.String() + fl := &GenericFlag{Name: test.name, Value: test.value, Usage: "test fl"} + output := fl.String() if output != test.expected { t.Errorf("%q does not match %q", output, test.expected) @@ -809,8 +806,8 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { _ = os.Setenv("APP_ZAP", "3") for _, test := range genericFlagTests { - flag := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} - output := flag.String() + fl := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} + output := fl.String() expectedSuffix := " [$APP_ZAP]" if runtime.GOOS == "windows" { @@ -825,7 +822,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { func TestGenericFlagApply_SetsAllNames(t *testing.T) { fl := GenericFlag{Name: "orbs", Aliases: []string{"O", "obrs"}, Value: &Parser{}} set := flag.NewFlagSet("test", 0) - fl.Apply(set) + _ = fl.Apply(set) err := set.Parse([]string{"--orbs", "eleventy,3", "-O", "4,bloop", "--obrs", "19,s"}) expect(t, err, nil) @@ -1424,8 +1421,8 @@ func TestParseBoolShortOptionHandle(t *testing.T) { return nil }, Flags: []Flag{ - BoolFlag{Name: "serve, s"}, - BoolFlag{Name: "option, o"}, + &BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &BoolFlag{Name: "option", Aliases: []string{"o"}}, }, }, }, @@ -1471,7 +1468,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) { func TestParseMultiBoolFromEnvCascade(t *testing.T) { os.Clearenv() - os.Setenv("APP_DEBUG", "1") + _ = os.Setenv("APP_DEBUG", "1") _ = (&App{ Flags: []Flag{ &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}}, @@ -1504,7 +1501,7 @@ func TestParseBoolTFromEnv(t *testing.T) { _ = os.Setenv("DEBUG", test.input) _ = (&App{ Flags: []Flag{ - BoolTFlag{Name: "debug, d", EnvVar: "DEBUG"}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, Value: true, EnvVars: []string{"DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != test.output { @@ -1521,19 +1518,19 @@ func TestParseBoolTFromEnv(t *testing.T) { func TestParseMultiBoolT(t *testing.T) { _ = (&App{ - Flags: []Flag{ - &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, - }, - Action: func(ctx *Context) error { - if ctx.Bool("implode") { - t.Errorf("main name not set") - } - if ctx.Bool("i") { - t.Errorf("short name not set") - } - return nil - }, - }).Run([]string{"run", "--implode=false"}) + Flags: []Flag{ + &BoolFlag{Name: "implode", Aliases: []string{"i"}, Value: true}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("implode") { + t.Errorf("main name not set") + } + if ctx.Bool("i") { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "--implode=false"}) } func TestParseMultiBoolTFromEnv(t *testing.T) { @@ -1541,13 +1538,13 @@ func TestParseMultiBoolTFromEnv(t *testing.T) { _ = os.Setenv("APP_DEBUG", "0") _ = (&App{ Flags: []Flag{ - &BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, Value: true, EnvVars: []string{"DEBUG"}}, }, Action: func(ctx *Context) error { - if ctx.BoolT("debug") != false { + if ctx.Bool("debug") != false { t.Errorf("main name not set from env") } - if ctx.BoolT("d") != false { + if ctx.Bool("d") != false { t.Errorf("short name not set from env") } return nil @@ -1560,13 +1557,13 @@ func TestParseMultiBoolTFromEnvCascade(t *testing.T) { _ = os.Setenv("APP_DEBUG", "0") _ = (&App{ Flags: []Flag{ - &BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, Value: true, EnvVars: []string{"DEBUG"}}, }, Action: func(ctx *Context) error { - if ctx.BoolT("debug") != false { + if ctx.Bool("debug") != false { t.Errorf("main name not set from env") } - if ctx.BoolT("d") != false { + if ctx.Bool("d") != false { t.Errorf("short name not set from env") } return nil @@ -1675,14 +1672,14 @@ func TestFlagFromFile(t *testing.T) { var filePathTests = []struct { path string - name string + name []string expected string }{ - {"file-does-not-exist", "APP_BAR", ""}, - {"file-does-not-exist", "APP_FOO", "123"}, - {"file-does-not-exist", "APP_FOO,APP_BAR", "123"}, - {temp.Name(), "APP_FOO", "123"}, - {temp.Name(), "APP_BAR", "abc"}, + {"file-does-not-exist", []string{"APP_BAR"}, ""}, + {"file-does-not-exist", []string{"APP_FOO"}, "123"}, + {"file-does-not-exist", []string{"APP_FOO", "APP_BAR"}, "123"}, + {temp.Name(), []string{"APP_FOO"}, "123"}, + {temp.Name(), []string{"APP_BAR"}, "abc"}, } for _, filePathTest := range filePathTests { @@ -1702,7 +1699,7 @@ func TestStringSlice_Serialized_Set(t *testing.T) { } sl1 := NewStringSlice("c", "d") - sl1.Set(ser0) + _ = sl1.Set(ser0) if sl0.String() != sl1.String() { t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) @@ -1718,7 +1715,7 @@ func TestIntSlice_Serialized_Set(t *testing.T) { } sl1 := NewIntSlice(3, 4) - sl1.Set(ser0) + _ = sl1.Set(ser0) if sl0.String() != sl1.String() { t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) @@ -1734,7 +1731,7 @@ func TestInt64Slice_Serialized_Set(t *testing.T) { } sl1 := NewInt64Slice(int64(3), int64(4)) - sl1.Set(ser0) + _ = sl1.Set(ser0) if sl0.String() != sl1.String() { t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) diff --git a/flag_uint.go b/flag_uint.go index 7a3e943..6f7fd8c 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -79,7 +79,10 @@ func (f *UintFlag) GetValue() string { // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { - return lookupUint(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupUint(name, fs) + } + return 0 } // GlobalUint looks up the value of a global UintFlag, returns diff --git a/flag_uint64.go b/flag_uint64.go index 65534c7..bcf8c76 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -79,7 +79,10 @@ func (f *Uint64Flag) GetValue() string { // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { - return lookupUint64(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupUint64(name, fs) + } + return 0 } // GlobalUint64 looks up the value of a global Uint64Flag, returns diff --git a/funcs.go b/funcs.go index 9ffe246..474c48f 100644 --- a/funcs.go +++ b/funcs.go @@ -23,7 +23,7 @@ type CommandNotFoundFunc func(*Context, string) // is displayed and the execution is interrupted. type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error -// ExitErrHandlerFunc is executed if provided in order to handle ExitError values +// ExitErrHandlerFunc is executed if provided in order to handle exitError values // returned by Actions and Before/After functions. type ExitErrHandlerFunc func(context *Context, err error) From d870ad6ccbeed6ffcdb57c1950959ab3f47d9262 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 15 Sep 2019 14:52:46 +0530 Subject: [PATCH 135/158] Fix all tests --- README.md | 12 ---- app_test.go | 2 +- build.go | 14 ++--- command.go | 2 + docs.go | 2 +- docs_test.go | 6 +- flag.go | 2 +- flag_bool.go | 2 +- flag_duration.go | 2 +- flag_float64.go | 2 +- flag_float64_slice.go | 2 +- flag_generic.go | 2 +- flag_int.go | 2 +- flag_int64.go | 2 +- flag_int64_slice.go | 2 +- flag_int_slice.go | 2 +- flag_string_slice.go | 2 +- flag_test.go | 142 +++++++++++++++--------------------------- flag_uint.go | 2 +- flag_uint64.go | 2 +- help.go | 138 ++++------------------------------------ help_test.go | 8 +-- helpers_test.go | 2 +- template.go | 14 ++--- 24 files changed, 100 insertions(+), 268 deletions(-) diff --git a/README.md b/README.md index 7624c48..08303e0 100644 --- a/README.md +++ b/README.md @@ -30,20 +30,12 @@ applications in an expressive way. + [Values from the Environment](#values-from-the-environment) + [Values from files](#values-from-files) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) -<<<<<<< HEAD - + [Default Values for help output](#default-values-for-help-output) - * [Subcommands](#subcommands) - * [Subcommands categories](#subcommands-categories) - * [Exit code](#exit-code) - * [Shell Completion](#shell-completion) -======= + [Precedence](#precedence) * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) * [Exit code](#exit-code) * [Combining short options](#combining-short-options) * [Bash Completion](#bash-completion) ->>>>>>> master + [Enabling](#enabling) + [Distribution](#distribution) + [Customization](#customization) @@ -111,7 +103,6 @@ import ( ... ``` -<<<<<<< HEAD **NOTE**: There is a [migrator (python) script](./cli-v1-to-v2) available to aid with the transition from the v1 to v2 API. @@ -120,9 +111,6 @@ with the transition from the v1 to v2 API. Similarly to the section above describing use of the `v2` branch, if one wants to avoid any unexpected compatibility pains once `v2` becomes `master`, then pinning to `v1` is an acceptable option, e.g.: -======= -### Using `v1` releases ->>>>>>> master ``` $ go get github.com/urfave/cli diff --git a/app_test.go b/app_test.go index fdac11e..07d2bd2 100644 --- a/app_test.go +++ b/app_test.go @@ -1736,7 +1736,7 @@ func TestApp_Run_Categories(t *testing.T) { } output := buf.String() - t.Logf("output: %q\n", buf.Bytes()) + //t.Logf("output: %q\n", buf.Bytes()) if !strings.Contains(output, "1:\n command1") { t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) diff --git a/build.go b/build.go index a828ae4..92188fd 100644 --- a/build.go +++ b/build.go @@ -12,7 +12,7 @@ import ( "os/exec" "strings" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) var packages = []string{"cli", "altsrc"} @@ -24,19 +24,19 @@ func main() { app.Usage = "Generates a new urfave/cli build!" app.Commands = cli.Commands{ - cli.Command{ + { Name: "vet", Action: VetActionFunc, }, - cli.Command{ + { Name: "test", Action: TestActionFunc, }, - cli.Command{ + { Name: "gfmrun", Action: GfmrunActionFunc, }, - cli.Command{ + { Name: "toc", Action: TocActionFunc, }, @@ -67,9 +67,9 @@ func TestActionFunc(c *cli.Context) error { var packageName string if pkg == "cli" { - packageName = "github.com/urfave/cli" + packageName = "github.com/urfave/cli/v2" } else { - packageName = fmt.Sprintf("github.com/urfave/cli/%s", pkg) + packageName = fmt.Sprintf("github.com/urfave/cli/v2/%s", pkg) } coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg) diff --git a/command.go b/command.go index 90c51e4..af86824 100644 --- a/command.go +++ b/command.go @@ -60,6 +60,8 @@ type Command struct { CustomHelpTemplate string } +type Commands []*Command + type CommandsByName []*Command func (c CommandsByName) Len() int { diff --git a/docs.go b/docs.go index 517a7ec..2e8b974 100644 --- a/docs.go +++ b/docs.go @@ -102,7 +102,7 @@ func prepareFlags( sep, opener, closer, value string, addDetails bool, ) []string { - args := []string{} + var args []string for _, f := range flags { flag, ok := f.(DocGenerationFlag) if !ok { diff --git a/docs_test.go b/docs_test.go index 3352338..e685f90 100644 --- a/docs_test.go +++ b/docs_test.go @@ -16,7 +16,7 @@ func testApp() *App { Value: "value", TakesFile: true, }, - &StringFlag{Name: "flag", Aliases: []string{" fl", "f"}}, + &StringFlag{Name: "flag", Aliases: []string{"fl", "f"}}, &BoolFlag{ Name: "another-flag", Aliases: []string{"b"}, @@ -28,7 +28,7 @@ func testApp() *App { Flags: []Flag{ &StringFlag{ Name: "flag", - Aliases: []string{" fl", "f"}, + Aliases: []string{"fl", "f"}, TakesFile: true, }, &BoolFlag{ @@ -64,7 +64,7 @@ func testApp() *App { }} app.UsageText = "app [first_arg] [second_arg]" app.Usage = "Some app" - app.Authors = []*Author{{Name: "Harrison", Email: "harrison@lolwut.com"}, {Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Authors = []*Author{{Name: "Harrison", Email: "harrison@lolwut.com"}} return app } diff --git a/flag.go b/flag.go index 86c619d..bc7cc27 100644 --- a/flag.go +++ b/flag.go @@ -235,7 +235,7 @@ func withEnvHint(envVars []string, str string) string { } func flagNames(f Flag) []string { - ret := []string{} + var ret []string name := flagStringField(f, "Name") aliases := flagStringSliceField(f, "Aliases") diff --git a/flag_bool.go b/flag_bool.go index 665a8ac..0efa71e 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -58,7 +58,7 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { if val != "" { valBool, err := strconv.ParseBool(val) if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as bool value for flag %s: %s", val, f.Name, err) } f.Value = valBool } diff --git a/flag_duration.go b/flag_duration.go index e3ccd18..0bf2b0e 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -58,7 +58,7 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error { if val != ""{ valDuration, err := time.ParseDuration(val) if err != nil { - return fmt.Errorf("could not parse %s as duration value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as duration value for flag %s: %s", val, f.Name, err) } f.Value = valDuration } diff --git a/flag_float64.go b/flag_float64.go index 641cb0b..03e8faa 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -58,7 +58,7 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val != "" { valFloat, err := strconv.ParseFloat(val, 10) if err != nil { - return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err) } f.Value = valFloat } diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 8331c43..159bdea 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -118,7 +118,7 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %s as float64 slice value for flag %s: %s", f.Value, f.Name, err) + return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) } } } diff --git a/flag_generic.go b/flag_generic.go index 169c364..4ab804b 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -65,7 +65,7 @@ func (f *GenericFlag) GetValue() string { 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 %s as value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err) } } diff --git a/flag_int.go b/flag_int.go index 76a7b9e..8bffad5 100644 --- a/flag_int.go +++ b/flag_int.go @@ -58,7 +58,7 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) } f.Value = int(valInt) } diff --git a/flag_int64.go b/flag_int64.go index be2879b..eb67b42 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -58,7 +58,7 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) } f.Value = valInt } diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 2c82329..b3f53d4 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -118,7 +118,7 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", f.Value, f.Name, err) + return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) } } } diff --git a/flag_int_slice.go b/flag_int_slice.go index c885dd3..1b63c8a 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -129,7 +129,7 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) } } } diff --git a/flag_string_slice.go b/flag_string_slice.go index 895328f..2dea966 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -112,7 +112,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { 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 %s as string value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) } } } diff --git a/flag_test.go b/flag_test.go index ea8d59d..cfc5ca8 100644 --- a/flag_test.go +++ b/flag_test.go @@ -75,7 +75,7 @@ func TestFlagsFromEnv(t *testing.T) { {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value for flag debug: .*`}, {"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""}, - {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration for flag time: .*`}, + {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration value for flag time: .*`}, {"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, {"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, @@ -113,16 +113,11 @@ func TestFlagsFromEnv(t *testing.T) { {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } - //<<<<<<< HEAD - // for i, test := range flagTests { - // os.Clearenv() - // envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) - // os.Setenv(envVarSlice.Index(0).String(), test.input) - //======= for i, test := range flagTests { os.Clearenv() - _ = os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input) - //>>>>>>> master + envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) + _ = os.Setenv(envVarSlice.Index(0).String(), test.input) + a := App{ Flags: []Flag{test.flag}, Action: func(ctx *Context) error { @@ -208,45 +203,46 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { var prefixStringFlagTests = []struct { name string + aliases []string usage string value string prefixer FlagNamePrefixFunc expected string }{ - {"foo", "", "", func(a []string, b string) string { + {name: "foo", usage: "", value: "", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: foo, ph: value\t"}, - {"f", "", "", func(a []string, b string) string { + }, expected: "name: foo, ph: value\t"}, + {name: "f", usage: "", value: "", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: f, ph: value\t"}, - {"f", "The total `foo` desired", "all", func(a []string, b string) string { + }, expected: "name: f, ph: value\t"}, + {name: "f", usage: "The total `foo` desired", value: "all", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: f, ph: foo\tThe total foo desired (default: \"all\")"}, - {"test", "", "Something", func(a []string, b string) string { + }, expected: "name: f, ph: foo\tThe total foo desired (default: \"all\")"}, + {name: "test", usage: "", value: "Something", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: test, ph: value\t(default: \"Something\")"}, - {"config,c", "Load configuration from `FILE`", "", func(a []string, b string) string { + }, expected: "name: test, ph: value\t(default: \"Something\")"}, + {name: "config", aliases: []string{"c"}, usage: "Load configuration from `FILE`", value: "", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: config,c, ph: FILE\tLoad configuration from FILE"}, - {"config,c", "Load configuration from `CONFIG`", "config.json", func(a []string, b string) string { + }, expected: "name: config,c, ph: FILE\tLoad configuration from FILE"}, + {name: "config", aliases: []string{"c"}, usage: "Load configuration from `CONFIG`", value: "config.json", prefixer: func(a []string, b string) string { return fmt.Sprintf("name: %s, ph: %s", a, b) - }, "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, + }, expected: "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, } -func TestFlagNamePrefixer(t *testing.T) { - defer func() { - FlagNamePrefixer = prefixedNames - }() - - for _, test := range prefixStringFlagTests { - FlagNamePrefixer = test.prefixer - fl := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} - output := fl.String() - if output != test.expected { - t.Errorf("%q does not match %q", output, test.expected) - } - } -} +//func TestFlagNamePrefixer(t *testing.T) { +// defer func() { +// FlagNamePrefixer = prefixedNames +// }() +// +// for _, test := range prefixStringFlagTests { +// FlagNamePrefixer = test.prefixer +// fl := StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} +// output := fl.String() +// if output != test.expected { +// t.Errorf("%q does not match %q", output, test.expected) +// } +// } +//} func TestStringFlagApply_SetsAllNames(t *testing.T) { v := "mmm" @@ -329,20 +325,20 @@ var envHintFlagTests = []struct { }, "env: ENV_VAR, str: -f value\t"}, } -func TestFlagEnvHinter(t *testing.T) { - defer func() { - FlagEnvHinter = withEnvHint - }() - - for _, test := range envHintFlagTests { - FlagEnvHinter = test.hinter - fl := StringFlag{Name: test.name, EnvVars: []string{test.env}} - output := fl.String() - if output != test.expected { - t.Errorf("%q does not match %q", output, test.expected) - } - } -} +//func TestFlagEnvHinter(t *testing.T) { +// defer func() { +// FlagEnvHinter = withEnvHint +// }() +// +// for _, test := range envHintFlagTests { +// FlagEnvHinter = test.hinter +// fl := StringFlag{Name: test.name, EnvVars: []string{test.env}} +// output := fl.String() +// if output != test.expected { +// t.Errorf("%q does not match %q", output, test.expected) +// } +// } +//} var stringSliceFlagTests = []struct { name string @@ -792,7 +788,7 @@ var genericFlagTests = []struct { func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { - fl := &GenericFlag{Name: test.name, Value: test.value, Usage: "test fl"} + fl := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} output := fl.String() if output != test.expected { @@ -1485,8 +1481,8 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) { }).Run([]string{"run"}) } -func TestParseBoolTFromEnv(t *testing.T) { - var boolTFlagTests = []struct { +func TestParseBoolFromEnv(t *testing.T) { + var boolFlagTests = []struct { input string output bool }{ @@ -1496,12 +1492,12 @@ func TestParseBoolTFromEnv(t *testing.T) { {"true", true}, } - for _, test := range boolTFlagTests { + for _, test := range boolFlagTests { os.Clearenv() _ = os.Setenv("DEBUG", test.input) _ = (&App{ Flags: []Flag{ - &BoolFlag{Name: "debug", Aliases: []string{"d"}, Value: true, EnvVars: []string{"DEBUG"}}, + &BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"DEBUG"}}, }, Action: func(ctx *Context) error { if ctx.Bool("debug") != test.output { @@ -1533,44 +1529,6 @@ func TestParseMultiBoolT(t *testing.T) { }).Run([]string{"run", "--implode=false"}) } -func TestParseMultiBoolTFromEnv(t *testing.T) { - os.Clearenv() - _ = os.Setenv("APP_DEBUG", "0") - _ = (&App{ - Flags: []Flag{ - &BoolFlag{Name: "debug", Aliases: []string{"d"}, Value: true, EnvVars: []string{"DEBUG"}}, - }, - Action: func(ctx *Context) error { - if ctx.Bool("debug") != false { - t.Errorf("main name not set from env") - } - if ctx.Bool("d") != false { - t.Errorf("short name not set from env") - } - return nil - }, - }).Run([]string{"run"}) -} - -func TestParseMultiBoolTFromEnvCascade(t *testing.T) { - os.Clearenv() - _ = os.Setenv("APP_DEBUG", "0") - _ = (&App{ - Flags: []Flag{ - &BoolFlag{Name: "debug", Aliases: []string{"d"}, Value: true, EnvVars: []string{"DEBUG"}}, - }, - Action: func(ctx *Context) error { - if ctx.Bool("debug") != false { - t.Errorf("main name not set from env") - } - if ctx.Bool("d") != false { - t.Errorf("short name not set from env") - } - return nil - }, - }).Run([]string{"run"}) -} - type Parser [2]string func (p *Parser) Set(value string) error { diff --git a/flag_uint.go b/flag_uint.go index 6f7fd8c..d1eb7f3 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -52,7 +52,7 @@ func (f *UintFlag) Apply(set *flag.FlagSet) error { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as uint value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as uint value for flag %s: %s", val, f.Name, err) } f.Value = uint(valInt) diff --git a/flag_uint64.go b/flag_uint64.go index bcf8c76..441c0fa 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -52,7 +52,7 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) error { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", val, f.Name, err) } f.Value = valInt diff --git a/help.go b/help.go index 155654f..3323817 100644 --- a/help.go +++ b/help.go @@ -10,75 +10,6 @@ import ( "unicode/utf8" ) -// AppHelpTemplate is the text template for the Default help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} - -USAGE: - {{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}} - -VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} - -DESCRIPTION: - {{.Description}}{{end}}{{if len .Authors}} - -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} - -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} - -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} - -COPYRIGHT: - {{.Copyright}}{{end}} -` - -// CommandHelpTemplate is the text template for the command help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var CommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} - -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} - -CATEGORY: - {{.Category}}{{end}}{{if .Description}} - -DESCRIPTION: - {{.Description}}{{end}}{{if .VisibleFlags}} - -OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} -` - -// SubcommandHelpTemplate is the text template for the subcommand help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} - -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} - -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} -{{end}}{{if .VisibleFlags}} -OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} -` - var helpCommand = &Command{ Name: "help", Aliases: []string{"h"}, @@ -157,7 +88,7 @@ func DefaultAppComplete(c *Context) { DefaultCompleteWithFlags(nil)(c) } -func printCommandSuggestions(commands []Command, writer io.Writer) { +func printCommandSuggestions(commands []*Command, writer io.Writer) { for _, command := range commands { if command.Hidden { continue @@ -195,10 +126,10 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { cur := strings.TrimPrefix(lastArg, "-") cur = strings.TrimPrefix(cur, "-") for _, flag := range flags { - if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden { + if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden { continue } - for _, name := range strings.Split(flag.GetName(), ",") { + for _, name := range flag.Names(){ name = strings.TrimSpace(name) // this will get total count utf8 letters in flag name count := utf8.RuneCountInString(name) @@ -211,7 +142,7 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { continue } // match if last argument matches this flag and it is not repeated - if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) { + if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) { flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) _, _ = fmt.Fprintln(writer, flagCompletion) } @@ -305,20 +236,13 @@ func ShowCompletions(c *Context) { // ShowCommandCompletions prints the custom completions for a given command func ShowCommandCompletions(ctx *Context, command string) { c := ctx.App.Command(command) - //TODO: Resolve -//<<<<<<< HEAD - if c != nil && c.ShellComplete != nil { - c.ShellComplete(ctx) -//======= -// if c != nil { -// if c.BashComplete != nil { -// c.BashComplete(ctx) -// } else { -// DefaultCompleteWithFlags(c)(ctx) -// } -//>>>>>>> master + if c != nil { + if c.BashComplete != nil { + c.BashComplete(ctx) + } else { + DefaultCompleteWithFlags(c)(ctx) + } } - } func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) { @@ -387,14 +311,14 @@ func checkSubcommandHelp(c *Context) bool { } func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { - if !a.EnableShellCompletion { + if !a.EnableBashCompletion { return false, arguments } pos := len(arguments) - 1 lastArg := arguments[pos] - if lastArg != "--"+genCompName() { + if lastArg != "--generate-bash-completion" { return false, arguments } @@ -426,41 +350,3 @@ func checkCommandCompletions(c *Context, name string) bool { ShowCommandCompletions(c, name) return true } - -func checkInitCompletion(c *Context) (bool, error) { - if c.IsSet(InitCompletionFlag.Name) { - shell := c.String(InitCompletionFlag.Name) - progName := os.Args[0] - switch shell { - case "bash": - fmt.Print(bashCompletionCode(progName)) - return true, nil - case "zsh": - fmt.Print(zshCompletionCode(progName)) - return true, nil - default: - return false, fmt.Errorf("--init-completion value cannot be '%s'", shell) - } - } - return false, nil -} - -func bashCompletionCode(progName string) string { - var template = `_cli_bash_autocomplete() { - local cur opts base; - COMPREPLY=(); - cur="${COMP_WORDS[COMP_CWORD]}"; - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --%s ); - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ); - return 0; -}; -complete -F _cli_bash_autocomplete %s` - return fmt.Sprintf(template, genCompName(), progName) -} - -func zshCompletionCode(progName string) string { - var template = `autoload -U compinit && compinit; -autoload -U bashcompinit && bashcompinit;` - - return template + "\n" + bashCompletionCode(progName) -} diff --git a/help_test.go b/help_test.go index 6d3a6a9..3eeddba 100644 --- a/help_test.go +++ b/help_test.go @@ -128,9 +128,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*ExitError) + exitErr, ok := err.(*exitError) if !ok { - t.Fatalf("expected *ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { @@ -173,9 +173,9 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { t.Fatalf("expected error from helpCommand.Action(), but got nil") } - exitErr, ok := err.(*ExitError) + exitErr, ok := err.(*exitError) if !ok { - t.Fatalf("expected *ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error()) } if !strings.HasPrefix(exitErr.Error(), "No help topic for") { diff --git a/helpers_test.go b/helpers_test.go index bcfa46b..767f404 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -13,7 +13,7 @@ var ( ) func init() { - os.Setenv("CLI_TEMPLATE_REPANIC", "1") + _ = os.Setenv("CLI_TEMPLATE_REPANIC", "1") } func expect(t *testing.T, a interface{}, b interface{}) { diff --git a/template.go b/template.go index c631fb9..558ab50 100644 --- a/template.go +++ b/template.go @@ -20,7 +20,6 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} @@ -63,19 +62,18 @@ USAGE: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} - - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} - + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} +{{end}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} {{end}}{{end}} ` var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }} - -% {{ .App.Author }} +{{ range $Author := .App.Authors}} +% {{ $Author.Name }} +{{- end}} # NAME From afbaca6ed72b7a69e16299f7f92315333b2aaf5e Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 15 Sep 2019 17:24:16 +0530 Subject: [PATCH 136/158] Fix README.md --- README.md | 39 ++++++++++++++------------------------- errors.go | 6 +++--- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 08303e0..e789096 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,6 @@ discovery. So a cli app can be as little as one line of code in `main()`. package main import ( - "log" "os" "github.com/urfave/cli/v2" @@ -652,7 +651,7 @@ func main() { app := cli.NewApp() app.Flags = []cli.Flag { - cli.StringFlag{ + &cli.StringFlag{ Name: "password, p", Usage: "password for the mysql database", FilePath: "/etc/mysql/password", @@ -754,6 +753,7 @@ For example this: package main import ( + "log" "os" "github.com/urfave/cli/v2" @@ -943,13 +943,12 @@ func main() { Flags: []cli.Flag{ &cli.BoolFlag{ Name: "ginger-crouton", - Value: true, Usage: "is it in the soup?", }, }, Action: func(ctx *cli.Context) error { if !ctx.Bool("ginger-crouton") { - return cli.Exit("it is not in the soup", 86) + return cli.Exit(Ginger croutons are not in the soup, 86) } return nil }, @@ -998,9 +997,9 @@ func main() { Name: "short", Usage: "complete a task on the list", Flags: []cli.Flag{ - cli.BoolFlag{Name: "serve, s"}, - cli.BoolFlag{Name: "option, o"}, - cli.StringFlag{Name: "message, m"}, + &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, + &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, }, Action: func(c *cli.Context) error { fmt.Println("serve:", c.Bool("serve")) @@ -1040,7 +1039,7 @@ show an app's subcommands, but you can write your own completion methods for the App or its subcommands. ``` go @@ -1114,11 +1113,11 @@ to the name of their program (as above). #### Customization -The default shell completion flag (`--generate-completion`) is defined as -`cli.GenerateCompletionFlag`, and may be redefined if desired, e.g.: +The default shell completion flag (`--generate-bash-completion`) is defined as +`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: ``` go @@ -1132,13 +1131,8 @@ import ( ) func main() { - cli.GenerateCompletionFlag = &cli.BoolFlag{ - Name: "compgen", - Hidden: true, - } - app := &cli.App{ - EnableShellCompletion: true, + EnableBashCompletion: true, Commands: []*cli.Command{ { Name: "wat", @@ -1174,7 +1168,6 @@ package main import ( "fmt" - "log" "io" "os" @@ -1233,7 +1226,6 @@ setting `cli.HelpFlag`, e.g.: package main import ( - "log" "os" "github.com/urfave/cli/v2" @@ -1269,7 +1261,6 @@ setting `cli.VersionFlag`, e.g.: package main import ( - "log" "os" "github.com/urfave/cli/v2" @@ -1300,7 +1291,6 @@ package main import ( "fmt" - "log" "os" "github.com/urfave/cli/v2" @@ -1352,7 +1342,6 @@ func init() { cli.SubcommandHelpTemplate += "\nor something\n" cli.HelpFlag = &cli.BoolFlag{Name: "halp"} - cli.GenerateCompletionFlag = &cli.BoolFlag{Name: "compgen", Hidden: true} cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { @@ -1432,7 +1421,7 @@ func main() { HideHelp: false, Hidden: false, HelpName: "doo!", - ShellComplete: func(c *cli.Context) { + BashComplete: func(c *cli.Context) { fmt.Fprintf(c.App.Writer, "--better\n") }, Before: func(c *cli.Context) error { @@ -1475,10 +1464,10 @@ func main() { &cli.UintFlag{Name: "age"}, &cli.Uint64Flag{Name: "bigage"}, }, - EnableShellCompletion: true, + EnableBashCompletion: true, HideHelp: false, HideVersion: false, - ShellComplete: func(c *cli.Context) { + BashComplete: func(c *cli.Context) { fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") }, Before: func(c *cli.Context) error { diff --git a/errors.go b/errors.go index 8ec6f89..be58903 100644 --- a/errors.go +++ b/errors.go @@ -73,8 +73,8 @@ func NewExitError(message interface{}, exitCode int) ExitCoder { // HandleExitCoder func Exit(message interface{}, exitCode int) ExitCoder { return &exitError{ - exitCode: exitCode, message: message, + exitCode: exitCode, } } @@ -98,9 +98,9 @@ func HandleExitCoder(err error) { if exitErr, ok := err.(ExitCoder); ok { if err.Error() != "" { if _, ok := exitErr.(ErrorFormatter); ok { - fmt.Fprintf(ErrWriter, "%+v\n", err) + _, _ = fmt.Fprintf(ErrWriter, "%+v\n", err) } else { - fmt.Fprintln(ErrWriter, err) + _, _ = fmt.Fprintln(ErrWriter, err) } } OsExiter(exitErr.ExitCode()) From 046e68b391f17d8c40b39da5e47df32b8d0bfeac Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 15 Sep 2019 17:33:58 +0530 Subject: [PATCH 137/158] Update TOC --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e789096..d7cee58 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ applications in an expressive way. - [Installation](#installation) * [Supported platforms](#supported-platforms) * [Using the `v2` branch](#using-the-v2-branch) - * [Using `v1` releases](#using-v1-releases) + * [Pinning to the `v1` releases](#pinning-to-the-v1-releases) - [Getting Started](#getting-started) - [Examples](#examples) * [Arguments](#arguments) @@ -30,6 +30,7 @@ applications in an expressive way. + [Values from the Environment](#values-from-the-environment) + [Values from files](#values-from-files) + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + + [Default Values for help output](#default-values-for-help-output) + [Precedence](#precedence) * [Subcommands](#subcommands) * [Subcommands categories](#subcommands-categories) From 9f97625dbb7a3bbefbf2e4897c0c2ccf5bbfcc67 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 15 Sep 2019 17:39:36 +0530 Subject: [PATCH 138/158] fix appveyor build --- appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index a8c05a1..dbbcd0c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,11 +7,13 @@ image: Visual Studio 2017 clone_folder: c:\gopath\src\github.com\urfave\cli cache: -- node_modules + - node_modules environment: GOPATH: C:\gopath GOVERSION: 1.11.x + GO111MODULE: on + GOPROXY: https://proxy.golang.org install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% From 2746d8dee6f5c1f629eccc4b82ef8cbcd9622b44 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 15 Sep 2019 17:40:56 +0530 Subject: [PATCH 139/158] add module specific commands --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index dbbcd0c..4c3d669 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,8 +19,8 @@ install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env - - go get github.com/urfave/gfmrun/... - - go get -v -t ./... + - go get github.com/urfave/gfmrun/cmd/gfmrun + - go mod vendor build_script: - go run build.go vet From ef1ba861f79e79c12b58afc4e5c870fe28d44452 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 15 Sep 2019 18:46:38 +0530 Subject: [PATCH 140/158] add is set method to flag interface - refer #294 --- CHANGELOG.md | 1 + app.go | 27 +-- app_test.go | 4 + args.go | 8 +- category.go | 7 - cli-v1-to-v2 | 479 ------------------------------------------ context.go | 35 +-- context_test.go | 103 ++------- flag.go | 43 +--- flag_bool.go | 19 +- flag_duration.go | 19 +- flag_float64.go | 18 +- flag_float64_slice.go | 27 +-- flag_generic.go | 28 +-- flag_int.go | 18 +- flag_int64.go | 18 +- flag_int64_slice.go | 17 +- flag_int_slice.go | 17 +- flag_path.go | 7 + flag_string.go | 52 +++-- flag_string_slice.go | 18 +- flag_uint.go | 16 +- flag_uint64.go | 16 +- 23 files changed, 197 insertions(+), 800 deletions(-) delete mode 100755 cli-v1-to-v2 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() - } + //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 - 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 + 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 { From b161244b73fe20b8a0728bbd2637f6e6dfa63f2f Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Mon, 16 Sep 2019 11:27:14 +0530 Subject: [PATCH 141/158] Merge latest master into v2-merge-master commit c71fbcefd21552b70cd625b2c54466006e258ad7 Merge: 61f3ae3 ef47250 Author: Ajitem Sahasrabuddhe Date: Thu Sep 12 05:35:50 2019 +0530 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge pull request #887 from urfave/asahasrabuddhe-patch-1 Release 1.22.1 commit ef47250cda5ff52a313118c01ad6b0c5b4877a70 Merge: 71eaf37 61f3ae3 Author: Ajitem Sahasrabuddhe Date: Thu Sep 12 05:19:58 2019 +0530 Merge branch 'master' into asahasrabuddhe-patch-1 commit 61f3ae353bf455e3522aff0d5a28be9278bba7f2 Merge: 388c2dd fa858dc Author: Ajitem Sahasrabuddhe Date: Thu Sep 12 05:19:33 2019 +0530 Merge pull request #890 from urfave/issue-878 Fix #878 commit fa858dcc260fb07c25aab13650d9fa0e64f851c7 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 15:10:14 2019 +0530 Ensure flag is not blank commit f8bb66ae7d679973cf9b3f6f8c3dc6933404a31a Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:42:38 2019 +0530 Fix Typo commit 056aef13fe0b0e51403036ca5527854f50a1f3cd Merge: c6ee3b4 82a84fc Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:37:06 2019 +0530 Merge branch 'issue-878' of https://github.com/urfave/cli into issue-878 commit c6ee3b4904ed76d34f277c315c2097ae7b22d38f Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:34:41 2019 +0530 Use iterative logic to determine missing flag commit 82a84fc187c23434a5c2e1398a7fcfbc9c51df94 Merge: 1547ac2 388c2dd Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:17:11 2019 +0530 Merge branch 'master' into issue-878 commit 1547ac2f6a3d3d39fe4d49570c0d1c2401a8f20e Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:15:20 2019 +0530 Modify variable names commit 388c2dd0f4ffaa8541e371d49c8413870a04d9fe Merge: e19126a 6d888d6 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:13:40 2019 +0530 Merge pull request #891 from saschagrunert/fish-hidden Don't generate fish completion for hidden commands commit 71eaf37e337d5daea12c6a137113c71056151530 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:09:50 2019 +0530 Update CHANGELOG.md commit 6d888d693d81e13806356854c57574334d9ef3b9 Merge: bac5bde e19126a Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:04:44 2019 +0530 Merge branch 'master' into fish-hidden commit e19126a8198a7c076339e69ed4d372567750dd24 Merge: b207e20 35eb598 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 14:01:44 2019 +0530 Merge pull request #883 from urfave/remove-flag-generation Remove flag generation commit bac5bde38c7725990645cf9b2bf2c824594f3963 Author: Sascha Grunert Date: Wed Sep 11 09:06:02 2019 +0200 Don't generate fish completion for hidden commands Added the missing test case as well. Signed-off-by: Sascha Grunert commit 36cdaa9964df03e2b8f8d2147a99497536851ad9 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 10:34:00 2019 +0530 Update CHANGELOG.md commit cbb9e015b89225aa090c41085bdb0933f6290d96 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 09:21:45 2019 +0530 Improve Code and Add Test Case commit 7d6a604106e44732edc0a76f4a4800c8c27ddfbe Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 08:59:51 2019 +0530 Fix #878 commit be37c2cbda3ba6a37fa8f7a0df960de844afc843 Merge: 0aee120 b207e20 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 08:27:45 2019 +0530 Merge branch 'master' into asahasrabuddhe-patch-1 commit 35eb598d43c3ab639e3c0ccc72e37f294e5b5828 Merge: 8575558 b207e20 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 05:39:35 2019 +0530 Merge branch 'master' into remove-flag-generation commit 0aee120c32003fff6f320c2a00a41d03285fdde0 Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 05:37:41 2019 +0530 Update CHANGELOG.md commit 5c019b10ca37c1a0b74b637d3b0aa25dfeb0110f Author: Ajitem Sahasrabuddhe Date: Wed Sep 11 05:33:46 2019 +0530 Update CHANGELOG.md commit b207e20873f5805e7c9cc544b3418a0a3ec63e09 Merge: 249cb33 487be14 Author: Audrius Butkevicius Date: Tue Sep 10 21:28:35 2019 +0100 Merge pull request #889 from crosbymichael/hidden-man Don't output hidden commands for man pages commit 487be14dceb185a3321cacd9bf302f5e811f5ee8 Author: Michael Crosby Date: Tue Sep 10 13:49:11 2019 -0400 Don't output hidden commands for man pages Signed-off-by: Michael Crosby commit 85755588ac06f74702bf7d62802dab0655881182 Merge: 024692c 249cb33 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 15:04:12 2019 +0530 Merge branch 'master' into remove-flag-generation commit 249cb3339254361b1a884733a98d4202e2838b9b Merge: bfe2e92 abfb13b Author: Audrius Butkevicius Date: Tue Sep 10 08:28:09 2019 +0100 Merge pull request #885 from urfave/go-modules-support Go modules support commit abfb13b8542fbe3d542d46543ab0d3be6aacb4e5 Merge: 534d60b bfe2e92 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:56:41 2019 +0530 Merge branch 'master' into go-modules-support commit 054fbefec36cad7425dc6f4cfb6d2963c2710751 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:42:34 2019 +0530 Update CHANGELOG.md commit 534d60bb9bb0476141540ec77c5a3d51e176d162 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:40:45 2019 +0530 Bump minimum supported version of Go to 1.11 commit 024692c172f7000fe2431c3280a1e4b724b15945 Merge: 4a9e440 bfe2e92 Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:33:21 2019 +0530 Merge branch 'master' into remove-flag-generation commit bfe2e925cfb6d44b40ad3a779165ea7e8aff9212 Merge: 3eca109 238c80f Author: Ajitem Sahasrabuddhe Date: Tue Sep 10 06:24:10 2019 +0530 Merge pull request #882 from urfave/lynncyrin-patch-1 Release 1.22.0 commit 426e21c150d9a33e4d8c13c2a13c5234e85f3a0e Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 13:15:47 2019 +0530 Update .travis.yml Set GOPROXY in Travis environment commit 39bd6176649871817d1966b6b91f042be4b62fe9 Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 08:37:16 2019 +0530 Cleanup after before_script to avoid git diff errors remove windows build commit edbf66c25cf83541faee77d0064fdb5ac35a51b1 Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 08:23:04 2019 +0530 Update gfmrun import command to suite Go Modules pattern Fix test command typo in travis script commit afd0ecbbf2fbda2f9459046228ccc9e8d2693258 Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 08:19:06 2019 +0530 Add support for Go 1.13 Drop support for Go 1.11 Use md2man v2 to avoid dependency issues when building with Go Modules Enabled Update TravisCI build environment images (trusty was deprecated) Add optional Windows build commit 4a9e440503f4113a351322da93f8630df14dfcaa Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 08:12:09 2019 +0530 Fix AppVeyor build commit 5c81af9f10b974cecbec6e20e4976574e86fc78b Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 07:47:18 2019 +0530 Remove generate script from travis flow Remove unused dependencies from travis script commit b6c5d17a835d17e8dd8f2e34c02e96af7f43c9e4 Author: Ajitem Sahasrabuddhe Date: Mon Sep 9 07:44:07 2019 +0530 Remove Flag Generation Remove Legacy Python Scripts commit 238c80f9b5e54a834f25423fd0887f68b9737cbb Author: Lynn Cyrin (they/them) Date: Sat Sep 7 20:44:48 2019 -0700 Update CHANGELOG.md commit 980742b7cc56e8f7b14b4668fb8ddcf89025c9ea Author: Lynn Cyrin (they/them) Date: Sat Sep 7 20:43:56 2019 -0700 typo commit 890d49ca7a15a3fb7f44d33e5cdb499f0e5c2331 Author: Lynn Cyrin (they/them) Date: Sat Sep 7 20:41:44 2019 -0700 Release 1.22.0 - adds the changelog for 1.22.0 - updates the changelog for 1.21.0. some PRs were missed, as was mentioned here https://github.com/urfave/cli/pull/829#issuecomment-517968795 - closes https://github.com/urfave/cli/issues/867 commit 3eca1090a37a65fea5b84bdbd2c61f8104211b74 Merge: 38a6c56 4bbff84 Author: Audrius Butkevicius Date: Fri Aug 30 15:53:55 2019 +0100 Merge pull request #879 from saschagrunert/escape-single-quotes Escape single quotes in fish shell completion commit 4bbff841690954ed86c147147151c0144dcf1765 Author: Sascha Grunert Date: Thu Aug 29 14:45:32 2019 +0200 Escape single quotes in fish shell completion Single quotes can break the generated fish shell completion and should be escaped correctly. Signed-off-by: Sascha Grunert commit 38a6c560b3b8ac6d47e031a44b92e0531824c7e5 Merge: fa6797b 687f721 Author: Audrius Butkevicius Date: Thu Aug 29 20:52:07 2019 +0100 Merge pull request #857 from saschagrunert/takes-file-fish Add `TakesFile` to fish shell completion commit 687f721eaa40859950820b37b9ad1fcd85b7da9f Author: Sascha Grunert Date: Mon Aug 26 10:07:50 2019 +0200 Update function alignment Signed-off-by: Sascha Grunert commit 0c01922a12c501867cad200bc4b36a25f9a073e0 Author: Sascha Grunert Date: Mon Aug 26 08:46:55 2019 +0200 Add type switch Signed-off-by: Sascha Grunert commit 38d0ac629677a7fbf08a52e17fec73894bb31263 Author: Sascha Grunert Date: Sun Aug 25 17:50:18 2019 +0200 Removed GetTakesFile and stick to type assertions Signed-off-by: Sascha Grunert commit a1cf7f44b6cf65c0c10c282c71524cc37442b798 Author: Sascha Grunert Date: Mon Aug 12 09:42:12 2019 +0200 Add `TakesFile` to fish shell completion The new `TakesFile` flag will be now consumed by the fish shell completion generator. Signed-off-by: Sascha Grunert commit fa6797beefc5727035323623aed515e4dfd3ccdf Merge: 2344c98 82eb0d7 Author: Lynn Cyrin (they/them) Date: Sat Aug 24 18:58:52 2019 -0700 Merge pull request #876 from urfave/lynncyrin-patch-1 Bump go version to 1.10 in readme commit 82eb0d70cbcf89de5e71965fc9ededbb41cdbd96 Merge: edd8cb2 2344c98 Author: Audrius Butkevicius Date: Sun Aug 25 01:04:33 2019 +0100 Merge branch 'master' into lynncyrin-patch-1 commit 2344c98f678ac236c6bf952fe724bc5b0a6bd69b Merge: 55de011 68ee2bc Author: Audrius Butkevicius Date: Sun Aug 25 01:04:17 2019 +0100 Merge pull request #860 from saschagrunert/takes-file-not-all Update `TakesFile` flag to apply only to supported flags commit edd8cb2068b6501d6b631299038cb42194926a8e Author: Lynn Cyrin (they/them) Date: Sat Aug 24 14:44:56 2019 -0700 Bump go version to 1.10 in readme Closes https://github.com/urfave/cli/issues/875 commit 68ee2bc4af27ae14cedbfb881384b0900a0ed3a9 Merge: 959d9ec 55de011 Author: Lynn Cyrin (they/them) Date: Sat Aug 24 14:34:15 2019 -0700 Merge branch 'master' into takes-file-not-all commit 55de011cf89b3d78842e3b3e2cf92f9d157fa399 Merge: 392c1de d3edef8 Author: Audrius Butkevicius Date: Sat Aug 24 11:55:28 2019 +0100 Merge pull request #873 from urfave/show-test-failures build: show failures when running tests commit d3edef887a2fc39830216cd41b16955ef60d0d3c Author: Audrius Butkevicius Date: Sat Aug 24 11:34:03 2019 +0100 Update build.go commit c2d1a132082e3b02a219e61eeef49da364d2c315 Author: Lynn Cyrin Date: Sat Aug 24 03:05:45 2019 -0700 Revert "check length" This reverts commit 1095838cca9d596e55cff88bcb35b67cf83bf4e4. commit 959d9ec36b7848004fd2e85f07b810266d65c8d2 Merge: 3681b05 392c1de Author: Sascha Grunert Date: Sat Aug 24 11:23:51 2019 +0200 Merge branch 'master' into takes-file-not-all commit 7d62a9d0547cbab68e78c2c92a79db97ee61f115 Merge: 1095838 392c1de Author: Lynn Cyrin (they/them) Date: Sat Aug 24 00:50:42 2019 -0700 Merge branch 'master' into show-test-failures commit 1095838cca9d596e55cff88bcb35b67cf83bf4e4 Author: Lynn Cyrin Date: Sat Aug 24 00:49:29 2019 -0700 check length commit 29ad6ee6ad7e02dbec3334e8843bb6711c011b55 Author: [[ BOT ]] Lynn Cyrin Date: Fri Aug 23 20:09:08 2019 -0700 DRY commit 392c1de1a2b3f8bc2ca95c2389dd05469d347b14 Merge: 23c8303 487c723 Author: Audrius Butkevicius Date: Fri Aug 23 22:51:39 2019 +0100 Merge pull request #874 from saschagrunert/go-mod-cleanup Cleanup go modules commit 487c7236736db7d5d2e46633d09453d0e149a0bd Author: Sascha Grunert Date: Fri Aug 23 10:28:32 2019 +0200 Cleanup go modules These two dependencies are not really needed, which can be reproduced via: ``` > export GO111MODULE=on && go mod tidy ``` Signed-off-by: Sascha Grunert commit 8469a9de07c45435b61cbfd4aed7167fb9e59cca Author: [[ BOT ]] Lynn Cyrin Date: Thu Aug 22 21:42:07 2019 -0700 show test failures commit 23c83030263f7adfc0e3c34b567ee508e8d536cf Merge: ecd576e 6a25af9 Author: Lynn Cyrin (they/them) Date: Sat Aug 17 11:24:05 2019 -0700 Merge pull request #862 from russoj88/UpdateREADME_gopkg.in_v1 Rewrite the "pinning to v1" section. commit 6a25af96413deaeb4d6c451d6288079db0840a82 Merge: 3bc62c4 ecd576e Author: russoj88 Date: Sat Aug 17 10:01:35 2019 -0700 Merge branch 'master' into UpdateREADME_gopkg.in_v1 commit ecd576e779bce41496738b34e9ee6272c63801d0 Merge: 6cc7e98 e11183f Author: Audrius Butkevicius Date: Sat Aug 17 16:51:43 2019 +0100 Merge pull request #868 from urfave/lynncyrin-patch-1 Modernize readme commit e11183fe50e3b3d75e481b1262e3222c565ba8bf Author: Lynn Cyrin (they/them) Date: Sat Aug 17 02:44:49 2019 -0700 Modernize readme I assume that people no longer care about what the package was named many years ago commit 3bc62c4fde03e107cad02f8828780470258b8fc0 Author: russoj88 Date: Thu Aug 15 12:30:29 2019 -0700 Mimic v2 example code from above. commit 62b8a7cc2cb05b1a454908087b35e5780a1d12ad Author: russoj88 Date: Wed Aug 14 11:20:09 2019 -0700 Add "Using v1 releases" to table of contents. commit cc091db561b137c49cbf370766a94b47cfdae182 Author: russoj88 Date: Wed Aug 14 11:21:40 2019 -0700 Update README.md Only instruct on right way to use library. Co-Authored-By: Lynn Cyrin (they/them) commit f529dad70caa6e307f95eecb2db6f16efc0f964d Author: russoj88 Date: Wed Aug 14 11:20:58 2019 -0700 Update README.md Include suggestion to put example in. Co-Authored-By: Lynn Cyrin (they/them) commit f2c26bab772e6b69a9fca945534728678578eb2b Author: russoj88 Date: Tue Aug 13 21:10:38 2019 -0700 Rewrite the "pinning to v1" section. commit 3681b057c5df7f380e75974674a8282cf5632dc3 Author: Sascha Grunert Date: Tue Aug 13 09:43:57 2019 +0200 Update `TakesFile` flag to apply only to supported flags Signed-off-by: Sascha Grunert commit 6cc7e987c4fa553caa5014c7dbc1e7acaea9f0f1 Merge: 7e49cc2 08c24e2 Author: Audrius Butkevicius Date: Mon Aug 12 21:30:37 2019 +0100 Merge pull request #856 from FaranIdo/master Add Subcommand fallback call to ExitErrHandler, fixing #816 commit 08c24e22ed2c4bebb348a738caf92c40bb63133c Author: FaranIdo Date: Mon Aug 12 00:29:46 2019 +0300 add missing ExitErrHandler in command + matching test, fixing #816 commit 7e49cc210a231eec218c2fba82df106af06d05b5 Merge: 8b18c71 4e42a2f Author: Ajitem Sahasrabuddhe Date: Sat Aug 10 09:01:16 2019 +0000 Merge pull request #848 from saschagrunert/fish-shell Add fish shell completion support commit 4e42a2f02ceb3cbfe2f8c4e5c6e419a712c335c9 Merge: 56d12d0 8b18c71 Author: Ajitem Sahasrabuddhe Date: Sat Aug 10 08:47:31 2019 +0000 Merge branch 'master' into fish-shell commit 8b18c71e1a4eabe8d7ba20d81d7fbd882709833d Merge: 7058c58 c6c2008 Author: Ajitem Sahasrabuddhe Date: Sat Aug 10 08:47:23 2019 +0000 Merge pull request #851 from saschagrunert/takes-file Add `TakesFile` indicator to flag commit 56d12d0c2f27a159e95165cf3cec2396df6f68af Merge: 7506b11 7058c58 Author: Ajitem Sahasrabuddhe Date: Fri Aug 9 17:21:24 2019 +0530 Merge branch 'master' into fish-shell commit c6c200864d770982106717a20ad99603396fb042 Merge: e9e9e0a 7058c58 Author: Sascha Grunert Date: Fri Aug 9 13:48:36 2019 +0200 Merge branch 'master' into takes-file commit 7058c58eb6af9ee166dafdf82012e1241890223d Merge: 2e0e39a de0fa70 Author: Ajitem Sahasrabuddhe Date: Fri Aug 9 17:16:13 2019 +0530 Merge pull request #847 from saschagrunert/remove-date-var Remove unused `Date` variable from `cliTemplate` commit de0fa704331adf0183d6f1b6d94a2390a48a810c Merge: 0d79d1d 2e0e39a Author: Audrius Butkevicius Date: Fri Aug 9 12:38:50 2019 +0100 Merge branch 'master' into remove-date-var commit e9e9e0ac5dfce215fb2207b3bbde2534a67907f6 Author: Sascha Grunert Date: Fri Aug 9 09:05:55 2019 +0200 Add `TakesFile` indicator to flag This new member of `Flag` indicates if the flag expects a file as input. This is especially useful for documentation and shell completion purposes. Signed-off-by: Sascha Grunert commit 7506b11da746beef287831f805f5b0e49264b400 Author: Sascha Grunert Date: Thu Aug 8 15:50:36 2019 +0200 Add fish shell completion support This commit adds a new method `ToFishCompletion` to the `*App` which can be used to generate a fish completion string for the application. Relates to: #351 Signed-off-by: Sascha Grunert commit 2e0e39a03b46023f83ec2e70f1948836e0581543 Merge: 946f918 aed704a Author: Ajitem Sahasrabuddhe Date: Fri Aug 9 10:34:28 2019 +0530 Merge pull request #845 from urfave/lint-fixes linter fixes commit 0d79d1d9d99db9e380e37034f677523b0ca435b5 Author: Sascha Grunert Date: Thu Aug 8 14:04:21 2019 +0200 Remove unused `Date` variable from `cliTemplate` Signed-off-by: Sascha Grunert commit aed704a9d036852c332867dffd97c60c51e8a38d Merge: 0990ca2 946f918 Author: Ajitem Sahasrabuddhe Date: Thu Aug 8 14:44:02 2019 +0530 Merge branch 'master' into lint-fixes commit 946f918365f62f6fe8d7fb7d4ea54dd441eccfb6 Merge: 2c477e7 286133f Author: Audrius Butkevicius Date: Thu Aug 8 09:13:09 2019 +0100 Merge pull request #735 from rliebz/combined Add app-wide support for combining short flags commit 0990ca2391ac8a72bc59d393e64ca520d9c53772 Merge: fdba7e0 2c477e7 Author: Ajitem Sahasrabuddhe Date: Thu Aug 8 13:36:30 2019 +0530 Merge branch 'master' into lint-fixes commit 286133fee5ef662bcfc9fdb7e410ce83528ab1f8 Merge: 815c29f 2c477e7 Author: Ajitem Sahasrabuddhe Date: Thu Aug 8 13:33:32 2019 +0530 Merge branch 'master' into combined commit 2c477e720e69b9ce81e9d6cf68c81a0334446016 Merge: e0057bb 99fad61 Author: Audrius Butkevicius Date: Thu Aug 8 08:41:19 2019 +0100 Merge pull request #830 from saschagrunert/docs-gen Add markdown and man page docs generation methods commit 99fad61ded52131321a0d7e5d330554512254ebe Merge: 40d4a25 e0057bb Author: Audrius Butkevicius Date: Thu Aug 8 07:06:08 2019 +0100 Merge branch 'master' into docs-gen commit e0057bb59731900e8b702b0b5282378577cb99e5 Merge: 521735b fd39578 Author: Audrius Butkevicius Date: Thu Aug 8 07:04:25 2019 +0100 Merge pull request #846 from urfave/asahasrabuddhe-patch-1 Update README.md commit 815c29ffc73623b32e587298eeda0a0dd2ff5737 Merge: a77c440 521735b Author: Audrius Butkevicius Date: Thu Aug 8 07:03:49 2019 +0100 Merge branch 'master' into combined commit fd395786a2c77da3f111c6208ba50b3041fe6ee2 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 21:00:07 2019 +0530 Update README.md remove quotes around coverage badge commit fdba7e0f8c921d4ce169cb416b2eae58026c83e9 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 20:14:50 2019 +0530 linter fixes code cleanup changing some test code to ensure uniformity commit 40d4a25a01296d2f51bf7bbf0b6e8eb99ba4a84c Author: Sascha Grunert Date: Sat Aug 3 12:41:50 2019 +0200 Add markdown and man page docs generation methods This adds two new methods to the `App` struct: - `ToMarkdown`: creates a markdown documentation string - `ToMan`: creates a man page string Signed-off-by: Sascha Grunert commit 521735b7608a25d771a39d42e2267e061e7e84b8 Merge: 97179ca 22e1fc8 Author: Audrius Butkevicius Date: Wed Aug 7 12:10:34 2019 +0100 Merge pull request #844 from urfave/asahasrabuddhe-patch-1 Update README.md commit 22e1fc84192059f056a7b53aa5ef2ee7113d9a83 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 14:02:52 2019 +0530 Update README.md add codecov.io badge commit 97179ca390abf228a187e6ebbedca69636d60f0d Merge: b6f7dd9 3a41d6d Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 13:58:51 2019 +0530 Merge pull request #843 from lafriks/patch-1 Support GoLang 1.10 to 1.12 commit 3a41d6d7851b15f132131444865e82b17baf0be0 Author: Lauris BH Date: Wed Aug 7 11:21:31 2019 +0300 Lower support to GoLang compiler version 1.10 commit 1f4473209dcad42ed88ba68f1be7d4e906ae91be Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 13:50:04 2019 +0530 Update .travis.yml support go versions in line with go's release policy commit e3fa7e8566f9374ac6c1e08ace0e0555f9666e10 Author: Lauris BH Date: Wed Aug 7 11:06:15 2019 +0300 Support also GoLang 1.11 compiler commit b6f7dd93594d17c08d349ba5f974e501b8c12b7b Merge: 93392d1 e2de8c7 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 13:28:34 2019 +0530 Merge pull request #836 from urfave/flag-type-generation-golang Flag Generation in the CLI commit e2de8c74587d464770155415cda09c3569224692 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 12:38:17 2019 +0530 update readme with correct error message, add 1.12 and 1.11 to travis commit a77c440b8476d59b59fcf2606a92fd3bb82603c3 Merge: 8d31c5e 93392d1 Author: Robert Liebowitz Date: Tue Aug 6 22:33:49 2019 -0400 Merge branch 'master' into combined commit 24de27b05e91ef797b9ba97e3c146842fb8e29d8 Merge: c19938f 93392d1 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 01:54:44 2019 +0530 Merge branch 'master' into flag-type-generation-golang commit c19938fbbfb19120beeca5d0af02291a99e61f27 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 01:51:02 2019 +0530 update ci commands commit 6ee5b89e03a0fc47c6351c902ef714f1475e8fde Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 01:50:50 2019 +0530 move build.go to root commit e8bbb4c3b5472320f0af5fb00961c76f1061edd4 Author: Ajitem Sahasrabuddhe Date: Wed Aug 7 01:46:22 2019 +0530 remove unnecessary sprintf commit 5070d00811cd5f2f21b0a6e021581e8efb2479e9 Author: Ajitem Sahasrabuddhe Date: Tue Aug 6 12:33:33 2019 +0530 move packages slice to global scope commit 93392d12e8cd60a1c7c62dc4bf4bab7a8e001eec Merge: 26945c5 1db0496 Author: Audrius Butkevicius Date: Tue Aug 6 07:59:53 2019 +0100 Merge pull request #808 from yogeshlonkar/master Add support for flags bash completion commit adfe6a09c121a6b96357ea161f2d679d509c013f Author: Ajitem Sahasrabuddhe Date: Tue Aug 6 12:14:18 2019 +0530 indenting fix commit 1db049685ac49e11b2e27285e1287793cfe0ea84 Author: Yogesh Lonkar Date: Mon Aug 5 20:22:52 2019 +0200 Fix unused regex commit 2be2bc755e4634d34136769a426a7ca52e698cc0 Author: Yogesh Lonkar Date: Mon Aug 5 20:18:08 2019 +0200 Add additional test for log flag completion and comments commit c3f51bed6fffdf84227c5b59bd3f2e90683314df Author: Yogesh Lonkar Date: Mon Aug 5 17:07:46 2019 +0200 Fix SC2199: Arrays implicitly concatenate in commit c5612e8cd21e0cd99f73d23103df99a9af70f853 Author: Yogesh Lonkar Date: Mon Aug 5 16:58:04 2019 +0200 Fix review comments commit 8d31c5e167103ef4235cc5553b0fb45a2f6e8f74 Author: Robert Liebowitz Date: Mon Aug 5 07:05:07 2019 -0400 Update README.md Co-Authored-By: Ajitem Sahasrabuddhe commit 03153b9cf8988d787fe79d02bd4138283ea507bd Author: Robert Liebowitz Date: Mon Aug 5 06:16:30 2019 -0400 Allow combining short flags globally commit d6523cf8692d40c0ff9f6d81f7ac25341c58da7a Merge: e949dc2 26945c5 Author: Yogesh Lonkar Date: Mon Aug 5 11:00:26 2019 +0200 Merge branch 'master' into master commit 26945c58edddd8cb0830baf73ebc7bee44b5f455 Merge: d09efb5 c25e4ca Author: Lynn Cyrin (they/them) Date: Sun Aug 4 12:36:23 2019 -0700 Merge pull request #823 from xordspar0/master Make the exit code example more clear commit c25e4cab32bab49d1d3c4847a0a6419e2cb3dd15 Merge: b1a7c50 d09efb5 Author: Lynn Cyrin (they/them) Date: Sun Aug 4 12:30:28 2019 -0700 Merge branch 'master' into master commit ac5c97b41844032ae47d5f94d7f73533af629f11 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:45:28 2019 +0530 add latest assets file commit 489d92d2e2477b240015e83af07658e826eaa7cb Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:44:15 2019 +0530 add missing os package commit a7f0d86509d0845980b77999dc5882b32f244818 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:33:21 2019 +0530 add zero mod fs back and commit file with latest ts commit 798e1f3d3aba8d04c9118962870b41ad099b7bea Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:29:53 2019 +0530 fix spacing issue commit 7a6f3d4394003447c99b223ce1c1e19e23b20124 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:23:32 2019 +0530 fix tests commit 58ae5eb590667b0115d3f82e03593d87e2924b1c Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 20:06:44 2019 +0530 move entire build system to go commit 8547458f1d93654348b3bbeccb6d04424d5eab3e Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 19:01:47 2019 +0530 remove zero mod fs commit d09efb5fbd744f23d561c02b99a16a61e679bba6 Merge: 7745000 1327f58 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 18:00:11 2019 +0530 Merge pull request #837 from urfave/codeowners Use codeowners instead of maintainers commit 1327f583142a70a5d2fb9ef8422862b84a1a8780 Merge: 9938dec 7745000 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 17:52:42 2019 +0530 Merge branch 'master' into codeowners commit e949dc2cc05a553645eea447ded78eddfec0ad37 Merge: 11c9e59 7745000 Author: Yogesh Lonkar Date: Sun Aug 4 10:38:56 2019 +0200 Merge branch 'master' into master commit 4b0a4104630bddd01a7a6c5aa8a70dc94642ab52 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 12:26:48 2019 +0530 fix travis build commit fb4cea5f30995b6caaa700ea789902af499d63b2 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 12:22:45 2019 +0530 add new generation logic to travis commit 365557021fe254d059d0f7065bb0c94686208723 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 12:17:27 2019 +0530 remove legacy version check code commit b6bfbe97f8430a4dfe05791363719d61cb921793 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 12:16:07 2019 +0530 update appveyor to go 1.11, add support for code coverage generation in tests commit 826954c97919610e0f4086ab3bcc007ac3326184 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 11:09:34 2019 +0530 update app name and remove version commit 04948f21526ed8343ae9697600e284962d8972be Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 11:06:19 2019 +0530 generate test with go generate commit 86e10211dea0c2b369610aabea1c987888bbb01a Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 11:05:50 2019 +0530 remove redundant go generate from altsrc update go generate in cli package to generate both files regeneration test commit c676ed4caa76219414ad737584ec46b03e94cbf8 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 11:04:23 2019 +0530 indentation fixes in template regeneration test commit c4fc88e46d182072228b2b2a0236b0f77a45e567 Merge: 2a08494 9260850 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 10:29:34 2019 +0530 Merge branch 'flag-type-generation-golang' of https://github.com/urfave/cli into flag-type-generation-golang commit 2a084945a47235959c023291f87127ead86fc168 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 10:28:08 2019 +0530 move around code change package to flag-gen to avoid conflict with flag-generator binary test code generation commit 065fe9e9af992d82126929c157edb16b1a1f06ab Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 10:05:44 2019 +0530 change structure to embed source json and template files restructure code to have defaults in place of choices commit d1ded77768d33ce64657686558884e69cbb5bce4 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 08:45:29 2019 +0530 rename utility from fg > flag-generator commit 7745000a0eabd118a94df095da6feeae887f9a34 Merge: e6cf83e 81acbeb Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 08:15:00 2019 +0530 Merge pull request #774 from whereswaldon/patch-1 Clarify that altsrc supports both TOML and JSON commit 81acbeb629cebde2c8c3c36421644e42ffd9e8f9 Merge: 8abc5a2 e6cf83e Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 08:12:24 2019 +0530 Merge branch 'master' into patch-1 commit e6cf83ec39f6e1158ced1927d4ed14578fda8edb Merge: 244eba7 eee6ce8 Author: Lynn Cyrin (they/them) Date: Sat Aug 3 19:37:52 2019 -0700 Merge pull request #829 from urfave/lynncyrin-patch-2 Release 1.21.0 commit 8abc5a2e49624c8f12a0b1734b57bd12aadf1604 Merge: b2421d1 244eba7 Author: Ajitem Sahasrabuddhe Date: Sun Aug 4 08:04:13 2019 +0530 Merge branch 'master' into patch-1 commit 9938dec695d6a0ba5a4d84b703766333cd7d10e8 Author: [[ BOT ]] Lynn Cyrin Date: Sat Aug 3 10:26:07 2019 -0700 update contributing docs commit 97dbddb32db290fdc6392e6a669a92acbadef9ff Author: [[ BOT ]] Lynn Cyrin Date: Sat Aug 3 10:23:29 2019 -0700 use codeowners instead of maintainers commit 92608509a4c011a598dcc5b10d15930040fa403e Merge: d209be3 244eba7 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:49:24 2019 +0530 Merge branch 'master' into flag-type-generation-golang commit d209be324522a802f8056094f8bb89b4562ca9a3 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:32:36 2019 +0530 update go generate command test file generation commit add69c7d4fbef52ac8541c2f7dfa465fdd9be2c3 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:16:25 2019 +0530 updated flag types generated courtesy fg cli commit c133a5aeb16e0978e3c29f8446ddf02922b7e150 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:15:43 2019 +0530 add explicit true/false choices for value and dest keys due to go default false for bool types commit 16c7a60528bc5f7d98030e09630a38f3d8fc9ddc Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 22:14:58 2019 +0530 finish generation of flag types for altsrc package rename package to fg (flag generator) commit 32ddef5ca7f20a9aa0e7b80484e59d1653e856e4 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 21:48:48 2019 +0530 finish generation of flag types for cli package commit 9766be8d3e11e71a2a4bb5c235fc6961278df983 Author: mingrammer Date: Thu Mar 7 00:04:18 2019 +0900 get latest changes from master commit e01e3c540c7aea9e4e9740d002bd212491c2fe00 Author: mingrammer Date: Wed Mar 6 23:51:22 2019 +0900 Fix the unaligned indents for the commands that have no categories commit b1a7c502eba6a479bf9432052136f73d7740a69b Merge: c75a689 244eba7 Author: Lynn Cyrin (they/them) Date: Sat Aug 3 09:36:27 2019 -0700 Merge branch 'master' into master commit eee6ce83c075e8aeb1d71a22f69e6fddbc70a248 Merge: 8a7f65e 244eba7 Author: Lynn Cyrin (they/them) Date: Sat Aug 3 09:06:47 2019 -0700 Merge branch 'master' into lynncyrin-patch-2 commit 11c9e598b06a0a2201f356f945c4cd2355a8ccbf Merge: 01ab016 244eba7 Author: Yogesh Lonkar Date: Sat Aug 3 15:52:08 2019 +0200 Merge branch 'master' into master commit 244eba7e4c24eb9a416bb1edadaf74d943a7bb89 Merge: 1169906 4627bbe Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 18:04:31 2019 +0530 Merge pull request #831 from saschagrunert/go-modules Add go module support commit b2421d123539be62bc9f2f9bdd77fce3cc2d1af6 Merge: 3e14507 1169906 Author: Audrius Butkevicius Date: Sat Aug 3 13:25:34 2019 +0100 Merge branch 'master' into patch-1 commit 4627bbe109fb902221de0a86f20048ad5679ea0c Author: Sascha Grunert Date: Sat Aug 3 12:55:06 2019 +0200 Add go module support This adds a go.{mod,sum} file to official support go modules. Signed-off-by: Sascha Grunert commit 1169906f575ec070559cc1a6fc083b38498160c0 Merge: 07c1638 94f4f83 Author: Audrius Butkevicius Date: Sat Aug 3 11:02:34 2019 +0100 Merge pull request #773 from teresy/redundant-nil-check-slice Remove redundant nil checks commit 94f4f8367278436d4554b3e7a72ec6b66c209312 Merge: da581b2 07c1638 Author: Lynn Cyrin (they/them) Date: Sat Aug 3 02:05:33 2019 -0700 Merge branch 'master' into redundant-nil-check-slice commit 07c163896936a0fb3ad24dcbf05b25e9c7aaee53 Merge: 842e3fe 7a51175 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 08:47:32 2019 +0530 Merge pull request #806 from mingrammer/fix-help-indentation Fix the unaligned indents for the command help messages commit 7a51175ce1cb78d5009e91001697240c1af84ee2 Merge: 330a914 842e3fe Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 08:37:34 2019 +0530 Merge branch 'master' into fix-help-indentation commit 842e3fe1b6adbb257f711a1f66553ad03174c5c0 Merge: 7675649 fa51d00 Author: Ajitem Sahasrabuddhe Date: Sat Aug 3 08:12:05 2019 +0530 Merge pull request #828 from urfave/lynncyrin-patch-1 Update maintainers for current reality commit 8a7f65e05215a76a2246b7b42cb9c082b5eae483 Author: Lynn Cyrin (they/them) Date: Fri Aug 2 19:30:41 2019 -0700 Update CHANGELOG.md commit e8eac43d9d73e96f6b034f89770ab2fdbec2cb5b Author: Lynn Cyrin Date: Fri Aug 2 18:26:41 2019 -0700 Update CHANGELOG.md commit 330a9143fb1ebfe61bb382b5e45833c02a405c0c Merge: ddc3453 7675649 Author: Lynn Cyrin Date: Fri Aug 2 18:14:50 2019 -0700 Merge branch 'master' into fix-help-indentation commit fa51d00dc6fb57edca14295fde308401d7720e61 Author: Lynn Cyrin Date: Fri Aug 2 18:06:15 2019 -0700 Update maintainers for current reality commit 01ab0164275805b0c43bc5f7f534a2d8f6abe48e Merge: d79d2a0 7675649 Author: Yogesh Lonkar Date: Fri Aug 2 22:03:55 2019 +0200 Merge branch 'master' into master commit c75a689f629137700e8a30651f95cc41cf12a6d1 Author: Jordan Christiansen Date: Fri Aug 2 14:28:57 2019 -0500 Make exit code example more clear The purpose of this example is to show that you can exit with an error code if a flag is unspecified, but with the code as it is, the only way to cause a non-zero exit is by adding the flag `--ginger-crouton=false`, which is not explained in the example. In this new version of the example, running the command with no flag will exit with an error, and running it with the flag will exit normally. commit 7675649a174ac724b1d7fa9d5932eff3dd1582a2 Merge: 656063a f8ba505 Author: Ajitem Sahasrabuddhe Date: Fri Aug 2 22:52:32 2019 +0530 Merge pull request #819 from lynncyrin/required-flags-take-2 Required flags commit f8ba505a7cc01559767d9d961fb68bda833d5d3d Merge: 60fb297 656063a Author: Ajitem Sahasrabuddhe Date: Fri Aug 2 22:49:29 2019 +0530 Merge branch 'master' into required-flags-take-2 commit 656063a84689d3e45f16ab9c40706e4df219190a Merge: 693af58 6505336 Author: Ajitem Sahasrabuddhe Date: Fri Aug 2 22:49:09 2019 +0530 Merge pull request #788 from benzvan/master adds test coverage to context commit 60fb2972328d6a7487c6821a58a86d476167c2bd Author: Lynn Cyrin Date: Thu Aug 1 23:27:34 2019 -0700 remove help assertion stuff commit d7ec4e801357fa5ccfab53669a42f78fc1a69d39 Author: Lynn Cyrin Date: Thu Aug 1 23:26:43 2019 -0700 add env var tests commit f4128a02f3215e532dff0b96e21a8e2cb08389a1 Author: Lynn Cyrin Date: Thu Aug 1 22:54:15 2019 -0700 Update command.go commit 38f9e1622d2d4a5e1a86afd8f8f9d6cbf0157816 Author: Lynn Cyrin Date: Thu Aug 1 22:52:21 2019 -0700 add environment variable support :tada: commit f21b22dd904b638518d9ea321d718f219bd6593c Author: Lynn Cyrin Date: Thu Aug 1 22:10:18 2019 -0700 cleanup some issues with error display commit fdd4d106912b363ccffa03eec51a56dd3a6a822b Author: Lynn Cyrin Date: Thu Aug 1 21:48:52 2019 -0700 update comments commit ef9acb4a3b846728c98844f7f92964ae2a79f259 Author: Lynn Cyrin Date: Thu Aug 1 21:46:56 2019 -0700 rename cases commit 45f2b3d8e71e11822cf591f1c370f8587726c425 Author: Lynn Cyrin Date: Thu Aug 1 21:45:11 2019 -0700 more test cases commit 78db152323afb7934f9f0dd207eeaf34147bb300 Author: Lynn Cyrin Date: Thu Aug 1 21:35:15 2019 -0700 add typed error assertions commit d4740d10d0cbde53a8e3132a0964464b2b50fc0b Author: Lynn Cyrin Date: Thu Aug 1 20:58:08 2019 -0700 more test cases commit 595382c50970039261f765043aee4c647aeccbd5 Author: Lynn Cyrin Date: Thu Aug 1 20:39:37 2019 -0700 expand test cases commit 3d6eec825ac768894a385ca3c3156a7905c27ce3 Author: Lynn Cyrin Date: Thu Aug 1 20:35:23 2019 -0700 add test cases commit 7b9e16b6b5255803ea279fe1ee0e41973f49a42e Author: Lynn Cyrin Date: Thu Aug 1 20:30:43 2019 -0700 update test names commit 95d3a8624d8aa6661831d0009550b602458fcb4d Author: Lynn Cyrin Date: Thu Aug 1 20:27:51 2019 -0700 update test to reflect app flag usage commit 714a73f028fa5a3c5b6512bf5e55b94be388de8f Author: Lynn Cyrin Date: Thu Aug 1 19:57:14 2019 -0700 remove unused thing commit 9438aba3b89e7053070ef277121a14e5fb95947e Author: Lynn Cyrin Date: Thu Aug 1 19:54:57 2019 -0700 remove showFlagError, we can use the help printer assertion to accomplish the same goal commit 386b379d1950e8939c8a3dbba0335cf79903f421 Author: Lynn Cyrin Date: Sun Jul 28 22:45:43 2019 -0700 Revert "reset generated flags changes" This reverts commit 9ec594d5290b846de59b0bc350849b848cabfbd7. commit 9ec594d5290b846de59b0bc350849b848cabfbd7 Author: Lynn Cyrin Date: Sun Jul 28 22:34:07 2019 -0700 reset generated flags changes commit 23f09ac1e82395dc1a70c36d649ab03929e32d79 Author: Lynn Cyrin Date: Sun Jul 28 22:19:35 2019 -0700 cleanup tests, check required flags in more places commit d79d2a04242b21441061e00475287f4b826614f8 Author: Yogesh Lonkar Date: Wed Jul 24 16:08:47 2019 +0200 Fix issue with source command completion Avoid competion for bash builtin `source` and fallback to default implementation as it throws below error ``` -bash: source: --: invalid option source: usage: source filename [arguments] ``` commit 7ce0af189ed431005f47e583a63648ea9a0a99ea Author: Lynn Cyrin Date: Thu Jul 18 00:52:24 2019 -0700 remove unused code commit d8985dc6d56ac75b35f0422d8efbc04814bf17f3 Author: Lynn Cyrin Date: Thu Jul 18 00:51:16 2019 -0700 reduce diff commit 19140e1fb52f458727a3c718f82fb93861d5849c Author: Lynn Cyrin Date: Thu Jul 18 00:48:09 2019 -0700 show errors commit 2299852c3c3512dafac738a10847da3bb3699b62 Author: Lynn Cyrin Date: Thu Jul 18 00:47:18 2019 -0700 cleanup subcommand and specs commit 300288670fe7713da8ae6e4a449d12e6c911b713 Author: Lynn Cyrin Date: Thu Jul 18 00:20:32 2019 -0700 add subcommand commit cc1cf8c459c947156bb429ef319f4cf762b1e468 Author: Lynn Cyrin Date: Thu Jul 18 00:09:07 2019 -0700 wording shift commit 32d84d8e870a7f475c228c3c58f2c879f6a4009e Author: Lynn Cyrin Date: Wed Jul 17 00:25:13 2019 -0700 copy update commit 01d5cfab7066912c97eeaf94cbbda8f90fc490f7 Author: Lynn Cyrin Date: Wed Jul 17 00:20:44 2019 -0700 use strings.Join commit cdc7af744e07ac8dbb34793f8b392af46ba443f7 Author: Lynn Cyrin Date: Wed Jul 17 00:16:40 2019 -0700 add handling for multiple required flags commit 9293f5b3cc6f5a96a1976bf2f810c957044c5ee8 Author: Lynn Cyrin Date: Sun Jul 14 21:00:16 2019 -0700 visually shorten logic commit f00f35ce8c1a6ebd7a3900901dd8b05049fbefc7 Author: Lynn Cyrin Date: Sat Jul 13 14:02:45 2019 -0700 docs commit 17108e1db49db34480170f575131e642b22bda2d Author: Lynn Cyrin Date: Sat Jul 13 13:59:29 2019 -0700 tabs commit cf824804c2353572e0f99b098829f2d7ffe2a0ec Author: Lynn Cyrin Date: Sat Jul 13 13:57:06 2019 -0700 update tests commit 80d7e91191cfb38c7e3fccbcf2b1320807d4b05d Author: Lynn Cyrin Date: Sat Jul 13 03:51:26 2019 -0700 fill out test cases commit 746866c10daf9425d41140f78ffc518ee4d9ae01 Author: Lynn Cyrin Date: Sat Jul 13 03:44:39 2019 -0700 add update integration with the help output commit 550ed20ea429e19b7b132984a6e34c057acabc42 Author: Lynn Cyrin Date: Sat Jul 13 01:26:47 2019 -0700 update tests commit f6777bf4bf44867abbcaa63a97a67db60469ea80 Author: Lynn Cyrin Date: Sat Jul 13 01:03:46 2019 -0700 quote the flag name commit 6a2ae783730e54eb7ea91cf6839ed46446134017 Author: Lynn Cyrin Date: Thu Jul 11 21:53:10 2019 -0700 backwards compatible RequiredFlag implementation commit 922d2318916c3b59eafde03c36b12551a71f2d51 Author: Lynn Cyrin Date: Thu Jul 11 21:28:09 2019 -0700 ./generate-flag-types cli -i flag-types.json -o flag_generated.go commit 8a58b7e039e37b0631d6ced0ab3279c319c4d8c8 Author: Lynn Cyrin Date: Thu Jul 11 20:47:47 2019 -0700 remove manual isRequired funcs commit 62e99ad1c16714cda6c9f8b980dd9483372771e2 Author: Lynn Cyrin Date: Thu Jul 11 20:46:22 2019 -0700 add IsRequired to generator commit 310bfeb1942571dfe0ac9f60f45e75df11189e4e Author: Lynn Cyrin Date: Thu Jul 11 20:44:41 2019 -0700 add required attr to generator commit af627c73c3ddc2d4ff1e4c0847c3355bc0a47c0d Author: Lynn Cyrin Date: Thu Jul 11 20:34:17 2019 -0700 update func name commit 3d2d6975b4fffee753c9422f3440d6b7c114ef40 Author: Lynn Cyrin Date: Thu Jul 11 20:32:42 2019 -0700 reduce diff commit 0608059cc709e86905bfd18886d6649275c9937e Author: Lynn Cyrin Date: Thu Jul 11 20:32:15 2019 -0700 reduce diff commit 9c299e7e8af265e017adf7abf431a0fe0c89dd95 Author: Lynn Cyrin Date: Thu Jul 11 20:28:29 2019 -0700 reduce diff commit 30a71dc427bc2634f00d9fe315e5717022e0eb66 Author: Lynn Cyrin Date: Thu Jul 11 20:25:52 2019 -0700 update Run command commit f7d5e2c21e4cca02de26a7f448d69f4dac531af7 Author: Lynn Cyrin Date: Thu Jul 11 20:22:16 2019 -0700 reduce diff commit e6842c0b7521b5e608da30a4e8a5ed06e6469cf7 Author: Lynn Cyrin Date: Thu Jul 11 20:21:05 2019 -0700 merge in test file commit fa8187f2ce6a7d8258899b46ccfe081c9c0ea6f7 Author: Lynn Cyrin Date: Thu Jul 11 20:19:42 2019 -0700 reduce diff commit ce1630141e70b2ca599a21fd9494e98b88f25b2d Author: Lynn Cyrin Date: Thu Jul 11 20:18:52 2019 -0700 reduce diff??? commit 138dbaafec9db29d5b0b10af383ca7c6848a2c0d Merge: aba73ce 693af58 Author: Lynn Cyrin Date: Thu Jul 11 20:07:55 2019 -0700 Merge branch 'master' into required_flags commit da581b24e88a3b8d5b3d4b7685f9eee32ec0df8e Merge: 6aa7f35 693af58 Author: Audrius Butkevicius Date: Fri Jun 28 07:55:04 2019 +0100 Merge branch 'master' into redundant-nil-check-slice commit 65053360c7533fc585bdb9f53abada1e9b39f564 Author: Ben Zvan Date: Wed Jun 26 09:41:11 2019 -0500 Revert "Created using Colaboratory" This reverts commit 83b99c4109dce6ac7a6b5d2048e26f1ad60ef3f3. This commit was randomly created here when I connected to colaboratory commit 83b99c4109dce6ac7a6b5d2048e26f1ad60ef3f3 Author: Ben Zvan Date: Tue Jun 25 18:47:58 2019 -0500 Created using Colaboratory commit 23042d37079702af32e4bcae8d41bb72569431da Merge: 4a76377 693af58 Author: Ben Zvan Date: Fri Jun 14 10:14:07 2019 -0500 Merge branch 'master' into master commit 62f02f21ef0b5c3c0aa67d3240aee15bc8a53457 Author: Yogesh Lonkar Date: Thu Apr 11 10:57:58 2019 +0530 Don't complete hidden flags commit 1d7a2b08d6f8e9764e2f2b911b1bb9fa49596f92 Author: Yogesh Lonkar Date: Thu Mar 21 13:01:48 2019 +0530 Add default completion on commands, test cases, refactor code commit fb1421d9031313c5e0f3c4a92625ed9cf5739b0d Author: Yogesh Lonkar Date: Wed Mar 20 21:34:56 2019 +0530 Fix duplicate completion of existing flag commit 58a072d5733d4bb2dc61ffbc3557ec9592e34adc Author: Yogesh Lonkar Date: Wed Mar 20 20:28:51 2019 +0530 Add bash completion support for flags commit ddc3453179ea450663473db4689f7c256225a72b Author: mingrammer Date: Thu Mar 7 00:04:18 2019 +0900 Update README.md commit a0453b2200cafa97ce263a4a5df87f5087d2abda Author: mingrammer Date: Wed Mar 6 23:51:22 2019 +0900 Fix the unaligned indents for the commands that have no categories commit 693af58b4d51b8fcc7f9d89576da170765980581 Merge: e229212 d7c3be8 Author: Audrius Butkevicius Date: Sun Feb 3 18:40:40 2019 +0000 Merge pull request #766 from agis/patch-1 Fix README typo commit 6aa7f352fa56438b4c8fcaff43e1050855526051 Merge: 21dfc6e e229212 Author: Audrius Butkevicius Date: Sun Feb 3 18:39:49 2019 +0000 Merge branch 'master' into redundant-nil-check-slice commit e2292127695d01e9fc3511f2ec7ef651bf3ca8af Merge: b67dcf9 5b83c89 Author: Audrius Butkevicius Date: Sun Feb 3 18:37:18 2019 +0000 Merge pull request #798 from Quasilyte/patch-1 use type switch instead of if/else commit 5b83c895a70b7714548f0aa4f43deb3fa5fc1601 Author: Iskander (Alex) Sharipov Date: Tue Jan 29 22:51:02 2019 +0300 use type switch instead of if/else This reduces the syntax noise of the code by removing excessive type assertions. Signed-off-by: Iskander Sharipov commit 4a76377775cebfc3dca4af752ba2837f9694b9d8 Author: Ben Zvan Date: Wed Dec 26 12:48:12 2018 -0600 go fmt commit d63733fe14aad10beca5490a453904bc1d67fe16 Author: Ben Zvan Date: Wed Dec 26 12:41:27 2018 -0600 adds test coverage to context commit b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b Merge: cbebba9 11ab68f Author: Audrius Butkevicius Date: Mon Oct 29 21:32:00 2018 +0000 Merge pull request #776 from gliptak/patch-2 Bring Go version current commit 11ab68f24d392fc36615c650bc6241c0b96c4318 Merge: 769f6d5 cbebba9 Author: Audrius Butkevicius Date: Mon Oct 29 21:19:45 2018 +0000 Merge branch 'master' into patch-2 commit cbebba941b23ee6f666b057c9f3d0937263ddd01 Merge: 934abfb 9587fc2 Author: Audrius Butkevicius Date: Mon Oct 29 21:18:40 2018 +0000 Merge pull request #775 from gliptak/patch-1 Correct typo commit 769f6d543bd3c9b36b98e3a46ad646cf63769120 Author: Gábor Lipták Date: Thu Oct 18 21:00:02 2018 -0400 Bring Go version current commit 9587fc27bd923141975eac8c34288bcf8de5cca2 Author: Gábor Lipták Date: Thu Oct 18 20:56:13 2018 -0400 Correct typo commit 3e145076abdbaf5c6e47e311b5e659251604a49b Author: Christopher Waldon Date: Fri Oct 12 11:30:46 2018 -0400 Clarify that altsrc supports both TOML and JSON commit 21dfc6eb8302c4db3547a22b0843c43e36fe058e Author: teresy Date: Wed Oct 10 14:54:48 2018 -0400 Remove redundant nil checks commit d7c3be82673f869fed4ea77a0c5e3f13bd65ba89 Author: Agis Anastasopoulos <827224+agis@users.noreply.github.com> Date: Tue Aug 21 11:19:37 2018 +0300 Fix README typo commit 934abfb2f102315b5794e15ebc7949e4ca253920 Merge: 8e01ec4 3e5a935 Author: Audrius Butkevicius Date: Tue Aug 21 07:40:27 2018 +0100 Merge pull request #758 from vrothberg/fix-short-opts-parsing short opt handling: fix parsing commit 3e5a935ed3cafadcddc6f5ab2fe7ddd2aa0c3cea Author: Valentin Rothberg Date: Tue Aug 21 08:33:42 2018 +0200 fix `go vet` warning command_test.go:342:3 value declared but not used Signed-off-by: Valentin Rothberg commit c23dfba7018a4666892af705d89150a5f1ac8293 Author: Valentin Rothberg Date: Thu Jun 28 16:41:02 2018 +0200 short opt handling: fix parsing Only split a given string (e.g., "-abc") into short options (e.g., "-a", "-b", "-c") if all those are flags. To further avoid mistakenly transform common arguments, catch "flag provided but not defined" errors to iteratively transform short options. Signed-off-by: Valentin Rothberg Fixes: https://github.com/projectatomic/libpod/issues/714 commit 8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff Merge: d4bf9ce 8dc47eb Author: Dan Buch Date: Sun Feb 25 22:02:53 2018 -0500 Merge pull request #598 from urfave/backport-json-support Backport JSON InputSource to v1 commit 8dc47eb3cbaea99e0d3e04424f6f3649657a6944 Merge: f551359 d4bf9ce Author: Dan Buch Date: Sun Feb 25 16:09:48 2018 -0500 Merge branch 'master' into backport-json-support commit d4bf9ce8609adfc60775b62676fa3471e7d978e0 Merge: b09aafd e59e474 Author: Dan Buch Date: Sun Feb 25 16:09:04 2018 -0500 Merge pull request #498 from urfave/merging-jereksel-zsh Merging #489 (plus hack) commit e59e4743b884a22e06b1b78a893c68513c0702b7 Merge: 5fc8124 b09aafd Author: Dan Buch Date: Sat Feb 24 22:02:40 2018 -0500 Merge branch 'master' into merging-jereksel-zsh commit b09aafdfe9ad3fa4353b82eaed4e80598878c128 Merge: 446f49e bc77a15 Author: Dan Buch Date: Sat Feb 24 22:02:19 2018 -0500 Merge pull request #681 from urfave/check-run-error-in-readme Update README examples to check for errors commit 5fc8124af17aae44085140a2a7b3141a973dbda0 Merge: 688c5a9 446f49e Author: Dan Buch Date: Sat Feb 24 21:58:26 2018 -0500 Merge branch 'master' into merging-jereksel-zsh commit bc77a15c69f9aadd39b15be9373abc4916c8ad53 Merge: 59e1ddb 446f49e Author: Dan Buch Date: Sat Feb 24 21:54:06 2018 -0500 Merge branch 'master' into check-run-error-in-readme commit 446f49e78f79a164079a99a88446182703331c75 Merge: a1c7408 45289ea Author: Jesse Szwedko Date: Tue Feb 20 21:16:31 2018 -0800 Merge pull request #715 from urfave/maintainers-notice Adjust contribution and maintainer prose per current reality commit 45289ea7a0de564a71532e13b9916961a38abc8e Author: Dan Buch Date: Tue Feb 20 12:40:43 2018 -0500 Adjust contribution and maintainer prose per current reality commit 59e1ddb43ed48e52bf1a0aca8a310d9bca9897d7 Merge: 9838c8b a1c7408 Author: Dan Buch Date: Tue Feb 13 15:27:04 2018 -0500 Merge branch 'master' into check-run-error-in-readme commit a1c7408de3f632d86eee604a3bb755f1ffb68226 Merge: 803d066 3a87b13 Author: Jesse Szwedko Date: Sat Feb 10 18:18:39 2018 -0800 Merge pull request #712 from windler/fix_args_reorder Fix args reordering when bool flags are present commit 3a87b13b01ac8628694f1e1b20bdb452cc0f54d2 Author: Nico Windler Date: Sat Feb 10 13:35:23 2018 +0100 Fix args reordering when bool flags are present commit 803d0665796d3b09d3190067803fc285d1604732 Merge: 75104e9 d7555e1 Author: Jesse Szwedko Date: Fri Feb 2 13:13:18 2018 -0800 Merge pull request #704 from dolmen/replace-unneeded-Sprintf Fix unnecessary uses of Sprintf commit d7555e172994da8d058334aa1fe69533b1685924 Author: Olivier Mengué Date: Fri Jan 26 21:14:34 2018 +0100 Fix unnecessary uses of Sprintf - use strconv directly - use concatenation for "%s%s" commit 75104e932ac2ddb944a6ea19d9f9f26316ff1145 Merge: 39908eb e38e4ae Author: Jesse Szwedko Date: Sat Jan 6 11:10:48 2018 -0800 Merge pull request #697 from urfave/fix-skip-flag-parsing Fix regression of SkipFlagParsing behavior commit e38e4ae2d05acf5b5164c160a67fb7048e1358b0 Author: Jesse Szwedko Date: Fri Dec 29 13:38:18 2017 -0500 Fix regression of SkipFlagParsing behavior Introduced by df562bf1a8626f2d16f91fcbf7230a5bdca3d592 Was mistakenly prepending the command name. commit 39908eb08fee7c10d842622a114a5c133fb0a3c6 Merge: 119bb65 2610681 Author: Jesse Szwedko Date: Tue Dec 12 08:34:29 2017 -0800 Merge pull request #691 from urfave/refactor-686 Refactor flag handling logic commit 2610681040722bb0a9d04c3a784a44d2efb52379 Merge: 0671b16 119bb65 Author: Jesse Szwedko Date: Mon Dec 11 18:51:46 2017 -0800 Merge branch 'master' into refactor-686 commit 0671b166dcacb3dc1215ba65bf986dab194581dc Author: Jesse Szwedko Date: Mon Dec 4 09:23:40 2017 -0800 Add tests for flag reordering commit 119bb6564841921ce6f1401e0f5d75317bdd9f4d Merge: c9eba3f c6eb2a0 Author: Jesse Szwedko Date: Sun Dec 3 13:42:37 2017 -0800 Merge pull request #690 from gliptak/patch-1 Correct go vet for Go tip commit df562bf1a8626f2d16f91fcbf7230a5bdca3d592 Author: Jesse Szwedko Date: Sun Dec 3 13:38:50 2017 -0800 Refactor flag handling logic Refactor logic introduced by #686 commit c9eba3f37a524c4fed60a8f3585ea5f304fd436d Merge: c6af884 ceaac7c Author: Jesse Szwedko Date: Sun Dec 3 12:48:28 2017 -0800 Merge pull request #686 from baude/shortoptionSkipArg Handle ShortOptions and SkipArgReorder commit c6eb2a051026c083d4e33591f8d6e95d5f4189dc Author: Gábor Lipták Date: Thu Nov 30 19:43:12 2017 -0500 Correct go vet for Go tip https://travis-ci.org/cloudflare/logshare/jobs/309796141#L646 commit ceaac7c9152121e6ba0f3b492b3254d61346f92a Author: baude Date: Mon Nov 20 09:32:03 2017 -0600 Handle ShortOptions and SkipArgReorder There was a bug in parsing when both ShortOptions and SkipArgReorder were being used together. Signed-off-by: baude commit c6af8847eb2b7b297d07c3ede98903e95e680ef9 Merge: 7ace96b 37b7abb Author: Jesse Szwedko Date: Mon Nov 27 19:55:04 2017 -0800 Merge pull request #687 from joshuarubin/master Don't clobber slices with EnvVar commit 37b7abb1c491c8c3630a2a98bb02a7051efbcc06 Author: Joshua Rubin Date: Tue Nov 21 15:21:31 2017 -0700 dont clobber slices with envvar Signed-off-by: Joshua Rubin commit 7ace96b43d4bdc46f81d0d1219742b2469874cf6 Merge: 44cb242 fd5382e Author: Jesse Szwedko Date: Wed Nov 15 20:56:12 2017 -0800 Merge pull request #684 from baude/shortOptionHandling Combine bool short names commit fd5382e7a539858cc19d7eed7755f7102bae5da9 Author: baude Date: Mon Nov 13 15:28:23 2017 -0600 Combine bool short names Adds the ability to allow the combination of bool short-name options. For example, cmd foobar -ov This is done through a bool "UseShortOptionHandler" set in the command struct. Built upon PR #621 Signed-off-by: baude commit 9838c8bcaa19fdb33259f6e0f9740d9fd3cbe13c Author: Jesse Szwedko Date: Sat Nov 11 16:23:24 2017 -0800 Update README examples to check for errors To encourage good practices. commit 43c8c02cf5a10196e5a4c458fdbfee90a561e97c Author: zhuchensong Date: Mon Apr 17 00:47:04 2017 +0800 Support POSIX-style short flag combining commit 44cb242eeb4d76cc813fdc69ba5c4b224677e799 Merge: 7f4b273 f971fca Author: Jesse Szwedko Date: Fri Nov 3 19:35:40 2017 -0700 Merge pull request #675 from jmccann/continue3 Ability to load variable from file - UPDATED commit f971fca2b2664c4dec0cee24225dc3c415211498 Author: Jacob McCann Date: Thu Oct 26 13:08:03 2017 -0500 Allow FilePath to take []string commit 18a556e1927fbe11c31fae47a7e3acf275ef6ae4 Author: Brad Rydzewski Date: Mon Apr 10 16:45:51 2017 +0200 fix FilePath documentation in README.md commit 4cc453ba6792515a8013340f8919e6c4b44851b7 Author: Brad Rydzewski Date: Sat Apr 1 12:55:46 2017 +0900 document field in README commit c698b821b896e9723d53c4ad1e81680f39a8cdc1 Author: Brad Rydzewski Date: Sat Apr 1 12:37:06 2017 +0900 unit tests for load from file commit 21fcab0dee7dab6969e929cf1740306bae1e16ad Author: Brad Rydzewski Date: Fri Mar 31 16:24:15 2017 +0900 ability to load variable from file commit 7f4b273a05858e05b96b6adf0a7907b7b695c352 Merge: 7bc6a0a b44660a Author: Jesse Szwedko Date: Mon Oct 30 19:55:34 2017 -0700 Merge pull request #676 from rliebz/lexicographic-sort Consider case when sorting strings commit b44660ac3da2f8e651372c40ae803782bddea283 Author: Robert Liebowitz Date: Sat Oct 28 03:00:11 2017 -0400 Consider case when sorting strings This makes sorting flags and other sections consistent with how most command line tools function, by placing both flags `-A` and `-a` before a flag `-B`. commit 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c Merge: 2997500 40263f4 Author: Jesse Szwedko Date: Sat Oct 14 13:27:26 2017 -0700 Merge pull request #628 from phinnaeus/master Allow custom ExitError handler function commit 40263f4d6aaf7383fb53bd97a5c34c02be39eda8 Merge: 7233c50 2997500 Author: Tyler Davis Date: Fri Oct 13 12:05:14 2017 -0700 Merge branch 'master' into master commit 2997500ba5f393daa5d76c18544c6367b2c48d16 Merge: ac24947 c202606 Author: Jesse Szwedko Date: Sat Oct 7 13:56:23 2017 -0700 Merge pull request #672 from sierraechobravo/master fix go report card issues commit c202606a17a763fcc1b320cac6cf584662e31364 Author: Sebastian Sprenger Date: Fri Oct 6 07:29:13 2017 +0200 fix golint issues commit c3cc74dac756e33c2919ab998481809e8720e068 Author: Sebastian Sprenger Date: Fri Oct 6 07:28:43 2017 +0200 fix ineffective assigns commit 67ee172e6da2cdad8e48af107eef0fbfd1e85eec Author: Sebastian Sprenger Date: Fri Oct 6 07:28:18 2017 +0200 fix misspelling issue commit ac249472b7de27a9e8990819566d9be95ab5b816 Merge: 7fb9c86 cbbe4c1 Author: Jesse Szwedko Date: Mon Sep 25 20:41:18 2017 -0700 Merge pull request #661 from rliebz/custom-flag-help Allow customization of prefixes and environment variable hints in flag help strings commit 7233c502e31b3e6d93e3e8cf5fe0616f4d32c020 Merge: 5dc55f2 7fb9c86 Author: Tyler Davis Date: Fri Sep 22 10:08:36 2017 -0700 Merge branch 'master' into master commit cbbe4c1a2c34e52c8ad0937c01c9c15ef407a6d5 Author: Robert Liebowitz Date: Mon Sep 18 00:44:42 2017 -0400 Add tests for custom flag prefix/env hints commit 11d45572f9727acfbc93daa8565f379d396125d6 Author: rliebz Date: Sat Aug 26 07:42:25 2017 -0400 Export funcs to configure flag prefix/env hints This will allow users to customize the prefix section or env hint section of the flag entries in the help menu without having to reimplement the rest of the logic required in defining FlagStringer. commit 7fb9c86b14e6a702a4157ccb5a863f07d844a207 Merge: f017f86 1d334f1 Author: Jesse Szwedko Date: Sun Sep 10 21:08:19 2017 -0700 Merge pull request #664 from maguro/master Add newline before command categories commit 1d334f10ce73c2b9e65c50a2290a86be3c743ff2 Author: Alan D. Cabrera Date: Fri Sep 8 10:37:48 2017 -0700 Add newline before command categories The simple formatting change adds a nice blank line before each command category. Documentation in README.md is also updated to be more accurate. commit 5dc55f22878a35487bdc17393f391bf25142c6e3 Merge: 10e81ba f017f86 Author: Dan Buch Date: Sun Aug 13 12:42:49 2017 -0400 Merge branch 'master' into master commit f017f86fccc5a039a98f23311f34fdf78b014f78 Merge: cfb3883 44c6487 Author: Dan Buch Date: Sun Aug 13 10:59:49 2017 -0400 Merge pull request #659 from urfave/define-flag-precedence Define flag source precedence in README commit 44c648739b75283265541baca66ed984476a17f5 Merge: e1fa109 cfb3883 Author: Dan Buch Date: Sun Aug 13 10:54:04 2017 -0400 Merge branch 'master' into define-flag-precedence commit cfb38830724cc34fedffe9a2a29fb54fa9169cd1 Author: Jesse Szwedko Date: Thu Aug 10 18:42:03 2017 -0700 Prepare CHANGELOG for v1.20.0 release commit f5513590f52b5f90566a10ad9452f52dffd469f8 Merge: 6a70c4c b99aa81 Author: Jesse Szwedko Date: Thu Aug 10 18:06:12 2017 -0700 Merge branch 'master' into backport-json-support commit e1fa109a3195a9fedcb635841ca1907b764ada1f Author: Jesse Szwedko Date: Thu Aug 10 17:54:24 2017 -0700 Define flag source precedence in README Fixes #646 commit 688c5a9d4f3beffff9d4fa50bd85907b7067d9a4 Merge: 7250c97 4b90d79 Author: Dan Buch Date: Thu Aug 3 14:38:20 2017 -0400 Merge branch 'master' into merging-jereksel-zsh commit 10e81bacd12f5c5a44d3a3e2d6e168d0c2533245 Merge: 5d528e2 4b90d79 Author: Tyler Davis Date: Thu Jul 20 12:44:56 2017 -0700 Merge branch 'master' into master commit 5d528e2052b3e7a49293d6aa0fac245047ea61e3 Author: Tyler Davis Date: Wed Jun 28 13:04:09 2017 -0700 use exit errors in uts commit 58450552ee1bada60f4175897aff8d69f7c904a1 Author: Tyler Davis Date: Wed Jun 28 12:52:50 2017 -0700 Add Test commit 71bdf81f5a65dc253482cb727c2ae973ae3b3830 Author: Tyler Davis Date: Wed Jun 28 10:10:11 2017 -0700 sigh... fix one more named parameter issue commit 172bb92059ed885c8b4249230f3ccbe9e3e1272b Author: Tyler Davis Date: Wed Jun 28 10:07:25 2017 -0700 fix named parameter issue commit 530df59178874f8d792d2d9cfd745464076f1eda Author: Tyler Davis Date: Wed Jun 28 09:52:12 2017 -0700 Pass context into handleExitCoder commit 9d61cbad0260bc7f2a72b07142a0120072e3800a Author: Tyler Davis Date: Tue Apr 25 12:45:08 2017 -0700 Updated command.go to use App handleExitCoder commit ceee6408d5cbbb9f113157d0a62b1ffed1f2b510 Author: Tyler Davis Date: Tue Apr 25 13:02:05 2017 -0700 Revert "Fix how to do defaults in app.go" This reverts commit 8906567dc2ad52fd31c50cf02fa606505a1323ba. commit 80b09a4d1117ad69430582685e59dfe560caa948 Author: Tyler Davis Date: Tue Apr 25 11:20:41 2017 -0700 Fix how to do defaults in app.go commit 827da610b4bff0ffbc06cd2d92eddae552f7d1a2 Author: Tyler Davis Date: Tue Apr 25 09:33:54 2017 -0700 Add a bit more documentation commit 538742687bbd979a7b4f975468af76ce5cffb972 Author: Tyler Davis Date: Tue Apr 25 09:31:53 2017 -0700 Add ExitErrHandlerFunc type commit c48a82964028acd0f19ee17257789f7c9f5afc78 Author: Tyler Davis Date: Tue Apr 25 09:29:43 2017 -0700 Allow custom exit err handlers commit 6a70c4cc923c7359bacfa0500dc234d62e0ca986 Author: John Weldon Date: Sat Jul 2 12:35:48 2016 -0700 Add JSON InputSource to altsrc package - Implement NewJSONSource* functions for returning an InputSource from various JSON data sources. - Copy and modify YAML tests for the JSON InputSource Changes: * Reverted the method calls and structs to match the v1 interface commit 7250c97913c213f17c721cb3fac5e2f555b198ca Merge: 363d9c9 0bdedde Author: Dan Buch Date: Wed Dec 21 15:11:00 2016 -0500 Merge branch 'master' into merging-jereksel-zsh commit 363d9c9a314cdb9ed68cad1a27c767b45eee8840 Author: Dan Buch Date: Sun Jul 24 17:29:13 2016 -0400 Add a hack so that zsh completion only runs for zsh commit 1cbb9a7f300b11a8e5a92b1fb24d8aeb168e0275 Merge: e43a9fb ceeebab Author: Dan Buch Date: Sun Jul 24 17:12:43 2016 -0400 Merge branch 'zsh' of https://github.com/jereksel/cli into jereksel-zsh commit ceeebaba04790bab2ecd03caded488528b0caf97 Author: Andrzej Ressel Date: Thu Jul 21 00:02:16 2016 +0200 [PoC] Improve zsh autocompletions commit aba73cedacbb7b1cec2efb9962460683cd00a90c Author: jhowarth Date: Tue Mar 3 14:02:42 2015 -0800 Copy the writer of the App to the subcommand App commit a6482d268753644175e769dd91ca3a4dfe838964 Merge: b5844af 50c77ec Author: jhowarth Date: Mon Mar 2 15:21:01 2015 -0800 Merge remote-tracking branch 'upstream/master' Conflicts: app.go command.go flag.go commit b5844af29892a881ea1d22ed0082f1e0a1559bfa Merge: 8f1fb06 145da32 Author: Jesse Howarth Date: Mon Mar 2 14:53:57 2015 -0800 Merge pull request #2 from ivey/requiredFlags Required flags commit 145da3210f41f401b1f42a08385d11ee8a80ec97 Author: jhowarth Date: Mon Mar 2 12:06:42 2015 -0800 don't require flags when the help flag is included commit 6023f370c1dfea78d4ff99a6ecc6be261347bfc9 Author: jhowarth Date: Mon Mar 2 12:00:21 2015 -0800 dry error messages commit e67e05f617978eec7bba579a6c86f3d0c11ad96b Author: jhowarth Date: Mon Mar 2 11:56:29 2015 -0800 DRY error handling commit cbd95292ac9c4ba7eb30ca121fbe3825ced64f72 Author: jhowarth Date: Mon Mar 2 11:18:59 2015 -0800 Remove debugging commit 8f1fb06a585610fdb76d38bc67a5edc89da4e82f Merge: 9908e96 4b2fcdb Author: Jesse Howarth Date: Tue Dec 2 15:23:01 2014 -0800 Merge pull request #1 from ivey/required_flags Required flags commit 4b2fcdb1ade79300c56074de8e7a7bf754cd407e Author: Jesse Howarth and Michael Ivey Date: Tue Dec 2 21:08:24 2014 +0000 Add tests for required flags commit 73e64a14fde90fc3e85fdebb3647af6024e48de0 Author: Jesse Howarth and Michael Ivey Date: Tue Dec 2 19:02:56 2014 +0000 Add (required) to help of flags that are required. commit 7e0532002650b69f219f34f4614656261be45363 Author: Jesse Howarth and Michael Ivey Date: Tue Dec 2 17:44:55 2014 +0000 Implement required flags --- CHANGELOG.md | 6 +-- README.md | 16 +++----- altsrc/json_source_context.go | 7 ++-- altsrc/toml_command_test.go | 2 +- app.go | 43 ++++---------------- app_test.go | 74 ----------------------------------- appveyor.yml | 1 - command.go | 4 -- command_test.go | 2 +- context.go | 54 +------------------------ context_test.go | 4 +- flag.go | 3 +- flag_int64.go | 1 - flag_int_slice.go | 1 + flag_string_slice.go | 1 - flag_test.go | 2 - help.go | 1 + template.go | 7 ++-- 18 files changed, 32 insertions(+), 197 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1924f5..1a4b1d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,10 +20,10 @@ To migrate to the new API, you may choose to run [the migrator (python) script](./cli-v1-to-v2). - The optimistic reordering of arguments and flags introduced by - https://github.com/codegangsta/cli/pull/36. This behavior only worked when + https://github.com/urfave/cli/pull/36. This behavior only worked when all arguments appeared before all flags, but caused [weird issues with boolean - flags](https://github.com/codegangsta/cli/issues/103) and [reordering of the - arguments](https://github.com/codegangsta/cli/issues/355) when the user + flags](https://github.com/urfave/cli/issues/103) and [reordering of the + arguments](https://github.com/urfave/cli/issues/355) when the user attempted to mix flags and arguments. Given the trade-offs we removed support for this reordering. - adapter code for deprecated `Action` func signature diff --git a/README.md b/README.md index d7cee58..c701b36 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ applications in an expressive way. - [Installation](#installation) * [Supported platforms](#supported-platforms) * [Using the `v2` branch](#using-the-v2-branch) - * [Pinning to the `v1` releases](#pinning-to-the-v1-releases) + * [Using `v1` releases](#using-v1-releases) - [Getting Started](#getting-started) - [Examples](#examples) * [Arguments](#arguments) @@ -104,9 +104,6 @@ import ( ... ``` -**NOTE**: There is a [migrator (python) script](./cli-v1-to-v2) available to aid -with the transition from the v1 to v2 API. - ### Pinning to the `v1` releases Similarly to the section above describing use of the `v2` branch, if one wants @@ -120,7 +117,7 @@ $ go get github.com/urfave/cli ```go ... import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli" ) ... ``` @@ -710,11 +707,10 @@ Here is a more complete sample of a command using YAML support: "output": "--test value.*default: 0" } --> ``` go -package notmain +package main import ( "fmt" - "log" "os" "github.com/urfave/cli/v2" @@ -735,7 +731,7 @@ func main() { Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), Flags: flags, } - + app.Run(os.Args) } ``` @@ -949,7 +945,7 @@ func main() { }, Action: func(ctx *cli.Context) error { if !ctx.Bool("ginger-crouton") { - return cli.Exit(Ginger croutons are not in the soup, 86) + return cli.Exit("Ginger croutons are not in the soup", 86) } return nil }, @@ -986,7 +982,7 @@ import ( "fmt" "log" "os" - + "github.com/urfave/cli/v2" ) diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index 41602ad..6f66aa3 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -66,10 +66,10 @@ func (x *jsonSource) Int(name string) (int, error) { return 0, fmt.Errorf("unexpected type %T for %q", i, name) case int: return v, nil - case float64: - return int(v), nil case float32: return int(v), nil + case float64: + return int(v), nil } } @@ -203,5 +203,6 @@ func jsonGetValue(key string, m map[string]interface{}) (interface{}, error) { type jsonSource struct { file string - deserialized map[string]interface{} + deserialized map[string]interface { + } } diff --git a/altsrc/toml_command_test.go b/altsrc/toml_command_test.go index 84558aa..9c96dce 100644 --- a/altsrc/toml_command_test.go +++ b/altsrc/toml_command_test.go @@ -241,7 +241,7 @@ func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) { } func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) { - app := (&cli.App{}) + app := &cli.App{} set := flag.NewFlagSet("test", 0) _ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) defer os.Remove("current.toml") diff --git a/app.go b/app.go index f0c6ac9..767b1f1 100644 --- a/app.go +++ b/app.go @@ -11,16 +11,14 @@ import ( "time" ) -var ( - changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" - appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) - - contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." - - errInvalidActionType = NewExitError("ERROR invalid Action type. "+ - fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ - fmt.Sprintf("See %s", appActionDeprecationURL), 2) -) +//var ( +// changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" +// appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) +// contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." +// errInvalidActionType = NewExitError("ERROR invalid Action type. "+ +// fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ +// fmt.Sprintf("See %s", appActionDeprecationURL), 2) +//) // App is the main structure of a cli application. It is recommended that // an app be created with the cli.NewApp() function @@ -534,28 +532,3 @@ func (a *Author) String() string { return fmt.Sprintf("%v%v", a.Name, e) } - -// DefaultAppComplete returns an ActionFunc to run a default command if non were passed. -// Usage: `app.Action = cli.DefaultCommand("command")` -func DefaultCommand(name string) ActionFunc { - return func(ctx *Context) error { - return ctx.App.Command(name).Run(ctx) - } -} - -// HandleAction attempts to figure out which Action signature was used. If -// it's an ActionFunc or a func with the legacy signature for Action, the func -// is run! -func HandleAction(action interface{}, context *Context) (err error) { - switch a := action.(type) { - case ActionFunc: - return a(context) - case func(*Context) error: - return a(context) - case func(*Context): // deprecated function signature - a(context) - return nil - } - - return errInvalidActionType -} diff --git a/app_test.go b/app_test.go index 2d1d656..7a73724 100644 --- a/app_test.go +++ b/app_test.go @@ -445,63 +445,6 @@ func TestApp_Setup_defaultsWriter(t *testing.T) { expect(t, app.Writer, os.Stdout) } - -//func TestApp_CommandWithArgBeforeFlags(t *testing.T) { -// var parsedOption, firstArg string -// -// app := NewApp() -// command := &Command{ -// Name: "cmd", -// Flags: []Flag{ -// &StringFlag{Name: "option", Value: "", Usage: "some option"}, -// }, -// Action: func(c *Context) error { -// parsedOption = c.String("option") -// firstArg = c.Args().First() -// return nil -// }, -// } -// app.Commands = []*Command{command} -// -// _ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) -// -// expect(t, parsedOption, "my-option") -// expect(t, firstArg, "my-arg") -//} -// -//func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) { -// var parsedOption, parsedSecondOption, firstArg string -// var parsedBool, parsedSecondBool bool -// -// app := NewApp() -// command := &Command{ -// Name: "cmd", -// Flags: []Flag{ -// &StringFlag{Name: "option", Value: "", Usage: "some option"}, -// &StringFlag{Name: "secondOption", Value: "", Usage: "another option"}, -// &BoolFlag{Name: "boolflag", Usage: "some bool"}, -// &BoolFlag{Name: "b", Usage: "another bool"}, -// }, -// Action: func(c *Context) error { -// parsedOption = c.String("option") -// parsedSecondOption = c.String("secondOption") -// parsedBool = c.Bool("boolflag") -// parsedSecondBool = c.Bool("b") -// firstArg = c.Args().First() -// return nil -// }, -// } -// app.Commands = []*Command{command} -// -// _ = app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"}) -// -// expect(t, parsedOption, "my-option") -// expect(t, parsedSecondOption, "fancy-option") -// expect(t, parsedBool, true) -// expect(t, parsedSecondBool, true) -// expect(t, firstArg, "my-arg") -//} - func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *Context @@ -2073,23 +2016,6 @@ func TestHandleExitCoder_Custom(t *testing.T) { } } -func TestHandleAction_WithUnknownPanic(t *testing.T) { - defer func() { refute(t, recover(), nil) }() - - var fn ActionFunc - - app := NewApp() - app.Action = func(ctx *Context) error { - _ = fn(ctx) - return nil - } - fs, err := flagSet(app.Name, app.Flags) - if err != nil { - t.Errorf("error creating FlagSet: %s", err) - } - _ = HandleAction(app.Action, NewContext(app, fs, nil)) -} - func TestShellCompletionForIncompleteFlags(t *testing.T) { app := &App{ Flags: []Flag{ diff --git a/appveyor.yml b/appveyor.yml index 4c3d669..1f30f3b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,4 +26,3 @@ build_script: - go run build.go vet - go run build.go test - go run build.go gfmrun - diff --git a/command.go b/command.go index af86824..3a1f21b 100644 --- a/command.go +++ b/command.go @@ -184,10 +184,6 @@ func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { return set, set.Parse(append([]string{"--"}, args.Tail()...)) } - //if !c.SkipArgReorder { - // args = reorderArgs(args) - //} - set, err := parseIter(c, args.Tail()) if err != nil { return nil, err diff --git a/command_test.go b/command_test.go index 5d91031..25e1ca5 100644 --- a/command_test.go +++ b/command_test.go @@ -28,7 +28,7 @@ func TestCommandFlagParsing(t *testing.T) { for _, c := range cases { app := &App{Writer: ioutil.Discard} set := flag.NewFlagSet("test", 0) - set.Parse(c.testArgs) + _ = set.Parse(c.testArgs) context := NewContext(app, set, nil) diff --git a/context.go b/context.go index 66dcdd6..70af324 100644 --- a/context.go +++ b/context.go @@ -74,40 +74,11 @@ func (c *Context) IsSet(name string) bool { } } - // XXX hack to support IsSet for flags with EnvVar - // - // There isn't an easy way to do this with the current implementation since - // whether a flag was set via an environment variable is very difficult to - // determine here. Instead, we intend to introduce a backwards incompatible - // change in version 2 to add `IsSet` to the Flag interface to push the - // responsibility closer to where the information required to determine - // whether a flag is set by non-standard means such as environment - // variables is available. - // - // See https://github.com/urfave/cli/issues/294 for additional discussion f := lookupFlag(name, c) if f == nil { 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 - return f.IsSet() } @@ -132,30 +103,6 @@ func (c *Context) FlagNames() []string { return names } -// FlagNames returns a slice of flag names used in this context. -//func (c *Context) FlagNames() (names []string) { -// for _, f := range c.Command.Flags { -// name := strings.Split(f.GetName(), ",")[0] -// if name == "help" { -// continue -// } -// names = append(names, name) -// } -// return -//} - -// GlobalFlagNames returns a slice of global flag names used by the app. -//func (c *Context) GlobalFlagNames() (names []string) { -// for _, f := range c.App.Flags { -// name := strings.Split(f.GetName(), ",")[0] -// if name == "help" || name == "version" { -// continue -// } -// names = append(names, name) -// } -// return names -//} - // Lineage returns *this* context and all of its ancestor contexts in order from // child to parent func (c *Context) Lineage() []*Context { @@ -310,6 +257,7 @@ func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { var flagPresent bool var flagName string + for _, key := range f.Names() { if len(key) > 1 { flagName = key diff --git a/context_test.go b/context_test.go index fef4945..081a8c4 100644 --- a/context_test.go +++ b/context_test.go @@ -3,12 +3,9 @@ package cli import ( "context" "flag" - "sort" - "os" "strings" - "testing" "time" ) @@ -462,6 +459,7 @@ func TestCheckRequiredFlags(t *testing.T) { parseInput: []string{"-n", "asd", "-n", "qwe"}, }, } + for _, test := range tdata { t.Run(test.testCase, func(t *testing.T) { // setup diff --git a/flag.go b/flag.go index 701ef31..fac1172 100644 --- a/flag.go +++ b/flag.go @@ -314,7 +314,7 @@ func stringifyIntSliceFlag(f *IntSliceFlag) string { var defaultVals []string if f.Value != nil && len(f.Value.Value()) > 0 { for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + defaultVals = append(defaultVals, strconv.Itoa(i)) } } @@ -346,7 +346,6 @@ func stringifyFloat64SliceFlag(f *Float64SliceFlag) string { func stringifyStringSliceFlag(f *StringSliceFlag) string { var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { for _, s := range f.Value.Value() { if len(s) > 0 { diff --git a/flag_int64.go b/flag_int64.go index 1f4cefd..c979119 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -80,7 +80,6 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { } set.Int64(name, f.Value, f.Usage) } - return nil } diff --git a/flag_int_slice.go b/flag_int_slice.go index d4f934e..9388978 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -50,6 +50,7 @@ func (i *IntSlice) Set(value string) error { } i.slice = append(i.slice, int(tmp)) + return nil } diff --git a/flag_string_slice.go b/flag_string_slice.go index a3a263b..0835833 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -132,7 +132,6 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { } set.Var(f.Value, name, f.Usage) } - return nil } diff --git a/flag_test.go b/flag_test.go index cfc5ca8..b4b2739 100644 --- a/flag_test.go +++ b/flag_test.go @@ -346,7 +346,6 @@ var stringSliceFlagTests = []struct { value *StringSlice expected string }{ - {"foo", nil, NewStringSlice(""), "--foo value\t"}, {"f", nil, NewStringSlice(""), "-f value\t"}, {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, @@ -1572,7 +1571,6 @@ func TestParseGenericFromEnv(t *testing.T) { os.Clearenv() _ = os.Setenv("APP_SERVE", "20,30") _ = (&App{ - Flags: []Flag{ &GenericFlag{ Name: "serve", diff --git a/help.go b/help.go index 3323817..2940e07 100644 --- a/help.go +++ b/help.go @@ -243,6 +243,7 @@ func ShowCommandCompletions(ctx *Context, command string) { DefaultCompleteWithFlags(c)(ctx) } } + } func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) { diff --git a/template.go b/template.go index 558ab50..78e182f 100644 --- a/template.go +++ b/template.go @@ -62,9 +62,10 @@ USAGE: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} -{{end}}{{if .VisibleFlags}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + OPTIONS: {{range .VisibleFlags}}{{.}} {{end}}{{end}} From f12b8ca4e52c0a13dd0b6a3e7d6fd2a26a3502aa Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Mon, 16 Sep 2019 12:17:30 +0530 Subject: [PATCH 142/158] Remove global method from Int64Slice Flag --- flag_int64_slice.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 5aa5e2a..41aa066 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -148,15 +148,6 @@ func (c *Context) Int64Slice(name string) []int64 { return lookupInt64Slice(name, c.flagSet) } -// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns -// nil if not found -func (c *Context) GlobalInt64Slice(name string) []int64 { - 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 { From 7d46b6d7f12151c317ff3fa2e980d7fd12046830 Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Fri, 13 Sep 2019 05:30:07 -0400 Subject: [PATCH 143/158] Avoid panic for missing flag value Currently, in cases where a flag value is required but not passed and short-option handling is enabled, a panic will occur due to a nil pointer dereference. This prevents that situation from occurring, instead propagating the appropriate error. --- app.go | 8 +++---- app_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++------- command.go | 12 +++++----- command_test.go | 43 +++++++++++++++++++--------------- parse.go | 22 ++++++++++-------- 5 files changed, 99 insertions(+), 47 deletions(-) diff --git a/app.go b/app.go index 3300cc5..d657bc4 100644 --- a/app.go +++ b/app.go @@ -218,12 +218,12 @@ func (a *App) Run(arguments []string) (err error) { // always appends the completion flag at the end of the command shellComplete, arguments := checkShellCompleteFlag(a, arguments) - _, err = a.newFlagSet() + set, err := a.newFlagSet() if err != nil { return err } - set, err := parseIter(a, arguments[1:]) + err = parseIter(set, a, arguments[1:]) nerr := normalizeFlags(a.Flags, set) context := NewContext(a, set, nil) if nerr != nil { @@ -344,12 +344,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } a.Commands = newCmds - _, err = a.newFlagSet() + set, err := a.newFlagSet() if err != nil { return err } - set, err := parseIter(a, ctx.Args().Tail()) + err = parseIter(set, a, ctx.Args().Tail()) nerr := normalizeFlags(a.Flags, set) context := NewContext(a, set, ctx) diff --git a/app_test.go b/app_test.go index 7a73724..fb82457 100644 --- a/app_test.go +++ b/app_test.go @@ -239,11 +239,11 @@ func ExampleApp_Run_bashComplete_withShortFlag() { app.EnableBashCompletion = true app.Flags = []Flag{ &IntFlag{ - Name: "other", + Name: "other", Aliases: []string{"o"}, }, &StringFlag{ - Name: "xyz", + Name: "xyz", Aliases: []string{"x"}, }, } @@ -268,11 +268,11 @@ func ExampleApp_Run_bashComplete_withLongFlag() { app.EnableBashCompletion = true app.Flags = []Flag{ &IntFlag{ - Name: "other", + Name: "other", Aliases: []string{"o"}, }, &StringFlag{ - Name: "xyz", + Name: "xyz", Aliases: []string{"x"}, }, &StringFlag{ @@ -296,11 +296,11 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() { app.EnableBashCompletion = true app.Flags = []Flag{ &IntFlag{ - Name: "int-flag", + Name: "int-flag", Aliases: []string{"i"}, }, &StringFlag{ - Name: "string", + Name: "string", Aliases: []string{"s"}, }, &StringFlag{ @@ -326,7 +326,7 @@ func ExampleApp_Run_bashComplete() { os.Args = []string{"greet", "--generate-bash-completion"} app := &App{ - Name: "greet", + Name: "greet", EnableBashCompletion: true, Commands: []*Command{ { @@ -638,6 +638,17 @@ func TestApp_UseShortOptionHandling(t *testing.T) { expect(t, name, expected) } +func TestApp_UseShortOptionHandling_missing_value(t *testing.T) { + app := NewApp() + app.UseShortOptionHandling = true + app.Flags = []Flag{ + &StringFlag{Name: "name", Aliases: []string{"n"}}, + } + + err := app.Run([]string{"", "-n"}) + expect(t, err, errors.New("flag needs an argument: -n")) +} + func TestApp_UseShortOptionHandlingCommand(t *testing.T) { var one, two bool var name string @@ -667,6 +678,21 @@ func TestApp_UseShortOptionHandlingCommand(t *testing.T) { expect(t, name, expected) } +func TestApp_UseShortOptionHandlingCommand_missing_value(t *testing.T) { + app := NewApp() + app.UseShortOptionHandling = true + command := &Command{ + Name: "cmd", + Flags: []Flag{ + &StringFlag{Name: "name", Aliases: []string{"n"}}, + }, + } + app.Commands = []*Command{command} + + err := app.Run([]string{"", "cmd", "-n"}) + expect(t, err, errors.New("flag needs an argument: -n")) +} + func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { var one, two bool var name string @@ -692,7 +718,7 @@ func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { }, } command.Subcommands = []*Command{subCommand} - app.Commands = []*Command{command} + app.Commands = []*Command{command} err := app.Run([]string{"", "cmd", "sub", "-on", expected}) expect(t, err, nil) @@ -701,6 +727,25 @@ func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) { expect(t, name, expected) } +func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) { + app := NewApp() + app.UseShortOptionHandling = true + command := &Command{ + Name: "cmd", + } + subCommand := &Command{ + Name: "sub", + Flags: []Flag{ + &StringFlag{Name: "name", Aliases: []string{"n"}}, + }, + } + command.Subcommands = []*Command{subCommand} + app.Commands = []*Command{command} + + err := app.Run([]string{"", "cmd", "sub", "-n"}) + expect(t, err, errors.New("flag needs an argument: -n")) +} + func TestApp_Float64Flag(t *testing.T) { var meters float64 diff --git a/command.go b/command.go index 3a1f21b..fcaafb6 100644 --- a/command.go +++ b/command.go @@ -175,16 +175,16 @@ func (c *Command) useShortOptionHandling() bool { } func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { - if c.SkipFlagParsing { - set, err := c.newFlagSet() - if err != nil { - return nil, err - } + set, err := c.newFlagSet() + if err != nil { + return nil, err + } + if c.SkipFlagParsing { return set, set.Parse(append([]string{"--"}, args.Tail()...)) } - set, err := parseIter(c, args.Tail()) + err = parseIter(set, c, args.Tail()) if err != nil { return nil, err } diff --git a/command_test.go b/command_test.go index 25e1ca5..f43b4ae 100644 --- a/command_test.go +++ b/command_test.go @@ -52,7 +52,7 @@ func TestParseAndRunShortOpts(t *testing.T) { cases := []struct { testArgs args expectedErr error - expectedArgs *args + expectedArgs Args }{ {testArgs: args{"foo", "test", "-a"}, expectedErr: nil, expectedArgs: &args{}}, {testArgs: args{"foo", "test", "-c", "arg1", "arg2"}, expectedErr: nil, expectedArgs: &args{"arg1", "arg2"}}, @@ -61,28 +61,33 @@ func TestParseAndRunShortOpts(t *testing.T) { {testArgs: args{"foo", "test", "-af"}, expectedErr: nil, expectedArgs: &args{}}, {testArgs: args{"foo", "test", "-cf"}, expectedErr: nil, expectedArgs: &args{}}, {testArgs: args{"foo", "test", "-acf"}, expectedErr: nil, expectedArgs: &args{}}, - {testArgs: args{"foo", "test", "-invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-invalid"}, expectedErr: errors.New("flag provided but not defined: -invalid"), expectedArgs: nil}, {testArgs: args{"foo", "test", "-acf", "arg1", "-invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "-invalid"}}, - } - - var args Args - cmd := &Command{ - Name: "test", - Usage: "this is for testing", - Description: "testing", - Action: func(c *Context) error { - args = c.Args() - return nil - }, - UseShortOptionHandling: true, - Flags: []Flag{ - &BoolFlag{Name: "abc", Aliases: []string{"a"}}, - &BoolFlag{Name: "cde", Aliases: []string{"c"}}, - &BoolFlag{Name: "fgh", Aliases: []string{"f"}}, - }, + {testArgs: args{"foo", "test", "-acfi", "not-arg", "arg1", "-invalid"}, expectedErr: nil, expectedArgs: &args{"arg1", "-invalid"}}, + {testArgs: args{"foo", "test", "-i", "ivalue"}, expectedErr: nil, expectedArgs: &args{}}, + {testArgs: args{"foo", "test", "-i", "ivalue", "arg1"}, expectedErr: nil, expectedArgs: &args{"arg1"}}, + {testArgs: args{"foo", "test", "-i"}, expectedErr: errors.New("flag needs an argument: -i"), expectedArgs: nil}, } for _, c := range cases { + var args Args + cmd := &Command{ + Name: "test", + Usage: "this is for testing", + Description: "testing", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + UseShortOptionHandling: true, + Flags: []Flag{ + &BoolFlag{Name: "abc", Aliases: []string{"a"}}, + &BoolFlag{Name: "cde", Aliases: []string{"c"}}, + &BoolFlag{Name: "fgh", Aliases: []string{"f"}}, + &StringFlag{Name: "ijk", Aliases:[]string{"i"}}, + }, + } + app := NewApp() app.Commands = []*Command{cmd} diff --git a/parse.go b/parse.go index 865accf..2c2005c 100644 --- a/parse.go +++ b/parse.go @@ -14,22 +14,17 @@ type iterativeParser interface { // iteratively catch parsing errors. This way we achieve LR parsing without // transforming any arguments. Otherwise, there is no way we can discriminate // combined short options from common arguments that should be left untouched. -func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) { +func parseIter(set *flag.FlagSet, ip iterativeParser, args []string) error { for { - set, err := ip.newFlagSet() - if err != nil { - return nil, err - } - - err = set.Parse(args) + err := set.Parse(args) if !ip.useShortOptionHandling() || err == nil { - return set, err + return err } errStr := err.Error() trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: ") if errStr == trimmed { - return nil, err + return err } // regenerate the initial args with the split short opts @@ -42,7 +37,7 @@ func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) { shortOpts := splitShortOptions(set, trimmed) if len(shortOpts) == 1 { - return nil, err + return err } // add each short option and all remaining arguments @@ -50,6 +45,13 @@ func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) { newArgs = append(newArgs, args[i+1:]...) args = newArgs } + + // Since custom parsing failed, replace the flag set before retrying + newSet, err := ip.newFlagSet() + if err != nil { + return err + } + *set = *newSet } } From 8043478cfd84f2c665efb971c1845d7cc0bf9ab4 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Wed, 18 Sep 2019 09:56:38 +0530 Subject: [PATCH 144/158] Remove final conflicts --- README.md | 24 +----------------------- appveyor.yml | 8 -------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/README.md b/README.md index 2a928a6..652049b 100644 --- a/README.md +++ b/README.md @@ -735,7 +735,6 @@ func main() { #### Default Values for help output -<<<<<<< HEAD Sometimes it's useful to specify a flag's default help-text value within the flag declaration. This can be useful if the default value for a flag is a computed value. The default value can be set via the `DefaultText` struct field. For example this: @@ -765,9 +764,7 @@ func main() { }, }, } - -======= ->>>>>>> master + err := app.Run(os.Args) if err != nil { log.Fatal(err) @@ -775,15 +772,12 @@ func main() { } ``` -<<<<<<< HEAD Will result in help output like: ``` --port value Use a randomized port (default: random) ``` -======= ->>>>>>> master #### Precedence The precedence for flag value sources is as follows (highest to lowest): @@ -885,7 +879,6 @@ import ( ) func main() { -<<<<<<< HEAD app := &cli.App{ Commands: []*cli.Command{ { @@ -899,21 +892,6 @@ func main() { Name: "remove", Category: "template", }, -======= - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "Template actions", - }, - { - Name: "remove", - Category: "Template actions", ->>>>>>> master }, } diff --git a/appveyor.yml b/appveyor.yml index 3447d40..1f30f3b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,23 +12,15 @@ cache: environment: GOPATH: C:\gopath GOVERSION: 1.11.x -<<<<<<< HEAD GO111MODULE: on GOPROXY: https://proxy.golang.org -======= ->>>>>>> master install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env -<<<<<<< HEAD - go get github.com/urfave/gfmrun/cmd/gfmrun - go mod vendor -======= - - go get github.com/urfave/gfmrun/... - - go get -v -t ./... ->>>>>>> master build_script: - go run build.go vet From be2255c1fb1e4e9c9566cf1fb98b4a4f9bf6edd8 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Wed, 18 Sep 2019 10:17:21 +0530 Subject: [PATCH 145/158] Fix failing build --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 652049b..36d3daf 100644 --- a/README.md +++ b/README.md @@ -707,7 +707,6 @@ package main import ( "fmt" - "log" "os" "github.com/urfave/cli/v2" From cad413652ca51aab42107693b2ca023361f8d89d Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Wed, 9 Oct 2019 12:59:24 +0530 Subject: [PATCH 146/158] remove old makefile --- GNUmakefile | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 GNUmakefile diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index 4543b19..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,37 +0,0 @@ -default: test - -deps: - go get golang.org/x/tools/cmd/goimports || true - go get github.com/urfave/gfmrun/... || true - go list ./... \ - | xargs go list -f '{{ join .Deps "\n" }}{{ printf "\n" }}{{ join .TestImports "\n" }}' \ - | grep -v github.com/urfave/cli \ - | xargs go get - @if [ ! -f node_modules/.bin/markdown-toc ]; then \ - npm install markdown-toc ; \ - fi - -gen: deps - ./runtests gen - -vet: - ./runtests vet - -gfmrun: - ./runtests gfmrun - -v1-to-v2: - ./cli-v1-to-v2 --selftest - -migrations: - ./runtests migrations - -toc: - ./runtests toc - -test: deps - ./runtests test - -all: gen vet test gfmrun v1-to-v2 migrations toc - -.PHONY: default gen vet test gfmrun migrations toc v1-to-v2 deps all From 16638666734a6440ff576c4fc3142e29ae5266c8 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Wed, 9 Oct 2019 13:04:17 +0530 Subject: [PATCH 147/158] Remove go mod vendor from appveyor --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1f30f3b..598ff4b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,7 +20,6 @@ install: - go version - go env - go get github.com/urfave/gfmrun/cmd/gfmrun - - go mod vendor build_script: - go run build.go vet From d8a42a7753ba0378fc27ddd546a1742e63e37830 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sat, 19 Oct 2019 09:54:59 +0530 Subject: [PATCH 148/158] add goflags readonly --- .travis.yml | 3 +-- appveyor.yml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e500b38..4c6a932 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,7 @@ os: - osx env: - GO111MODULE=on - GOPROXY=https://proxy.golang.org + GO111MODULE=on GOPROXY=https://proxy.golang.org GOFLAGS=-mod=readonly cache: directories: diff --git a/appveyor.yml b/appveyor.yml index 598ff4b..ca41f62 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,7 @@ environment: GOVERSION: 1.11.x GO111MODULE: on GOPROXY: https://proxy.golang.org + GOFLAGS: -mod=readonly install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% From 5f97906045a35cd1489e0e69794f361f328ae11d Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sat, 19 Oct 2019 09:59:15 +0530 Subject: [PATCH 149/158] remove goflags, add go mod tidy --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index ca41f62..5ec8a04 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,13 +14,14 @@ environment: GOVERSION: 1.11.x GO111MODULE: on GOPROXY: https://proxy.golang.org - GOFLAGS: -mod=readonly install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env - go get github.com/urfave/gfmrun/cmd/gfmrun + - go get golang.org/x/tools/cmd/goimports + - go mod tidy build_script: - go run build.go vet From 3415a1aade2c59339e463bfbed9862c30f8acae3 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sat, 19 Oct 2019 10:03:24 +0530 Subject: [PATCH 150/158] remove goflags from travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4c6a932..334408c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ os: - osx env: - GO111MODULE=on GOPROXY=https://proxy.golang.org GOFLAGS=-mod=readonly + GO111MODULE=on GOPROXY=https://proxy.golang.org cache: directories: From 3bd997859c0c6df39db43ccef6f17582bd2db3ac Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sat, 19 Oct 2019 10:13:03 +0530 Subject: [PATCH 151/158] make categories private --- app.go | 12 ++++++------ app_test.go | 4 ++-- category.go | 2 +- command.go | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app.go b/app.go index d657bc4..d03ec5d 100644 --- a/app.go +++ b/app.go @@ -47,8 +47,8 @@ type App struct { HideHelp bool // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool - // Categories contains the categorized commands and is populated on app startup - Categories CommandCategories + // categories contains the categorized commands and is populated on app startup + categories CommandCategories // An action to execute when the shell completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -182,11 +182,11 @@ func (a *App) Setup() { a.appendFlag(VersionFlag) } - a.Categories = newCommandCategories() + a.categories = newCommandCategories() for _, command := range a.Commands { - a.Categories.AddCommand(command.Category, command) + a.categories.AddCommand(command.Category, command) } - sort.Sort(a.Categories.(*commandCategories)) + sort.Sort(a.categories.(*commandCategories)) if a.Metadata == nil { a.Metadata = make(map[string]interface{}) @@ -449,7 +449,7 @@ func (a *App) Command(name string) *Command { // Hidden=false func (a *App) VisibleCategories() []CommandCategory { ret := []CommandCategory{} - for _, category := range a.Categories.Categories() { + for _, category := range a.categories.Categories() { if visible := func() CommandCategory { if len(category.VisibleCommands()) > 0 { return category diff --git a/app_test.go b/app_test.go index fb82457..9d33745 100644 --- a/app_test.go +++ b/app_test.go @@ -1719,8 +1719,8 @@ func TestApp_Run_Categories(t *testing.T) { }, }) - if !reflect.DeepEqual(app.Categories, &expect) { - t.Fatalf("expected categories %#v, to equal %#v", app.Categories, &expect) + if !reflect.DeepEqual(app.categories, &expect) { + t.Fatalf("expected categories %#v, to equal %#v", app.categories, &expect) } output := buf.String() diff --git a/category.go b/category.go index fa56845..0defa31 100644 --- a/category.go +++ b/category.go @@ -3,7 +3,7 @@ package cli type CommandCategories interface { // AddCommand adds a command to a category, creating a new category if necessary. AddCommand(category string, command *Command) - // Categories returns a copy of the category slice + // categories returns a copy of the category slice Categories() []CommandCategory } diff --git a/command.go b/command.go index fcaafb6..a579fdb 100644 --- a/command.go +++ b/command.go @@ -245,12 +245,12 @@ func (c *Command) startApp(ctx *Context) error { app.ExitErrHandler = ctx.App.ExitErrHandler app.UseShortOptionHandling = ctx.App.UseShortOptionHandling - app.Categories = newCommandCategories() + app.categories = newCommandCategories() for _, command := range c.Subcommands { - app.Categories.AddCommand(command.Category, command) + app.categories.AddCommand(command.Category, command) } - sort.Sort(app.Categories.(*commandCategories)) + sort.Sort(app.categories.(*commandCategories)) // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion From 109b320c057fee8e57358154f439bcd1fa256bae Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Mon, 21 Oct 2019 18:54:21 +0530 Subject: [PATCH 152/158] remove redundant conversion --- category.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/category.go b/category.go index 0defa31..d9e73a0 100644 --- a/category.go +++ b/category.go @@ -15,7 +15,7 @@ func newCommandCategories() CommandCategories { } func (c *commandCategories) Less(i, j int) bool { - return lexicographicLess((*c)[i].Name(), (*c)[j].Name() ) + return lexicographicLess((*c)[i].Name(), (*c)[j].Name()) } func (c *commandCategories) Len() int { @@ -33,8 +33,8 @@ func (c *commandCategories) AddCommand(category string, command *Command) { return } } - newVal := commandCategories(append(*c, - &commandCategory{name: category, commands: []*Command{command}})) + newVal := append(*c, + &commandCategory{name: category, commands: []*Command{command}}) *c = newVal } From 19756499b94c1bc7cbe84f7dd209c33bf8b7a534 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 23 Oct 2019 22:19:35 -0700 Subject: [PATCH 153/158] fix spacing --- docs/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1a4b1d3..252df89 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -145,8 +145,11 @@ `cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag` interface to be used. + ## [1.19.1] - 2016-11-21 + ### Fixed + - Fixes regression introduced in 1.19.0 where using an `ActionFunc` as the `Action` for a command would cause it to error rather than calling the function. Should not have a affected declarative cases using `func(c From 8576adbf98b940d1c53facd8bc44a38999ba0a9b Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 23 Oct 2019 22:22:04 -0700 Subject: [PATCH 154/158] clip contributing guidelines --- docs/v2/manual.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 4155756..d394308 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -28,7 +28,6 @@ cli v2 manual * [Version Flag](#version-flag) + [Customization](#customization-2) * [Full API Example](#full-api-example) -- [Contribution Guidelines](#contribution-guidelines) @@ -1488,7 +1487,3 @@ func wopAction(c *cli.Context) error { return nil } ``` - -## Contribution Guidelines - -See [./CONTRIBUTING.md](./CONTRIBUTING.md) From c4b24a1c87a4c529455efea10046c72d836888d2 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 23 Oct 2019 22:23:50 -0700 Subject: [PATCH 155/158] add v2 doc tests --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d291a82..9f61c62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,8 @@ script: - go run build.go test - go run build.go gfmrun docs/v1/manual.md - go run build.go toc docs/v1/manual.md + - go run build.go gfmrun docs/v2/manual.md + - go run build.go toc docs/v2/manual.md after_success: - bash <(curl -s https://codecov.io/bash) From 3dce56ab0df30e1b9ce69a6f49ca1bcfe3ca68f1 Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 23 Oct 2019 22:26:34 -0700 Subject: [PATCH 156/158] remove v1 regression test --- app_regression_test.go | 59 ------------------------------------------ 1 file changed, 59 deletions(-) delete mode 100644 app_regression_test.go diff --git a/app_regression_test.go b/app_regression_test.go deleted file mode 100644 index 3c8681b..0000000 --- a/app_regression_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package cli - -import ( - "testing" -) - -// TestRegression tests a regression that was merged between versions 1.20.0 and 1.21.0 -// The included app.Run line worked in 1.20.0, and then was broken in 1.21.0. -// Relevant PR: https://github.com/urfave/cli/pull/872 -func TestVersionOneTwoOneRegression(t *testing.T) { - testData := []struct { - testCase string - appRunInput []string - skipArgReorder bool - }{ - { - testCase: "with_dash_dash", - appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "--", "docker", "image", "ls", "--no-trunc"}, - }, - { - testCase: "with_dash_dash_and_skip_reorder", - appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "--", "docker", "image", "ls", "--no-trunc"}, - skipArgReorder: true, - }, - { - testCase: "without_dash_dash", - appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}, - }, - { - testCase: "without_dash_dash_and_skip_reorder", - appRunInput: []string{"cli", "command", "--flagone", "flagvalue", "docker", "image", "ls", "--no-trunc"}, - skipArgReorder: true, - }, - } - for _, test := range testData { - t.Run(test.testCase, func(t *testing.T) { - // setup - app := NewApp() - app.Commands = []Command{{ - Name: "command", - SkipArgReorder: test.skipArgReorder, - Flags: []Flag{ - StringFlag{ - Name: "flagone", - }, - }, - Action: func(c *Context) error { return nil }, - }} - - // logic under test - err := app.Run(test.appRunInput) - - // assertions - if err != nil { - t.Errorf("did not expected an error, but there was one: %s", err) - } - }) - } -} From 434d6cba03d8373464c9701e2351960885353f9b Mon Sep 17 00:00:00 2001 From: Lynn Cyrin Date: Wed, 23 Oct 2019 22:29:10 -0700 Subject: [PATCH 157/158] remove old tests --- command_test.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/command_test.go b/command_test.go index e675cdc..7c55f13 100644 --- a/command_test.go +++ b/command_test.go @@ -18,14 +18,6 @@ func TestCommandFlagParsing(t *testing.T) { }{ // Test normal "not ignoring flags" flow {testArgs: []string{"test-cmd", "-break", "blah", "blah"}, skipFlagParsing: false, useShortOptionHandling: false, expectedErr: errors.New("flag provided but not defined: -break")}, - {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, nil, false}, - // Test no arg reorder - {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false}, - {[]string{"test-cmd", "blah", "blah", "-break", "ls", "-l"}, false, true, nil, true}, - {[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags - {[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg - {[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg - {[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling {testArgs: []string{"test-cmd", "blah", "blah"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing without any args that look like flags {testArgs: []string{"test-cmd", "blah", "-break"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing with random flag arg {testArgs: []string{"test-cmd", "blah", "-help"}, skipFlagParsing: true, useShortOptionHandling: false, expectedErr: nil}, // Test SkipFlagParsing with "special" help flag arg From 84461bc1ff26662933f6f4aa07d65c77b9a82be8 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Fri, 8 Nov 2019 22:11:34 +0530 Subject: [PATCH 158/158] Update CHANGELOG.md update changelog --- docs/CHANGELOG.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 252df89..9302459 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -17,15 +17,6 @@ ### Removed - the ability to specify `&StringSlice{...string}` or `&IntSlice{...int}`. - To migrate to the new API, you may choose to run [the migrator - (python) script](./cli-v1-to-v2). -- The optimistic reordering of arguments and flags introduced by - https://github.com/urfave/cli/pull/36. This behavior only worked when - all arguments appeared before all flags, but caused [weird issues with boolean - flags](https://github.com/urfave/cli/issues/103) and [reordering of the - arguments](https://github.com/urfave/cli/issues/355) when the user - attempted to mix flags and arguments. Given the trade-offs we removed support - for this reordering. - adapter code for deprecated `Action` func signature - deprecated `App.Author`, `App.Email`, and `Command.ShortName` fields - All `Context.Global*` methods, as the non-global versions now traverse up