util.go (5376B)
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 // Unwrap returns the wrapped error. 35 func (pe ConfigParseError) Unwrap() error { 36 return pe.err 37 } 38 39 // toCaseInsensitiveValue checks if the value is a map; 40 // if so, create a copy and lower-case the keys recursively. 41 func toCaseInsensitiveValue(value interface{}) interface{} { 42 switch v := value.(type) { 43 case map[interface{}]interface{}: 44 value = copyAndInsensitiviseMap(cast.ToStringMap(v)) 45 case map[string]interface{}: 46 value = copyAndInsensitiviseMap(v) 47 } 48 49 return value 50 } 51 52 // copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of 53 // any map it makes case insensitive. 54 func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} { 55 nm := make(map[string]interface{}) 56 57 for key, val := range m { 58 lkey := strings.ToLower(key) 59 switch v := val.(type) { 60 case map[interface{}]interface{}: 61 nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v)) 62 case map[string]interface{}: 63 nm[lkey] = copyAndInsensitiviseMap(v) 64 default: 65 nm[lkey] = v 66 } 67 } 68 69 return nm 70 } 71 72 func insensitiviseVal(val interface{}) interface{} { 73 switch val.(type) { 74 case map[interface{}]interface{}: 75 // nested map: cast and recursively insensitivise 76 val = cast.ToStringMap(val) 77 insensitiviseMap(val.(map[string]interface{})) 78 case map[string]interface{}: 79 // nested map: recursively insensitivise 80 insensitiviseMap(val.(map[string]interface{})) 81 case []interface{}: 82 // nested array: recursively insensitivise 83 insensitiveArray(val.([]interface{})) 84 } 85 return val 86 } 87 88 func insensitiviseMap(m map[string]interface{}) { 89 for key, val := range m { 90 val = insensitiviseVal(val) 91 lower := strings.ToLower(key) 92 if key != lower { 93 // remove old key (not lower-cased) 94 delete(m, key) 95 } 96 // update map 97 m[lower] = val 98 } 99 } 100 101 func insensitiveArray(a []interface{}) { 102 for i, val := range a { 103 a[i] = insensitiviseVal(val) 104 } 105 } 106 107 func absPathify(logger Logger, inPath string) string { 108 logger.Info("trying to resolve absolute path", "path", inPath) 109 110 if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) { 111 inPath = userHomeDir() + inPath[5:] 112 } 113 114 inPath = os.ExpandEnv(inPath) 115 116 if filepath.IsAbs(inPath) { 117 return filepath.Clean(inPath) 118 } 119 120 p, err := filepath.Abs(inPath) 121 if err == nil { 122 return filepath.Clean(p) 123 } 124 125 logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error()) 126 127 return "" 128 } 129 130 func stringInSlice(a string, list []string) bool { 131 for _, b := range list { 132 if b == a { 133 return true 134 } 135 } 136 return false 137 } 138 139 func userHomeDir() string { 140 if runtime.GOOS == "windows" { 141 home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") 142 if home == "" { 143 home = os.Getenv("USERPROFILE") 144 } 145 return home 146 } 147 return os.Getenv("HOME") 148 } 149 150 func safeMul(a, b uint) uint { 151 c := a * b 152 if a > 1 && b > 1 && c/b != a { 153 return 0 154 } 155 return c 156 } 157 158 // parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes 159 func parseSizeInBytes(sizeStr string) uint { 160 sizeStr = strings.TrimSpace(sizeStr) 161 lastChar := len(sizeStr) - 1 162 multiplier := uint(1) 163 164 if lastChar > 0 { 165 if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' { 166 if lastChar > 1 { 167 switch unicode.ToLower(rune(sizeStr[lastChar-1])) { 168 case 'k': 169 multiplier = 1 << 10 170 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 171 case 'm': 172 multiplier = 1 << 20 173 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 174 case 'g': 175 multiplier = 1 << 30 176 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 177 default: 178 multiplier = 1 179 sizeStr = strings.TrimSpace(sizeStr[:lastChar]) 180 } 181 } 182 } 183 } 184 185 size := cast.ToInt(sizeStr) 186 if size < 0 { 187 size = 0 188 } 189 190 return safeMul(uint(size), multiplier) 191 } 192 193 // deepSearch scans deep maps, following the key indexes listed in the 194 // sequence "path". 195 // The last value is expected to be another map, and is returned. 196 // 197 // In case intermediate keys do not exist, or map to a non-map value, 198 // a new map is created and inserted, and the search continues from there: 199 // the initial map "m" may be modified! 200 func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { 201 for _, k := range path { 202 m2, ok := m[k] 203 if !ok { 204 // intermediate key does not exist 205 // => create it and continue from there 206 m3 := make(map[string]interface{}) 207 m[k] = m3 208 m = m3 209 continue 210 } 211 m3, ok := m2.(map[string]interface{}) 212 if !ok { 213 // intermediate key is a value 214 // => replace with a new map 215 m3 = make(map[string]interface{}) 216 m[k] = m3 217 } 218 // continue search from here 219 m = m3 220 } 221 return m 222 }