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