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