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 11package viper 12 13import ( 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. 27type ConfigParseError struct { 28 err error 29} 30 31// Error returns the formatted configuration error. 32func (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. 38func 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. 51func 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 69func 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 91func absPathify(inPath string) string { 92 jww.INFO.Println("Trying to resolve absolute path to", inPath) 93 94 if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) { 95 inPath = userHomeDir() + inPath[5:] 96 } 97 98 inPath = os.ExpandEnv(inPath) 99 100 if filepath.IsAbs(inPath) { 101 return filepath.Clean(inPath) 102 } 103 104 p, err := filepath.Abs(inPath) 105 if err == nil { 106 return filepath.Clean(p) 107 } 108 109 jww.ERROR.Println("Couldn't discover absolute path") 110 jww.ERROR.Println(err) 111 return "" 112} 113 114// Check if file Exists 115func exists(fs afero.Fs, path string) (bool, error) { 116 stat, err := fs.Stat(path) 117 if err == nil { 118 return !stat.IsDir(), nil 119 } 120 if os.IsNotExist(err) { 121 return false, nil 122 } 123 return false, err 124} 125 126func stringInSlice(a string, list []string) bool { 127 for _, b := range list { 128 if b == a { 129 return true 130 } 131 } 132 return false 133} 134 135func userHomeDir() string { 136 if runtime.GOOS == "windows" { 137 home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") 138 if home == "" { 139 home = os.Getenv("USERPROFILE") 140 } 141 return home 142 } 143 return os.Getenv("HOME") 144} 145 146func safeMul(a, b uint) uint { 147 c := a * b 148 if a > 1 && b > 1 && c/b != a { 149 return 0 150 } 151 return c 152} 153 154// parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes 155func parseSizeInBytes(sizeStr string) uint { 156 sizeStr = strings.TrimSpace(sizeStr) 157 lastChar := len(sizeStr) - 1 158 multiplier := uint(1) 159 160 if lastChar > 0 { 161 if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' { 162 if lastChar > 1 { 163 switch unicode.ToLower(rune(sizeStr[lastChar-1])) { 164 case 'k': 165 multiplier = 1 << 10 166 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 167 case 'm': 168 multiplier = 1 << 20 169 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 170 case 'g': 171 multiplier = 1 << 30 172 sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) 173 default: 174 multiplier = 1 175 sizeStr = strings.TrimSpace(sizeStr[:lastChar]) 176 } 177 } 178 } 179 } 180 181 size := cast.ToInt(sizeStr) 182 if size < 0 { 183 size = 0 184 } 185 186 return safeMul(uint(size), multiplier) 187} 188 189// deepSearch scans deep maps, following the key indexes listed in the 190// sequence "path". 191// The last value is expected to be another map, and is returned. 192// 193// In case intermediate keys do not exist, or map to a non-map value, 194// a new map is created and inserted, and the search continues from there: 195// the initial map "m" may be modified! 196func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { 197 for _, k := range path { 198 m2, ok := m[k] 199 if !ok { 200 // intermediate key does not exist 201 // => create it and continue from there 202 m3 := make(map[string]interface{}) 203 m[k] = m3 204 m = m3 205 continue 206 } 207 m3, ok := m2.(map[string]interface{}) 208 if !ok { 209 // intermediate key is a value 210 // => replace with a new map 211 m3 = make(map[string]interface{}) 212 m[k] = m3 213 } 214 // continue search from here 215 m = m3 216 } 217 return m 218} 219