1
0
mirror of https://github.com/Luzifer/rconfig.git synced 2024-09-19 00:43:00 +00:00

Add AutoEnv feature

to automatically derive environment variable names from struct field
names

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-08-02 12:02:13 +02:00
parent 954ac1f137
commit f2a0efe97c
Signed by: luzifer
GPG Key ID: DC2729FDD34BE99E
3 changed files with 108 additions and 2 deletions

64
autoenv.go Normal file
View File

@ -0,0 +1,64 @@
package rconfig
import "strings"
type characterClass [2]rune
func (c characterClass) Contains(r rune) bool {
return c[0] <= r && c[1] >= r
}
type characterClasses []characterClass
func (c characterClasses) Contains(r rune) bool {
for _, cc := range c {
if cc.Contains(r) {
return true
}
}
return false
}
var (
charGroupUpperLetter = characterClass{'A', 'Z'}
charGroupLowerLetter = characterClass{'a', 'z'}
charGroupNumber = characterClass{'0', '9'}
charGroupLowerNumber = characterClasses{charGroupLowerLetter, charGroupNumber}
)
func deriveEnvVarName(s string) string {
var (
words []string
word []rune
)
for _, l := range s {
switch {
case charGroupUpperLetter.Contains(l):
if len(word) > 0 && charGroupLowerNumber.Contains(word[len(word)-1]) {
words = append(words, string(word))
word = []rune{}
}
word = append(word, l)
case charGroupLowerLetter.Contains(l):
if len(word) > 1 && charGroupUpperLetter.Contains(word[len(word)-1]) {
words = append(words, string(word[0:len(word)-1]))
word = word[len(word)-1:]
}
word = append(word, l)
case charGroupNumber.Contains(l):
word = append(word, l)
default:
if len(word) > 0 {
words = append(words, string(word))
}
word = []rune{}
}
}
words = append(words, string(word))
return strings.ToUpper(strings.Join(words, "_"))
}

29
autoenv_test.go Normal file
View File

@ -0,0 +1,29 @@
package rconfig
import (
"testing"
)
func TestDeriveEnvVarName(t *testing.T) {
for test, expect := range map[string]string{
"1Foobar": "1_FOOBAR",
"BC1": "BC1",
"BIGCase1": "BIG_CASE1",
"BIGCase": "BIG_CASE",
"Camel1": "CAMEL1",
"camel": "CAMEL",
"Camel": "CAMEL",
"CAMEL": "CAMEL",
"CamelCase": "CAMEL_CASE",
"_foobar": "FOOBAR",
"ILoveGoAndJSONSoMuch": "I_LOVE_GO_AND_JSON_SO_MUCH",
"mrT": "MR_T",
"my_case1": "MY_CASE1",
"MyFieldName": "MY_FIELD_NAME",
"SmallCASE": "SMALL_CASE",
} {
if d := deriveEnvVarName(test); d != expect {
t.Errorf("Derived variable %q did not match expectation %q", d, expect)
}
}
}

View File

@ -17,6 +17,7 @@ import (
)
var (
autoEnv bool
fs *pflag.FlagSet
variableDefaults map[string]string
)
@ -60,6 +61,13 @@ func Args() []string {
return fs.Args()
}
// AutoEnv enables or disables automated env variable guessing. If no `env` struct
// tag was set and AutoEnv is enabled the env variable name is derived from the
// name of the field: `MyFieldName` will get `MY_FIELD_NAME`
func AutoEnv(enable bool) {
autoEnv = enable
}
// Usage prints a basic usage with the corresponding defaults for the flags to
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
func Usage() {
@ -116,7 +124,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
}
value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default"))
value = envDefault(typeField.Tag.Get("env"), value)
value = envDefault(typeField, value)
parts := strings.Split(typeField.Tag.Get("flag"), ",")
switch typeField.Type {
@ -334,9 +342,14 @@ func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, part
}
}
func envDefault(env, def string) string {
func envDefault(field reflect.StructField, def string) string {
value := def
env := field.Tag.Get("env")
if env == "" && autoEnv {
env = deriveEnvVarName(field.Name)
}
if env != "" {
if e := os.Getenv(env); e != "" {
value = e