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 + }) +} 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