Load environment variables as a resolver (#480)
This commit is contained in:
@@ -91,7 +91,7 @@ func New(grammar any, options ...Option) (*Kong, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
options = append(options, Bind(k))
|
options = append(options, Bind(k), Resolvers(EnvResolver()))
|
||||||
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
if err := option.Apply(k); err != nil {
|
if err := option.Apply(k); err != nil {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package kong
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -377,19 +376,6 @@ func (v *Value) ApplyDefault() error {
|
|||||||
// Does not include resolvers.
|
// Does not include resolvers.
|
||||||
func (v *Value) Reset() error {
|
func (v *Value) Reset() error {
|
||||||
v.Target.Set(reflect.Zero(v.Target.Type()))
|
v.Target.Set(reflect.Zero(v.Target.Type()))
|
||||||
if len(v.Tag.Envs) != 0 {
|
|
||||||
for _, env := range v.Tag.Envs {
|
|
||||||
envar, ok := os.LookupEnv(env)
|
|
||||||
// Parse the first non-empty ENV in the list
|
|
||||||
if ok {
|
|
||||||
err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s (from envar %s=%q)", err, env, envar)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v.HasDefault {
|
if v.HasDefault {
|
||||||
return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target)
|
return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target)
|
||||||
}
|
}
|
||||||
|
|||||||
+68
@@ -2,7 +2,9 @@ package kong
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,3 +68,69 @@ func snakeCase(name string) string {
|
|||||||
name = strings.Join(strings.Split(strings.Title(name), "-"), "")
|
name = strings.Join(strings.Split(strings.Title(name), "-"), "")
|
||||||
return strings.ToLower(name[:1]) + name[1:]
|
return strings.ToLower(name[:1]) + name[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnvResolver provides a resolver for environment variables tags
|
||||||
|
func EnvResolver() Resolver {
|
||||||
|
// Resolvers are typically only invoked for flags, as shown here:
|
||||||
|
// https://github.com/alecthomas/kong/blob/v1.6.0/context.go#L567
|
||||||
|
// However, environment variable annotations can also apply to arguments,
|
||||||
|
// as demonstrated in this test:
|
||||||
|
// https://github.com/alecthomas/kong/blob/v1.6.0/kong_test.go#L1226-L1244
|
||||||
|
// To handle this, we ensure that arguments are resolved as well.
|
||||||
|
// Since the resolution only needs to happen once, we use this boolean
|
||||||
|
// to track whether the resolution process has already been performed.
|
||||||
|
argsResolved := false
|
||||||
|
return ResolverFunc(func(context *Context, parent *Path, flag *Flag) (interface{}, error) {
|
||||||
|
if !argsResolved {
|
||||||
|
if err := resolveArgs(context.Path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// once resolved we do not want to run this anymore
|
||||||
|
argsResolved = true
|
||||||
|
}
|
||||||
|
for _, env := range flag.Tag.Envs {
|
||||||
|
envar, ok := os.LookupEnv(env)
|
||||||
|
// Parse the first non-empty ENV in the list
|
||||||
|
if ok {
|
||||||
|
return envar, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveArgs(paths []*Path) error {
|
||||||
|
for _, path := range paths {
|
||||||
|
if path.Command == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, positional := range path.Command.Positional {
|
||||||
|
if positional.Tag == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := visitValue(positional); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if path.Command.Argument != nil {
|
||||||
|
if err := visitValue(path.Command.Argument); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func visitValue(value *Value) error {
|
||||||
|
for _, env := range value.Tag.Envs {
|
||||||
|
envar, ok := os.LookupEnv(env)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
token := Token{Type: FlagValueToken, Value: envar}
|
||||||
|
if err := value.Parse(ScanFromTokens(token), value.Target); err != nil {
|
||||||
|
return fmt.Errorf("%s (from envar %s=%q)", err, env, envar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user