Repository: alecthomas/kong
Pull request #177 (2021-06-18 18:11:22)

Environment option
Generating environment variable names for flags.

```go
type Some {
    Value string
}

cli := struct {
    Some  Some `prefix:"some." embed:""`
}
kong.Parse(root, kong.Env("PREFIX"))
```

Generates:
``    --some.value -> PREFIX_SOME_VALUE
``



alecthomas: Awesome, thanks :)

Patch:
diff --git a/options.go b/options.go
index 98620c5..57a8f13 100644
--- a/options.go
+++ b/options.go
@@ -371,3 +371,56 @@ func ExpandPath(path string) string {
 	}
 	return abspath
 }
+
+func siftStrings(ss []string, filter func(s string) bool) []string {
+	i := 0
+	ss = append([]string(nil), ss...)
+	for _, s := range ss {
+		if filter(s) {
+			ss[i] = s
+			i++
+		}
+	}
+	return ss[0:i]
+}
+
+// DefaultEnvars option inits environment names for flags.
+// The name will not generate if tag "env" is "-".
+// Predefined environment variables are skipped.
+//
+// For example:
+//   --some.value -> PREFIX_SOME_VALUE
+func DefaultEnvars(prefix string) Option {
+	processFlag := func(flag *Flag) {
+		switch env := flag.Env; {
+		case flag.Name == "help":
+			return
+		case env == "-":
+			flag.Env = ""
+			return
+		case env != "":
+			return
+		}
+		replacer := strings.NewReplacer("-", "_", ".", "_")
+		names := append([]string{prefix}, camelCase(replacer.Replace(flag.Name))...)
+		names = siftStrings(names, func(s string) bool { return !(s == "_" || strings.TrimSpace(s) == "") })
+		name := strings.ToUpper(strings.Join(names, "_"))
+		flag.Env = name
+		flag.Value.Tag.Env = name
+	}
+
+	var processNode func(node *Node)
+	processNode = func(node *Node) {
+		for _, flag := range node.Flags {
+			processFlag(flag)
+		}
+		for _, node := range node.Children {
+			processNode(node)
+		}
+	}
+
+	return PostBuild(func(k *Kong) error {
+		processNode(k.Model.Node)
+		return nil
+	})
+}


Test patch:
diff --git a/resolver_test.go b/resolver_test.go
index f601544..8e44d24 100644
--- a/resolver_test.go
+++ b/resolver_test.go
@@ -26,10 +26,10 @@ func tempEnv(env envMap) func() {
 	}
 }
 
-func newEnvParser(t *testing.T, cli interface{}, env envMap) (*kong.Kong, func()) {
+func newEnvParser(t *testing.T, cli interface{}, env envMap, options ...kong.Option) (*kong.Kong, func()) {
 	t.Helper()
 	restoreEnv := tempEnv(env)
-	parser := mustNew(t, cli)
+	parser := mustNew(t, cli, options...)
 	return parser, restoreEnv
 }
 
@@ -92,6 +92,60 @@ func TestEnvarsWithDefault(t *testing.T) {
 	require.Equal(t, "moo", cli.Flag)
 }
 
+func TestEnv(t *testing.T) {
+	type Embed struct {
+		Flag string
+	}
+	type Cli struct {
+		One   Embed `prefix:"one-" embed:""`
+		Two   Embed `prefix:"two." embed:""`
+		Three Embed `prefix:"three_" embed:""`
+		Four  Embed `prefix:"four_" embed:""`
+		Five  bool
+		Six   bool `env:"-"`
+	}
+
+	var cli Cli
+
+	expected := Cli{
+		One:   Embed{Flag: "one"},
+		Two:   Embed{Flag: "two"},
+		Three: Embed{Flag: "three"},
+		Four:  Embed{Flag: "four"},
+		Five:  true,
+	}
+
+	// With the prefix
+	parser, unsetEnvs := newEnvParser(t, &cli, envMap{
+		"KONG_ONE_FLAG":   "one",
+		"KONG_TWO_FLAG":   "two",
+		"KONG_THREE_FLAG": "three",
+		"KONG_FOUR_FLAG":  "four",
+		"KONG_FIVE":       "true",
+		"KONG_SIX":        "true",
+	}, kong.DefaultEnvars("KONG"))
+	defer unsetEnvs()
+
+	_, err := parser.Parse(nil)
+	require.NoError(t, err)
+	require.Equal(t, expected, cli)
+
+	// Without the prefix
+	parser, unsetEnvs = newEnvParser(t, &cli, envMap{
+		"ONE_FLAG":   "one",
+		"TWO_FLAG":   "two",
+		"THREE_FLAG": "three",
+		"FOUR_FLAG":  "four",
+		"FIVE":       "true",
+		"SIX":        "true",
+	}, kong.DefaultEnvars(""))
+	defer unsetEnvs()
+
+	_, err = parser.Parse(nil)
+	require.NoError(t, err)
+	require.Equal(t, expected, cli)
+}
+
 func TestJSONBasic(t *testing.T) {
 	var cli struct {
 		String          string