From 0bd5cdb506c953d3dbf1e82884f78f677132eace Mon Sep 17 00:00:00 2001 From: Justin Bastress Date: Fri, 29 Jun 2018 10:55:53 -0400 Subject: [PATCH] add fallbacks (#7/#9) --- fallback_test.go | 144 +++++++++++++++++++++++++++++++++++++++++++++++ group.go | 9 ++- multitag.go | 8 +++ 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 fallback_test.go diff --git a/fallback_test.go b/fallback_test.go new file mode 100644 index 0000000..ceaffa8 --- /dev/null +++ b/fallback_test.go @@ -0,0 +1,144 @@ +package flags + +import ( + "os" + "reflect" + "testing" + "time" +) + +// Test that when `long` is absent, it attempts to fall back to `json`, and +// when `env` is absent, it attempts to fall back to `long` (and to `json`) +func TestFallback(t *testing.T) { + type Options struct { + Int int `long:"int" json:"json-int" default:"1"` + Time time.Duration `json:"time" default:"1m"` + Map map[string]int `json:"map,omitempty" default:"a:1" env-delim:";"` + Slice []int `long:"slice" default:"1" default:"2" env:"OVERRIDE_SLICE" env-delim:","` + } + + var tests = []struct { + msg string + args []string + expected Options + env map[string]string + }{ + { + msg: "JSON override", + args: []string{}, + expected: Options{ + Int: 23, + Time: time.Minute * 3, + Map: map[string]int{"key1": 1}, + Slice: []int{3,4,5}, + }, + env: map[string]string{ + // since both `json` and `long` are present, `long` ("int") wins + "json-int": "4", + "int": "23", + "time": "3m", + "map": "key1:1", + // since both `env` and `long` are present, `env` ("OVERRIDE_SLICE") wins + "slice": "3,2,1", + "OVERRIDE_SLICE": "3,4,5", + }, + }, + { + msg: "no arguments, no env, expecting default values", + args: []string{}, + expected: Options{ + Int: 1, + Time: time.Minute, + Map: map[string]int{"a": 1}, + Slice: []int{1, 2}, + }, + }, + { + msg: "no arguments, env defaults, expecting env default values", + args: []string{}, + expected: Options{ + Int: 2, + Time: 2 * time.Minute, + Map: map[string]int{"a": 2, "b": 3}, + Slice: []int{4, 5, 6}, + }, + env: map[string]string{ + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", + "OVERRIDE_SLICE": "4,5,6", + }, + }, + { + msg: "non-zero value arguments, expecting overwritten arguments", + args: []string{"--int=3", "--time=3ms", "--map=c:3", "--slice=3", "--map=d:4", "--slice=1"}, + expected: Options{ + Int: 3, + Time: 3 * time.Millisecond, + Map: map[string]int{"c": 3,"d":4}, + Slice: []int{3,1}, + }, + env: map[string]string{ + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", + "OVERRIDE_SLICE": "4,5,6", + }, + }, + { + msg: "zero value arguments, expecting overwritten arguments", + args: []string{"--int=0", "--time=0ms", "--map=:0", "--slice=0"}, + expected: Options{ + Int: 0, + Time: 0, + Map: map[string]int{"": 0}, + Slice: []int{0}, + }, + env: map[string]string{ + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", + "OVERRIDE_SLICE": "4,5,6", + }, + }, + { + msg: "`long` used for env name even though `env` was present", + args: []string{"--int=0", "--time=0ms", "--map=:0", "--slice=0"}, + expected: Options{ + Int: 0, + Time: 0, + Map: map[string]int{"": 0}, + Slice: []int{0}, + }, + env: map[string]string{ + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", + "Slice": "4,5,6", + }, + }, + } + + oldEnv := EnvSnapshot() + defer oldEnv.Restore() + + for _, test := range tests { + var opts Options + oldEnv.Restore() + for envKey, envValue := range test.env { + os.Setenv(envKey, envValue) + } + _, _, _, err := ParseArgs(&opts, test.args) + if err != nil { + t.Fatalf("%s:\nUnexpected error: %v", test.msg, err) + } + + if opts.Slice == nil { + opts.Slice = []int{} + } + + if !reflect.DeepEqual(opts, test.expected) { + t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts) + } + } +} diff --git a/group.go b/group.go index 9e057ab..9ead968 100644 --- a/group.go +++ b/group.go @@ -234,6 +234,13 @@ func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, h longname := mtag.Get("long") shortname := mtag.Get("short") + // If `long` is not specified, try the JSON key + if longname == "" { + if j := mtag.Get("json"); j != "" { + // key = anything before a comma, if one is present + longname = strings.SplitN(j, ",", 2)[0] + } + } // Need at least either a short or long name if longname == "" && shortname == "" && mtag.Get("ini-name") == "" { continue @@ -268,7 +275,7 @@ func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, h ShortName: short, LongName: longname, Default: def, - EnvDefaultKey: mtag.Get("env"), + EnvDefaultKey: mtag.OptGet("env", longname), EnvDefaultDelim: mtag.Get("env-delim"), OptionalArgument: optional, OptionalValue: optionalValue, diff --git a/multitag.go b/multitag.go index 96bb1a3..3075d78 100644 --- a/multitag.go +++ b/multitag.go @@ -124,6 +124,14 @@ func (x *multiTag) Get(key string) string { return "" } +// OptGet returns Get(key), unless it is empty, when it returns def +func (x *multiTag) OptGet(key string, def string) string { + if ret := x.Get(key); ret != "" { + return ret + } + return def +} + func (x *multiTag) GetMany(key string) []string { c := x.cached() return c[key]