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