github.com/andrewseidl/viper@v0.0.0-20191228040610-757ecc9b7b3e/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 "bytes" 15 "encoding/json" 16 "fmt" 17 "io" 18 "os" 19 "path/filepath" 20 "runtime" 21 "strings" 22 "unicode" 23 24 "github.com/magiconair/properties" 25 toml "github.com/pelletier/go-toml" 26 "github.com/spf13/cast" 27 jww "github.com/spf13/jwalterweatherman" 28 "gopkg.in/yaml.v2" 29 ) 30 31 // ConfigParseError denotes failing to parse configuration file. 32 type ConfigParseError struct { 33 err error 34 } 35 36 // Error returns the formatted configuration error. 37 func (pe ConfigParseError) Error() string { 38 return fmt.Sprintf("While parsing config: %s", pe.err.Error()) 39 } 40 41 // toCaseInsensitiveValue checks if the value is a map; 42 // if so, create a copy and lower-case the keys recursively. 43 func toCaseInsensitiveValue(value interface{}) interface{} { 44 switch v := value.(type) { 45 case map[interface{}]interface{}: 46 value = copyAndInsensitiviseMap(cast.ToStringMap(v)) 47 case map[string]interface{}: 48 value = copyAndInsensitiviseMap(v) 49 } 50 51 return value 52 } 53 54 // copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of 55 // any map it makes case insensitive. 56 func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} { 57 nm := make(map[string]interface{}) 58 59 for key, val := range m { 60 lkey := strings.ToLower(key) 61 switch v := val.(type) { 62 case map[interface{}]interface{}: 63 nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v)) 64 case map[string]interface{}: 65 nm[lkey] = copyAndInsensitiviseMap(v) 66 default: 67 nm[lkey] = v 68 } 69 } 70 71 return nm 72 } 73 74 func insensitiviseMap(m map[string]interface{}) { 75 for key, val := range m { 76 switch val.(type) { 77 case map[interface{}]interface{}: 78 // nested map: cast and recursively insensitivise 79 val = cast.ToStringMap(val) 80 insensitiviseMap(val.(map[string]interface{})) 81 case map[string]interface{}: 82 // nested map: recursively insensitivise 83 insensitiviseMap(val.(map[string]interface{})) 84 } 85 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 absPathify(inPath string) string { 97 jww.INFO.Println("Trying to resolve absolute path to", inPath) 98 99 if strings.HasPrefix(inPath, "$HOME") { 100 inPath = userHomeDir() + inPath[5:] 101 } 102 103 if strings.HasPrefix(inPath, "$") { 104 end := strings.Index(inPath, string(os.PathSeparator)) 105 inPath = os.Getenv(inPath[1:end]) + inPath[end:] 106 } 107 108 if filepath.IsAbs(inPath) { 109 return filepath.Clean(inPath) 110 } 111 112 p, err := filepath.Abs(inPath) 113 if err == nil { 114 return filepath.Clean(p) 115 } 116 117 jww.ERROR.Println("Couldn't discover absolute path") 118 jww.ERROR.Println(err) 119 return "" 120 } 121 122 // Check if File / Directory Exists 123 func exists(path string) (bool, error) { 124 _, err := v.fs.Stat(path) 125 if err == nil { 126 return true, nil 127 } 128 if os.IsNotExist(err) { 129 return false, nil 130 } 131 return false, err 132 } 133 134 func stringInSlice(a string, list []string) bool { 135 for _, b := range list { 136 if b == a { 137 return true 138 } 139 } 140 return false 141 } 142 143 func userHomeDir() string { 144 if runtime.GOOS == "windows" { 145 home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") 146 if home == "" { 147 home = os.Getenv("USERPROFILE") 148 } 149 return home 150 } 151 return os.Getenv("HOME") 152 } 153 154 func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType string) error { 155 buf := new(bytes.Buffer) 156 buf.ReadFrom(in) 157 158 switch strings.ToLower(configType) { 159 case "yaml", "yml": 160 if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil { 161 return ConfigParseError{err} 162 } 163 164 case "json": 165 if err := json.Unmarshal(buf.Bytes(), &c); err != nil { 166 return ConfigParseError{err} 167 } 168 169 case "toml": 170 tree, err := toml.LoadReader(buf) 171 if err != nil { 172 return ConfigParseError{err} 173 } 174 tmap := tree.ToMap() 175 for k, v := range tmap { 176 c[k] = v 177 } 178 179 case "properties", "props", "prop": 180 var p *properties.Properties 181 var err error 182 if p, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil { 183 return ConfigParseError{err} 184 } 185 for _, key := range p.Keys() { 186 value, _ := p.Get(key) 187 // recursively build nested maps 188 path := strings.Split(key, ".") 189 lastKey := strings.ToLower(path[len(path)-1]) 190 deepestMap := deepSearch(c, path[0:len(path)-1]) 191 // set innermost value 192 deepestMap[lastKey] = value 193 } 194 } 195 196 insensitiviseMap(c) 197 return nil 198 } 199 200 func safeMul(a, b uint) uint { 201 c := a * b 202 if a > 1 && b > 1 && c/b != a { 203 return 0 204 } 205 return c 206 } 207 208 // parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes 209 func parseSizeInBytes(sizeStr string) uint { 210 sizeStr = strings.TrimSpace(sizeStr) 211 lastChar := len(sizeStr) - 1 212 multiplier := uint(1) 213 214 if lastChar > 0 { 215 if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' { 216 if lastChar > 1 { 217 switch unicode.ToLower(rune(sizeStr[lastChar-1])) { 218 case 'k': 219 multiplier = 1 << 10 220 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 221 case 'm': 222 multiplier = 1 << 20 223 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 224 case 'g': 225 multiplier = 1 << 30 226 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 227 default: 228 multiplier = 1 229 sizeStr = strings.TrimSpace(sizeStr[:lastChar]) 230 } 231 } 232 } 233 } 234 235 size := cast.ToInt(sizeStr) 236 if size < 0 { 237 size = 0 238 } 239 240 return safeMul(uint(size), multiplier) 241 } 242 243 // deepSearch scans deep maps, following the key indexes listed in the 244 // sequence "path". 245 // The last value is expected to be another map, and is returned. 246 // 247 // In case intermediate keys do not exist, or map to a non-map value, 248 // a new map is created and inserted, and the search continues from there: 249 // the initial map "m" may be modified! 250 func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { 251 for _, k := range path { 252 m2, ok := m[k] 253 if !ok { 254 // intermediate key does not exist 255 // => create it and continue from there 256 m3 := make(map[string]interface{}) 257 m[k] = m3 258 m = m3 259 continue 260 } 261 m3, ok := m2.(map[string]interface{}) 262 if !ok { 263 // intermediate key is a value 264 // => replace with a new map 265 m3 = make(map[string]interface{}) 266 m[k] = m3 267 } 268 // continue search from here 269 m = m3 270 } 271 return m 272 }