package config 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" yaml "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()) } // ConfigMarshalError denotes failing to marshal configuration. type ConfigMarshalError struct { err error } // Error returns the formatted marshal error. func (me ConfigMarshalError) Error() string { return fmt.Sprintf("While marshaling config: %s", me.err.Error()) } func absPathify(inPath string) (string, error) { 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), nil } p, err := filepath.Abs(inPath) if err == nil { return filepath.Clean(p), nil } return "", err } 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 stringInSlice(a string, list []string) bool { for _, b := range list { if b == a { return true } } return false } func exists(path string) (bool, error) { _, err := _c.fs.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } func marshallConfig(c map[string]interface{}, configType string) ([]byte, error) { var ( buf []byte err error ) switch strings.ToLower(configType) { case "yaml", "yml": if buf, err = yaml.Marshal(c); err != nil { return nil, ConfigMarshalError{err} } case "json": if buf, err = json.Marshal(c); err != nil { return nil, ConfigMarshalError{err} } case "toml": if buf, err = toml.Marshal(c); err != nil { return nil, ConfigMarshalError{err} } } return buf, nil } 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 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 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 } 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 } 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 safeMul(a, b uint) uint { c := a * b if a > 1 && b > 1 && c/b != a { return 0 } return c } 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) }