github.com/best4tires/kit@v1.0.5/env/env.go (about) 1 // Package env provides a uniform way of dealing with environment such as .env files, os.Environ and (command line) flags. 2 // The goal is, that applications don't have to care about the source from a variable but just handle the values. 3 package env 4 5 import ( 6 "fmt" 7 "os" 8 "strings" 9 10 "github.com/best4tires/kit/convert" 11 ) 12 13 // merge merges "from" env into "to" env, keeping already existing values 14 func merge(from map[string]any, to map[string]any) { 15 for k, v := range from { 16 if _, ok := to[k]; !ok { 17 to[k] = v 18 } 19 } 20 } 21 22 func unquote(s string) string { 23 if (strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`)) || 24 (strings.HasPrefix(s, `'`) && strings.HasSuffix(s, `'`)) { 25 return s[1 : len(s)-1] 26 } 27 return s 28 } 29 30 type Var struct { 31 Key string 32 Value any 33 } 34 35 func MkVar(k string, v any) Var { 36 return Var{Key: k, Value: v} 37 } 38 39 type Env map[string]any 40 41 func (env Env) add(k string, v any) { 42 k = strings.TrimSpace(k) 43 if k == "" { 44 return 45 } 46 env[k] = v 47 } 48 49 // Load loads environment variables from all available sources. It takes additional vars, which may be passed to the environment at runtime. 50 func Load(vars ...Var) Env { 51 env := Env{} 52 for _, osev := range os.Environ() { 53 k, v, _ := strings.Cut(osev, "=") 54 k = strings.TrimSpace(k) 55 if k == "" { 56 continue 57 } 58 v = unquote(strings.TrimSpace(v)) 59 if v == "" { 60 env.add(k, true) 61 } else { 62 env.add(k, v) 63 } 64 } 65 denv := LoadDotenv() 66 for k, v := range denv { 67 env.add(k, v) 68 } 69 flags := ParseFlags(os.Args) 70 for k, v := range flags { 71 env.add(k, v) 72 } 73 for _, v := range vars { 74 env.add(v.Key, v.Value) 75 } 76 77 // expand all values to allow for "inline" string vars 78 repl := env.Expander() 79 for k, v := range env { 80 if s, ok := v.(string); ok { 81 env[k] = repl.Replace(s) 82 } 83 } 84 return env 85 } 86 87 func (env Env) Expander() *strings.Replacer { 88 var oldnew []string 89 for k := range env { 90 new, ok := env.String(k) 91 if !ok { 92 continue 93 } 94 //new = escape(new) 95 oldnew = append(oldnew, fmt.Sprintf("{%s}", k), new) 96 } 97 repl := strings.NewReplacer(oldnew...) 98 return repl 99 } 100 101 // Var returns the value for the passed key if exists, otherwise, false 102 func (env Env) Var(key string) (any, bool) { 103 v, ok := env[key] 104 if !ok { 105 return nil, false 106 } 107 return v, true 108 } 109 110 // String returns the string-value for the passed key if exists, otherwise, false 111 func (env Env) String(key string) (string, bool) { 112 s, ok := env[key] 113 if !ok { 114 return "", false 115 } 116 return fmt.Sprintf("%v", s), true 117 } 118 119 // Int returns the int-value for the passed key if exists, otherwise, false 120 func (env Env) Int(key string) (int, bool) { 121 v, ok := env[key] 122 if !ok { 123 return 0, false 124 } 125 return convert.ToInt(v) 126 } 127 128 // StringOrDefault first tries to lookup the passed key, otherwise return def 129 func (env Env) StringOrDefault(key string, def string) string { 130 if v, ok := env.String(key); ok { 131 return v 132 } 133 return def 134 } 135 136 // IntOrDefault first tries to lookup the passed key, otherwise return def 137 func (env Env) IntOrDefault(key string, def int) int { 138 if v, ok := env.Int(key); ok { 139 return v 140 } 141 return def 142 } 143 144 // StringWithTag first to lookup "key.tag", otherwise return Env.String 145 func (env Env) StringWithTag(key string, tag string) (string, bool) { 146 tagProbe := fmt.Sprintf("%s.%s", key, tag) 147 if tagVal, ok := env.String(tagProbe); ok { 148 return tagVal, true 149 } 150 return env.String(key) 151 } 152 153 // StringWithTagOrDefault first tries to lookup "key.tag", otherwise return Env.StringOrDefault 154 func (env Env) StringWithTagOrDefault(key string, tag string, def string) string { 155 tagProbe := fmt.Sprintf("%s.%s", key, tag) 156 if tagVal, ok := env.String(tagProbe); ok { 157 return tagVal 158 } 159 return env.StringOrDefault(key, def) 160 }