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