github.com/fzambia/viper-lite@v0.0.0-20171108064948-d5a31e6aa18b/util.go (about)

     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  
    11  package viper
    12  
    13  import (
    14  	"bytes"
    15  	"encoding/json"
    16  	"fmt"
    17  	"io"
    18  	"os"
    19  	"path/filepath"
    20  	"runtime"
    21  	"strings"
    22  	"unicode"
    23  
    24  	"github.com/pelletier/go-toml"
    25  	"github.com/spf13/cast"
    26  	jww "github.com/spf13/jwalterweatherman"
    27  	"gopkg.in/yaml.v2"
    28  )
    29  
    30  // ConfigParseError denotes failing to parse configuration file.
    31  type ConfigParseError struct {
    32  	err error
    33  }
    34  
    35  // Error returns the formatted configuration error.
    36  func (pe ConfigParseError) Error() string {
    37  	return fmt.Sprintf("While parsing config: %s", pe.err.Error())
    38  }
    39  
    40  // toCaseInsensitiveValue checks if the value is a  map;
    41  // if so, create a copy and lower-case the keys recursively.
    42  func toCaseInsensitiveValue(value interface{}) interface{} {
    43  	switch v := value.(type) {
    44  	case map[interface{}]interface{}:
    45  		value = copyAndInsensitiviseMap(cast.ToStringMap(v))
    46  	case map[string]interface{}:
    47  		value = copyAndInsensitiviseMap(v)
    48  	}
    49  
    50  	return value
    51  }
    52  
    53  // copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of
    54  // any map it makes case insensitive.
    55  func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} {
    56  	nm := make(map[string]interface{})
    57  
    58  	for key, val := range m {
    59  		lkey := strings.ToLower(key)
    60  		switch v := val.(type) {
    61  		case map[interface{}]interface{}:
    62  			nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v))
    63  		case map[string]interface{}:
    64  			nm[lkey] = copyAndInsensitiviseMap(v)
    65  		default:
    66  			nm[lkey] = v
    67  		}
    68  	}
    69  
    70  	return nm
    71  }
    72  
    73  func insensitiviseMap(m map[string]interface{}) {
    74  	for key, val := range m {
    75  		switch val.(type) {
    76  		case map[interface{}]interface{}:
    77  			// nested map: cast and recursively insensitivise
    78  			val = cast.ToStringMap(val)
    79  			insensitiviseMap(val.(map[string]interface{}))
    80  		case map[string]interface{}:
    81  			// nested map: recursively insensitivise
    82  			insensitiviseMap(val.(map[string]interface{}))
    83  		}
    84  
    85  		lower := strings.ToLower(key)
    86  		if key != lower {
    87  			// remove old key (not lower-cased)
    88  			delete(m, key)
    89  		}
    90  		// update map
    91  		m[lower] = val
    92  	}
    93  }
    94  
    95  func absPathify(inPath string) string {
    96  
    97  	if strings.HasPrefix(inPath, "$HOME") {
    98  		inPath = userHomeDir() + inPath[5:]
    99  	}
   100  
   101  	if strings.HasPrefix(inPath, "$") {
   102  		end := strings.Index(inPath, string(os.PathSeparator))
   103  		inPath = os.Getenv(inPath[1:end]) + inPath[end:]
   104  	}
   105  
   106  	if filepath.IsAbs(inPath) {
   107  		return filepath.Clean(inPath)
   108  	}
   109  
   110  	p, err := filepath.Abs(inPath)
   111  	if err == nil {
   112  		return filepath.Clean(p)
   113  	}
   114  
   115  	jww.ERROR.Println("Couldn't discover absolute path")
   116  	jww.ERROR.Println(err)
   117  	return ""
   118  }
   119  
   120  // Check if File / Directory Exists
   121  func exists(path string) (bool, error) {
   122  	_, err := os.Stat(path)
   123  	if err == nil {
   124  		return true, nil
   125  	}
   126  	if os.IsNotExist(err) {
   127  		return false, nil
   128  	}
   129  	return false, err
   130  }
   131  
   132  func stringInSlice(a string, list []string) bool {
   133  	for _, b := range list {
   134  		if b == a {
   135  			return true
   136  		}
   137  	}
   138  	return false
   139  }
   140  
   141  func userHomeDir() string {
   142  	if runtime.GOOS == "windows" {
   143  		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
   144  		if home == "" {
   145  			home = os.Getenv("USERPROFILE")
   146  		}
   147  		return home
   148  	}
   149  	return os.Getenv("HOME")
   150  }
   151  
   152  func unmarshallConfigReader(in io.Reader, c map[string]interface{}, configType string) error {
   153  	buf := new(bytes.Buffer)
   154  	buf.ReadFrom(in)
   155  
   156  	switch strings.ToLower(configType) {
   157  	case "yaml", "yml":
   158  		if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil {
   159  			return ConfigParseError{err}
   160  		}
   161  
   162  	case "json":
   163  		if err := json.Unmarshal(buf.Bytes(), &c); err != nil {
   164  			return ConfigParseError{err}
   165  		}
   166  
   167  	case "toml":
   168  		tree, err := toml.LoadReader(buf)
   169  		if err != nil {
   170  			return ConfigParseError{err}
   171  		}
   172  		tmap := tree.ToMap()
   173  		for k, v := range tmap {
   174  			c[k] = v
   175  		}
   176  	}
   177  
   178  	insensitiviseMap(c)
   179  	return nil
   180  }
   181  
   182  func safeMul(a, b uint) uint {
   183  	c := a * b
   184  	if a > 1 && b > 1 && c/b != a {
   185  		return 0
   186  	}
   187  	return c
   188  }
   189  
   190  // parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes
   191  func parseSizeInBytes(sizeStr string) uint {
   192  	sizeStr = strings.TrimSpace(sizeStr)
   193  	lastChar := len(sizeStr) - 1
   194  	multiplier := uint(1)
   195  
   196  	if lastChar > 0 {
   197  		if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' {
   198  			if lastChar > 1 {
   199  				switch unicode.ToLower(rune(sizeStr[lastChar-1])) {
   200  				case 'k':
   201  					multiplier = 1 << 10
   202  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   203  				case 'm':
   204  					multiplier = 1 << 20
   205  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   206  				case 'g':
   207  					multiplier = 1 << 30
   208  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   209  				default:
   210  					multiplier = 1
   211  					sizeStr = strings.TrimSpace(sizeStr[:lastChar])
   212  				}
   213  			}
   214  		}
   215  	}
   216  
   217  	size := cast.ToInt(sizeStr)
   218  	if size < 0 {
   219  		size = 0
   220  	}
   221  
   222  	return safeMul(uint(size), multiplier)
   223  }
   224  
   225  // deepSearch scans deep maps, following the key indexes listed in the
   226  // sequence "path".
   227  // The last value is expected to be another map, and is returned.
   228  //
   229  // In case intermediate keys do not exist, or map to a non-map value,
   230  // a new map is created and inserted, and the search continues from there:
   231  // the initial map "m" may be modified!
   232  func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
   233  	for _, k := range path {
   234  		m2, ok := m[k]
   235  		if !ok {
   236  			// intermediate key does not exist
   237  			// => create it and continue from there
   238  			m3 := make(map[string]interface{})
   239  			m[k] = m3
   240  			m = m3
   241  			continue
   242  		}
   243  		m3, ok := m2.(map[string]interface{})
   244  		if !ok {
   245  			// intermediate key is a value
   246  			// => replace with a new map
   247  			m3 = make(map[string]interface{})
   248  			m[k] = m3
   249  		}
   250  		// continue search from here
   251  		m = m3
   252  	}
   253  	return m
   254  }