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