github.com/DominikUrban/viper@v0.0.0-20220730150717-aaf74638bd32/util.go (about) 1 // Copyright © 2014 Steve Francia <spf@spf13.com>. 2 // 3 // Use of this source code is governed by an MIT-style 4 // license that can be found in the LICENSE file. 5 6 // Viper is a application configuration system. 7 // It believes that applications can be configured a variety of ways 8 // via flags, ENVIRONMENT variables, configuration files retrieved 9 // from the file system, or a remote key/value store. 10 11 package viper 12 13 import ( 14 "fmt" 15 "os" 16 "path/filepath" 17 "runtime" 18 "strings" 19 "unicode" 20 21 "github.com/spf13/cast" 22 ) 23 24 // ConfigParseError denotes failing to parse configuration file. 25 type ConfigParseError struct { 26 err error 27 } 28 29 // Error returns the formatted configuration error. 30 func (pe ConfigParseError) Error() string { 31 return fmt.Sprintf("While parsing config: %s", pe.err.Error()) 32 } 33 34 // toCaseInsensitiveValue checks if the value is a map; 35 // if so, create a copy and lower-case the keys recursively. 36 func toCaseInsensitiveValue(value interface{}) interface{} { 37 switch v := value.(type) { 38 case map[interface{}]interface{}: 39 value = copyAndInsensitiviseMap(cast.ToStringMap(v)) 40 case map[string]interface{}: 41 value = copyAndInsensitiviseMap(v) 42 } 43 44 return value 45 } 46 47 // copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of 48 // any map it makes case insensitive. 49 func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} { 50 nm := make(map[string]interface{}) 51 52 for key, val := range m { 53 lkey := strings.ToLower(key) 54 switch v := val.(type) { 55 case map[interface{}]interface{}: 56 nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v)) 57 case map[string]interface{}: 58 nm[lkey] = copyAndInsensitiviseMap(v) 59 default: 60 nm[lkey] = v 61 } 62 } 63 64 return nm 65 } 66 67 func insensitiviseVal(val interface{}) interface{} { 68 switch val.(type) { 69 case map[interface{}]interface{}: 70 // nested map: cast and recursively insensitivise 71 val = cast.ToStringMap(val) 72 insensitiviseMap(val.(map[string]interface{})) 73 case map[string]interface{}: 74 // nested map: recursively insensitivise 75 insensitiviseMap(val.(map[string]interface{})) 76 case []interface{}: 77 // nested array: recursively insensitivise 78 insensitiveArray(val.([]interface{})) 79 } 80 return val 81 } 82 83 func insensitiviseMap(m map[string]interface{}) { 84 for key, val := range m { 85 val = insensitiviseVal(val) 86 lower := strings.ToLower(key) 87 if key != lower { 88 // remove old key (not lower-cased) 89 delete(m, key) 90 } 91 // update map 92 m[lower] = val 93 } 94 } 95 96 func insensitiveArray(a []interface{}) { 97 for i, val := range a { 98 a[i] = insensitiviseVal(val) 99 } 100 } 101 102 func absPathify(logger Logger, inPath string) string { 103 logger.Info("trying to resolve absolute path", "path", inPath) 104 105 if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) { 106 inPath = userHomeDir() + inPath[5:] 107 } 108 109 inPath = os.ExpandEnv(inPath) 110 111 if filepath.IsAbs(inPath) { 112 return filepath.Clean(inPath) 113 } 114 115 p, err := filepath.Abs(inPath) 116 if err == nil { 117 return filepath.Clean(p) 118 } 119 120 logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error()) 121 122 return "" 123 } 124 125 func stringInSlice(a string, list []string) bool { 126 for _, b := range list { 127 if b == a { 128 return true 129 } 130 } 131 return false 132 } 133 134 func userHomeDir() string { 135 if runtime.GOOS == "windows" { 136 home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") 137 if home == "" { 138 home = os.Getenv("USERPROFILE") 139 } 140 return home 141 } 142 return os.Getenv("HOME") 143 } 144 145 func safeMul(a, b uint) uint { 146 c := a * b 147 if a > 1 && b > 1 && c/b != a { 148 return 0 149 } 150 return c 151 } 152 153 // parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes 154 func parseSizeInBytes(sizeStr string) uint { 155 sizeStr = strings.TrimSpace(sizeStr) 156 lastChar := len(sizeStr) - 1 157 multiplier := uint(1) 158 159 if lastChar > 0 { 160 if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' { 161 if lastChar > 1 { 162 switch unicode.ToLower(rune(sizeStr[lastChar-1])) { 163 case 'k': 164 multiplier = 1 << 10 165 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 166 case 'm': 167 multiplier = 1 << 20 168 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 169 case 'g': 170 multiplier = 1 << 30 171 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 172 default: 173 multiplier = 1 174 sizeStr = strings.TrimSpace(sizeStr[:lastChar]) 175 } 176 } 177 } 178 } 179 180 size := cast.ToInt(sizeStr) 181 if size < 0 { 182 size = 0 183 } 184 185 return safeMul(uint(size), multiplier) 186 } 187 188 // deepSearch scans deep maps, following the key indexes listed in the 189 // sequence "path". 190 // The last value is expected to be another map, and is returned. 191 // 192 // In case intermediate keys do not exist, or map to a non-map value, 193 // a new map is created and inserted, and the search continues from there: 194 // the initial map "m" may be modified! 195 func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { 196 for _, k := range path { 197 m2, ok := m[k] 198 if !ok { 199 // intermediate key does not exist 200 // => create it and continue from there 201 m3 := make(map[string]interface{}) 202 m[k] = m3 203 m = m3 204 continue 205 } 206 m3, ok := m2.(map[string]interface{}) 207 if !ok { 208 // intermediate key is a value 209 // => replace with a new map 210 m3 = make(map[string]interface{}) 211 m[k] = m3 212 } 213 // continue search from here 214 m = m3 215 } 216 return m 217 }