Skip to content

Commit

Permalink
support for float64 values, support for parsing a given FlagSet
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Teich committed Jan 15, 2020
1 parent 8f7bd85 commit c0f63ef
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 19 deletions.
22 changes: 17 additions & 5 deletions configstruct.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ import (
"strings"
)

// Parse uses a given struct c with tags and parses values from env or cli flags
// Parse uses a given struct c with tags and parses values from env or cli flags, it uses the default FlagSet and os.Args
func Parse(c interface{}) error {
return ParseWithFlagSet(flag.CommandLine, os.Args, c)
}

// ParseWithFlagSet can use a specific FlagSet and args slice to parse data from
func ParseWithFlagSet(flagSet *flag.FlagSet, cliArgs []string, c interface{}) error {

// use reflection to deep dive into our struct
valueRef := reflect.ValueOf(c)
Expand All @@ -28,19 +33,21 @@ func Parse(c interface{}) error {
if cli != "" {
switch field.Type.Kind() {
case reflect.String:
flag.StringVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*string), cli, value.String(), usage)
flagSet.StringVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*string), cli, value.String(), usage)
case reflect.Bool:
flag.BoolVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*bool), cli, value.Bool(), usage)
flagSet.BoolVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*bool), cli, value.Bool(), usage)
case reflect.Int:
flag.IntVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*int), cli, int(value.Int()), usage)
flagSet.IntVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*int), cli, int(value.Int()), usage)
case reflect.Float64:
flagSet.Float64Var(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*float64), cli, value.Float(), usage)
default:
return fmt.Errorf("config cli type %s not implemented", field.Type.Name())
}
}
}

// parse cli flags
flag.Parse()
flagSet.Parse(cliArgs[1:])

// iterate over struct fields for env values
for i := 0; i < confType.NumField(); i++ {
Expand All @@ -62,6 +69,11 @@ func Parse(c interface{}) error {
if err == nil {
valueRef.Elem().FieldByName(field.Name).SetInt(value)
}
case reflect.Float64:
value, err := strconv.ParseFloat(envValue, 64)
if err == nil {
valueRef.Elem().FieldByName(field.Name).SetFloat(value)
}
default:
return fmt.Errorf("config env type %s not implemented", field.Type.Name())
}
Expand Down
36 changes: 22 additions & 14 deletions configstruct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,62 @@ import (
)

type testConfig struct {
Hostname string `env:"CONFIGSTRUCT_HOSTNAME" cli:"hostname" usage:"hostname value"`
Port int `env:"CONFIGSTRUCT_PORT" cli:"port" usage:"listen port"`
Debug bool `env:"CONFIGSTRUCT_DEBUG" cli:"debug" usage:"debug mode"`
Hostname string `env:"CONFIGSTRUCT_HOSTNAME" cli:"hostname" usage:"hostname value"`
Port int `env:"CONFIGSTRUCT_PORT" cli:"port" usage:"listen port"`
Debug bool `env:"CONFIGSTRUCT_DEBUG" cli:"debug" usage:"debug mode"`
FloatValue float64 `env:"CONFIGSTRUCT_FLOAT" cli:"floatValue" usage:"float value"`
}

func TestParse(t *testing.T) {

t.Run("valid cli fields", func(t *testing.T) {
os.Args = []string{"command", "-hostname", "localhost", "-port", "8080", "-debug", "true"}
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
cliArgs := []string{"command", "-hostname=localhost", "-port=8080", "-debug=true", "-floatValue=100.5"}
flagSet := flag.NewFlagSet(cliArgs[0], flag.ExitOnError)

conf := testConfig{}

err := Parse(&conf)
err := ParseWithFlagSet(flagSet, cliArgs, &conf)
assert.NoError(t, err)

assert.Equal(t, 8080, conf.Port)
assert.Equal(t, "localhost", conf.Hostname)
assert.True(t, conf.Debug)
assert.Equal(t, 100.5, conf.FloatValue)

})

t.Run("set using env fields", func(t *testing.T) {
cliArgs := []string{"command", "-hostname=localhost", "-port=8080", "-debug=true", "-floatValue=100.5"}
flagSet := flag.NewFlagSet(cliArgs[0], flag.ExitOnError)

os.Clearenv()
os.Setenv("CONFIGSTRUCT_HOSTNAME", "myhost")
os.Setenv("CONFIGSTRUCT_PORT", "9000")
os.Setenv("CONFIGSTRUCT_DEBUG", "true")
os.Setenv("CONFIGSTRUCT_FLOAT", "2.5")

conf := testConfig{}

err := Parse(&conf)
err := ParseWithFlagSet(flagSet, cliArgs, &conf)
assert.NoError(t, err)

assert.Equal(t, 9000, conf.Port)
assert.Equal(t, "myhost", conf.Hostname)
assert.True(t, conf.Debug)

assert.Equal(t, 2.5, conf.FloatValue)
})

t.Run("overwrite cli flags with env fields", func(t *testing.T) {
os.Args = []string{"command", "-hostname", "localhost", "-port", "8080", "-debug", "true"}
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
cliArgs := []string{"command", "-hostname=server", "-port=8000", "-debug=true", "-floatValue=100.5"}
flagSet := flag.NewFlagSet(cliArgs[0], flag.ExitOnError)

os.Clearenv()
os.Setenv("CONFIGSTRUCT_HOSTNAME", "myhost")
os.Setenv("CONFIGSTRUCT_PORT", "9000")

conf := testConfig{}

err := Parse(&conf)
err := ParseWithFlagSet(flagSet, cliArgs, &conf)
assert.NoError(t, err)

assert.Equal(t, 9000, conf.Port)
Expand All @@ -71,9 +77,10 @@ func TestParse(t *testing.T) {
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)

conf := testConfig{
Hostname: "localhost",
Port: 8000,
Debug: true,
Hostname: "localhost",
Port: 8000,
Debug: true,
FloatValue: 300.1,
}

err := Parse(&conf)
Expand All @@ -82,6 +89,7 @@ func TestParse(t *testing.T) {
assert.Equal(t, 8000, conf.Port)
assert.Equal(t, "myhost", conf.Hostname)
assert.True(t, conf.Debug)
assert.Equal(t, 300.1, conf.FloatValue)
})
}

Expand Down

0 comments on commit c0f63ef

Please sign in to comment.