|  | // Copyright © 2014 Steve Francia <spf@spf13.com>. | 
|  | // | 
|  | // Use of this source code is governed by an MIT-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // Viper is a application configuration system. | 
|  | // It believes that applications can be configured a variety of ways | 
|  | // via flags, ENVIRONMENT variables, configuration files retrieved | 
|  | // from the file system, or a remote key/value store. | 
|  |  | 
|  | package viper | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "encoding/json" | 
|  | "fmt" | 
|  | "io" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "runtime" | 
|  | "strings" | 
|  | "unicode" | 
|  |  | 
|  | "github.com/hashicorp/hcl" | 
|  | "github.com/magiconair/properties" | 
|  | toml "github.com/pelletier/go-toml" | 
|  | "github.com/spf13/cast" | 
|  | jww "github.com/spf13/jwalterweatherman" | 
|  | "gopkg.in/yaml.v2" | 
|  | ) | 
|  |  | 
|  | // ConfigParseError denotes failing to parse configuration file. | 
|  | type ConfigParseError struct { | 
|  | err error | 
|  | } | 
|  |  | 
|  | // Error returns the formatted configuration error. | 
|  | func (pe ConfigParseError) Error() string { | 
|  | return fmt.Sprintf("While parsing config: %s", pe.err.Error()) | 
|  | } | 
|  |  | 
|  | // toCaseInsensitiveValue checks if the value is a  map; | 
|  | // if so, create a copy and lower-case the keys recursively. | 
|  | func toCaseInsensitiveValue(value interface{}) interface{} { | 
|  | switch v := value.(type) { | 
|  | case map[interface{}]interface{}: | 
|  | value = copyAndInsensitiviseMap(cast.ToStringMap(v)) | 
|  | case map[string]interface{}: | 
|  | value = copyAndInsensitiviseMap(v) | 
|  | } | 
|  |  | 
|  | return value | 
|  | } | 
|  |  | 
|  | // copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of | 
|  | // any map it makes case insensitive. | 
|  | func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} { | 
|  | nm := make(map[string]interface{}) | 
|  |  | 
|  | for key, val := range m { | 
|  | lkey := strings.ToLower(key) | 
|  | switch v := val.(type) { | 
|  | case map[interface{}]interface{}: | 
|  | nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v)) | 
|  | case map[string]interface{}: | 
|  | nm[lkey] = copyAndInsensitiviseMap(v) | 
|  | default: | 
|  | nm[lkey] = v | 
|  | } | 
|  | } | 
|  |  | 
|  | return nm | 
|  | } | 
|  |  | 
|  | func insensitiviseMap(m map[string]interface{}) { | 
|  | for key, val := range m { | 
|  | switch val.(type) { | 
|  | case map[interface{}]interface{}: | 
|  | // nested map: cast and recursively insensitivise | 
|  | val = cast.ToStringMap(val) | 
|  | insensitiviseMap(val.(map[string]interface{})) | 
|  | case map[string]interface{}: | 
|  | // nested map: recursively insensitivise | 
|  | insensitiviseMap(val.(map[string]interface{})) | 
|  | } | 
|  |  | 
|  | lower := strings.ToLower(key) | 
|  | if key != lower { | 
|  | // remove old key (not lower-cased) | 
|  | delete(m, key) | 
|  | } | 
|  | // update map | 
|  | m[lower] = val | 
|  | } | 
|  | } | 
|  |  | 
|  | func absPathify(inPath string) string { | 
|  | jww.INFO.Println("Trying to resolve absolute path to", inPath) | 
|  |  | 
|  | if strings.HasPrefix(inPath, "$HOME") { | 
|  | inPath = userHomeDir() + inPath[5:] | 
|  | } | 
|  |  | 
|  | if strings.HasPrefix(inPath, "$") { | 
|  | end := strings.Index(inPath, string(os.PathSeparator)) | 
|  | inPath = os.Getenv(inPath[1:end]) + inPath[end:] | 
|  | } | 
|  |  | 
|  | if filepath.IsAbs(inPath) { | 
|  | return filepath.Clean(inPath) | 
|  | } | 
|  |  | 
|  | p, err := filepath.Abs(inPath) | 
|  | if err == nil { | 
|  | return filepath.Clean(p) | 
|  | } | 
|  |  | 
|  | jww.ERROR.Println("Couldn't discover absolute path") | 
|  | jww.ERROR.Println(err) | 
|  | return "" | 
|  | } | 
|  |  | 
|  | // Check if File / Directory Exists | 
|  | func exists(path string) (bool, error) { | 
|  | _, err := v.fs.Stat(path) | 
|  | if err == nil { | 
|  | return true, nil | 
|  | } | 
|  | if os.IsNotExist(err) { | 
|  | return false, nil | 
|  | } | 
|  | return false, err | 
|  | } | 
|  |  | 
|  | func stringInSlice(a string, list []string) bool { | 
|  | for _, b := range list { | 
|  | if b == a { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | func userHomeDir() string { | 
|  | if runtime.GOOS == "windows" { | 
|  | home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") | 
|  | if home == "" { | 
|  | home = os.Getenv("USERPROFILE") | 
|  | } | 
|  | return home | 
|  | } | 
|  | return os.Getenv("HOME") | 
|  | } | 
|  |  | 
|  | func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType string) error { | 
|  | buf := new(bytes.Buffer) | 
|  | buf.ReadFrom(in) | 
|  |  | 
|  | switch strings.ToLower(configType) { | 
|  | case "yaml", "yml": | 
|  | if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil { | 
|  | return ConfigParseError{err} | 
|  | } | 
|  |  | 
|  | case "json": | 
|  | if err := json.Unmarshal(buf.Bytes(), &c); err != nil { | 
|  | return ConfigParseError{err} | 
|  | } | 
|  |  | 
|  | case "hcl": | 
|  | obj, err := hcl.Parse(string(buf.Bytes())) | 
|  | if err != nil { | 
|  | return ConfigParseError{err} | 
|  | } | 
|  | if err = hcl.DecodeObject(&c, obj); err != nil { | 
|  | return ConfigParseError{err} | 
|  | } | 
|  |  | 
|  | case "toml": | 
|  | tree, err := toml.LoadReader(buf) | 
|  | if err != nil { | 
|  | return ConfigParseError{err} | 
|  | } | 
|  | tmap := tree.ToMap() | 
|  | for k, v := range tmap { | 
|  | c[k] = v | 
|  | } | 
|  |  | 
|  | case "properties", "props", "prop": | 
|  | var p *properties.Properties | 
|  | var err error | 
|  | if p, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil { | 
|  | return ConfigParseError{err} | 
|  | } | 
|  | for _, key := range p.Keys() { | 
|  | value, _ := p.Get(key) | 
|  | // recursively build nested maps | 
|  | path := strings.Split(key, ".") | 
|  | lastKey := strings.ToLower(path[len(path)-1]) | 
|  | deepestMap := deepSearch(c, path[0:len(path)-1]) | 
|  | // set innermost value | 
|  | deepestMap[lastKey] = value | 
|  | } | 
|  | } | 
|  |  | 
|  | insensitiviseMap(c) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func safeMul(a, b uint) uint { | 
|  | c := a * b | 
|  | if a > 1 && b > 1 && c/b != a { | 
|  | return 0 | 
|  | } | 
|  | return c | 
|  | } | 
|  |  | 
|  | // parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes | 
|  | func parseSizeInBytes(sizeStr string) uint { | 
|  | sizeStr = strings.TrimSpace(sizeStr) | 
|  | lastChar := len(sizeStr) - 1 | 
|  | multiplier := uint(1) | 
|  |  | 
|  | if lastChar > 0 { | 
|  | if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' { | 
|  | if lastChar > 1 { | 
|  | switch unicode.ToLower(rune(sizeStr[lastChar-1])) { | 
|  | case 'k': | 
|  | multiplier = 1 << 10 | 
|  | sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) | 
|  | case 'm': | 
|  | multiplier = 1 << 20 | 
|  | sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) | 
|  | case 'g': | 
|  | multiplier = 1 << 30 | 
|  | sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) | 
|  | default: | 
|  | multiplier = 1 | 
|  | sizeStr = strings.TrimSpace(sizeStr[:lastChar]) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | size := cast.ToInt(sizeStr) | 
|  | if size < 0 { | 
|  | size = 0 | 
|  | } | 
|  |  | 
|  | return safeMul(uint(size), multiplier) | 
|  | } | 
|  |  | 
|  | // deepSearch scans deep maps, following the key indexes listed in the | 
|  | // sequence "path". | 
|  | // The last value is expected to be another map, and is returned. | 
|  | // | 
|  | // In case intermediate keys do not exist, or map to a non-map value, | 
|  | // a new map is created and inserted, and the search continues from there: | 
|  | // the initial map "m" may be modified! | 
|  | func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { | 
|  | for _, k := range path { | 
|  | m2, ok := m[k] | 
|  | if !ok { | 
|  | // intermediate key does not exist | 
|  | // => create it and continue from there | 
|  | m3 := make(map[string]interface{}) | 
|  | m[k] = m3 | 
|  | m = m3 | 
|  | continue | 
|  | } | 
|  | m3, ok := m2.(map[string]interface{}) | 
|  | if !ok { | 
|  | // intermediate key is a value | 
|  | // => replace with a new map | 
|  | m3 = make(map[string]interface{}) | 
|  | m[k] = m3 | 
|  | } | 
|  | // continue search from here | 
|  | m = m3 | 
|  | } | 
|  | return m | 
|  | } |