From fcb2552e73245ce4d214c4644a95198bbea6a512 Mon Sep 17 00:00:00 2001 From: Roy Olav Purser Date: Sat, 15 Jan 2022 17:55:29 +0100 Subject: [PATCH] add env map --- datatype.go | 1 + envconf.go | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/datatype.go b/datatype.go index 687d235..6933880 100644 --- a/datatype.go +++ b/datatype.go @@ -22,6 +22,7 @@ type cValue struct { durval time.Duration boolval bool strval string + mapval map[string]cValue err error } diff --git a/envconf.go b/envconf.go index f50fa54..882e879 100644 --- a/envconf.go +++ b/envconf.go @@ -18,9 +18,11 @@ type cEntry struct { defval string // The default value hasdef bool // Default value is defined } + type Config struct { parsed bool env map[string]cEntry + mapEnv map[string]map[string]cEntry } // NewConfig returns an envconf.Config that is used to read configuration from environment variables. @@ -30,10 +32,21 @@ func NewConfig() *Config { config := new(Config) config.parsed = false config.env = make(map[string]cEntry) + config.mapEnv = make(map[string]map[string]cEntry) + for _, v := range os.Environ() { splitted := strings.SplitN(v, "=", 2) if len(splitted) == 2 { - key := strings.TrimSpace(strings.ToUpper(splitted[0])) + key := cleanKey(splitted[0]) + keysplit := strings.Split(key, "_") + left := "" + right := "" + + if len(keysplit) > 1 { + left = strings.Join(keysplit[:len(keysplit)-1], "_") + right = keysplit[len(keysplit)-1] + } + if unicode.IsLetter(getFirstRune(key)) { var entry cEntry entry.value = splitted[1] @@ -41,13 +54,14 @@ func NewConfig() *Config { entry.unset = false entry.empty = false config.env[key] = entry + config.mapEnv[left][right] = entry } } } return config } -// Define defines the type of an environment variable. +// Define the type of an environment variable. // Variables without a defined type will be ignored by Parse. func (c *Config) Define(key string, dtype DataType) { upper := strings.ToUpper(key) @@ -64,7 +78,20 @@ func (c *Config) Define(key string, dtype DataType) { } } -// DefineDefault defines the type and default value of an environment variable. +// Define the type of an environment variable. +// Variables without a defined type will be ignored by Parse. +func (c *Config) DefineMap(key string, dtype DataType) { + upper := strings.ToUpper(key) + entries, ok := c.mapEnv[upper] + if ok { + for mapKey, entry := range entries { + entry.dtype = dtype + c.mapEnv[key][mapKey] = entry + } + } +} + +// Define the type and default value of an environment variable. // Variables without a defined type will be ignored by Parse. func (c *Config) DefineDefault(key string, val string, dtype DataType) { upper := strings.ToUpper(key) @@ -108,6 +135,14 @@ func (c *Config) Parse() { c.env[k] = v } } + + for k, v := range c.mapEnv { + for mk, mv := range v { + mv.parsed = mv.dtype.parse(k+"_"+mk, mv.value) + c.mapEnv[k][mk] = mv + } + } + if failed { for k, v := range c.env { if (v.parsed.err == nil) && v.unset { @@ -199,6 +234,17 @@ func (c *Config) Status() (ok bool) { } } } + for _, v := range c.mapEnv { + for _, mv := range v { + err := mv.parsed.err + if err != nil { + ok = false + if !mv.empty { + fmt.Fprintln(os.Stderr, err) + } + } + } + } if !ok { fmt.Fprintln(os.Stderr, "") for _, v := range c.env { @@ -212,6 +258,27 @@ func (c *Config) Status() (ok bool) { return } +func cleanKey(str string) string { + fn := func(r rune) rune { + if (r >= '0' && r <= '9') || (r >= 'A' && r <= 'Z') || r == '_' { + return r + } + return -1 + } + + str = strings.Map(fn, strings.ToUpper(str)) + + oldlen := len(str) + 1 + newlen := len(str) + for newlen < oldlen { + oldlen = newlen + str = strings.ReplaceAll(str, "__", "_") + newlen = len(str) + } + + return str +} + func (c *Config) getRaw(key string, dtype DataType) (val cValue) { val.dtype = TypeNone if c.parsed { @@ -224,6 +291,27 @@ func (c *Config) getRaw(key string, dtype DataType) (val cValue) { return } +func (c *Config) getRawMap(key string, dtype DataType) (empty map[string]cValue) { + empty = make(map[string]cValue) + retval := make(map[string]cValue) + if c.parsed { + key = cleanKey(key) + entries, ok := c.mapEnv[key] + + if ok { + for k, v := range entries { + if v.dtype == dtype { + retval[k] = v.parsed + } else { + return + } + } + return retval + } + } + return +} + // GetInt returns the value of an environment variable. // If the variable is not defined as envconf.TypeInt the function will return 0. func (c *Config) GetInt(key string) int64 { @@ -270,6 +358,38 @@ func (c *Config) GetBool(key string) bool { return val.boolval } +func (c *Config) GetMapInt(key string) (retval map[string]int64) { + retval = make(map[string]int64) + for k, v := range c.getRawMap(key, TypeInt) { + retval[k] = v.intval + } + return +} + +func (c *Config) GetMapDuration(key string) (retval map[string]time.Duration) { + retval = make(map[string]time.Duration) + for k, v := range c.getRawMap(key, TypeDuration) { + retval[k] = v.durval + } + return +} + +func (c *Config) GetMapString(key string) (retval map[string]string) { + retval = make(map[string]string) + for k, v := range c.getRawMap(key, TypeString) { + retval[k] = v.strval + } + return +} + +func (c *Config) GetMapBool(key string) (retval map[string]bool) { + retval = make(map[string]bool) + for k, v := range c.getRawMap(key, TypeBool) { + retval[k] = v.boolval + } + return +} + func getFirstRune(str string) rune { for _, v := range str { return v