aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flag.go49
-rw-r--r--flag_test.go59
2 files changed, 108 insertions, 0 deletions
diff --git a/flag.go b/flag.go
index 9200b6f..d48c091 100644
--- a/flag.go
+++ b/flag.go
@@ -55,6 +55,53 @@ func FlagTSliceVar[T any](fs *flag.FlagSet, p *[]T, name string, values []T, usa
fs.Var(&flagValueSlice[T]{Parse: parse, Separator: sep, Values: p}, name, usage)
}
+// InitFlagSet initializes a flag.FlagSet by setting flags in the
+// following order: environment variables, then an arbitrary map, then
+// command line arguments.
+//
+// Note that InitFlagSet does not require the use of any of the Flag
+// functions defined in this package. Standard flags will work just as
+// well.
+func InitFlagSet(fs *flag.FlagSet, env []string, cfg map[string]string, args []string) (err error) {
+ var environ map[string]string
+ if env != nil {
+ environ = make(map[string]string, len(env))
+ for _, kv := range env {
+ if buf := strings.SplitN(kv, "=", 2); len(buf) == 2 {
+ environ[buf[0]] = buf[1]
+ continue
+ }
+ if val, ok := os.LookupEnv(kv); ok {
+ environ[kv] = val
+ }
+ }
+ }
+
+ fs.VisitAll(func(f *flag.Flag) {
+ if err != nil {
+ return
+ }
+
+ var next string
+ if val, found := environ[strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))]; found {
+ next = val
+ }
+ if val, found := cfg[f.Name]; found {
+ next = val
+ }
+ if next != "" {
+ err = f.Value.Set(next)
+ }
+ if f, ok := f.Value.(interface{ resetShouldAppend() }); ok {
+ f.resetShouldAppend()
+ }
+ })
+ if err == nil && !fs.Parsed() {
+ return fs.Parse(args)
+ }
+ return err
+}
+
// ParseString returns the string passed with no error set.
func ParseString(s string) (string, error) {
return s, nil
@@ -130,3 +177,5 @@ func (f *flagValueSlice[T]) String() string {
}
return fmt.Sprintf("%v", *f.Values)
}
+
+func (f *flagValueSlice[T]) resetShouldAppend() { f.shouldAppend = false }
diff --git a/flag_test.go b/flag_test.go
index ccbfadb..584a2c9 100644
--- a/flag_test.go
+++ b/flag_test.go
@@ -52,3 +52,62 @@ func TestFlagTSliceVar(s *testing.T) {
t.AssertErrorIs(nil, fs.Parse([]string{"-test=1", "-test=2", "-test=42,84"}))
t.AssertEqual([]int{1, 2, 42, 84}, fl)
}
+
+func TestInitFlagSet(s *testing.T) {
+ t := core.T{T: s}
+
+ for _, tc := range []struct {
+ name string
+ env []string
+ cfg map[string]string
+ args []string
+
+ expInt int
+ expIntSlice []int
+ expStr string
+ expErr error
+ }{
+ {
+ name: "ArgsOnly",
+ args: []string{"-int=42", "-int-slice=42,84"},
+
+ expInt: 42,
+ expIntSlice: []int{42, 84},
+ },
+ {
+ name: "EnvOnly",
+ env: []string{"INT=42", "INT_SLICE=42,84"},
+
+ expInt: 42,
+ expIntSlice: []int{42, 84},
+ },
+ {
+ name: "CfgOnly",
+ cfg: map[string]string{"int": "42", "int-slice": "42,84"},
+
+ expInt: 42,
+ expIntSlice: []int{42, 84},
+ },
+ {
+ name: "InOrder",
+ env: []string{"STRING=Hello World!"},
+ cfg: map[string]string{"string": "Hello Universe!", "int-slice": "42,84"},
+ args: []string{"-int=42", "-int-slice=21,42"},
+
+ expInt: 42,
+ expIntSlice: []int{21, 42},
+ expStr: "Hello Universe!",
+ },
+ } {
+ t.Run(tc.name, func(t *core.T) {
+ fs := flag.NewFlagSet("", flag.PanicOnError)
+ fi := fs.Int("int", 0, "")
+ fl := core.FlagTSlice(fs, "int-slice", nil, "", strconv.Atoi, ",")
+ fm := fs.String("string", "", "")
+ t.AssertErrorIs(tc.expErr, core.InitFlagSet(fs, tc.env, tc.cfg, tc.args))
+ t.AssertEqual(tc.expInt, *fi)
+ t.AssertEqual(tc.expIntSlice, *fl)
+ t.AssertEqual(tc.expStr, *fm)
+ })
+ }
+}