github.com/17media/viper@v1.13.0/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  	"fmt"
    15  	"os"
    16  	"path/filepath"
    17  	"runtime"
    18  	"strings"
    19  	"unicode"
    20  
    21  	"github.com/spf13/cast"
    22  )
    23  
    24  // ConfigParseError denotes failing to parse configuration file.
    25  type ConfigParseError struct {
    26  	err error
    27  }
    28  
    29  // Error returns the formatted configuration error.
    30  func (pe ConfigParseError) Error() string {
    31  	return fmt.Sprintf("While parsing config: %s", pe.err.Error())
    32  }
    33  
    34  // toCaseInsensitiveValue checks if the value is a  map;
    35  // if so, create a copy and lower-case the keys recursively.
    36  func toCaseInsensitiveValue(value interface{}) interface{} {
    37  	switch v := value.(type) {
    38  	case map[interface{}]interface{}:
    39  		value = copyAndInsensitiviseMap(cast.ToStringMap(v))
    40  	case map[string]interface{}:
    41  		value = copyAndInsensitiviseMap(v)
    42  	}
    43  
    44  	return value
    45  }
    46  
    47  // copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of
    48  // any map it makes case insensitive.
    49  func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} {
    50  	nm := make(map[string]interface{})
    51  
    52  	for key, val := range m {
    53  		lkey := strings.ToLower(key)
    54  		switch v := val.(type) {
    55  		case map[interface{}]interface{}:
    56  			nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v))
    57  		case map[string]interface{}:
    58  			nm[lkey] = copyAndInsensitiviseMap(v)
    59  		default:
    60  			nm[lkey] = v
    61  		}
    62  	}
    63  
    64  	return nm
    65  }
    66  
    67  func insensitiviseMap(m map[string]interface{}) {
    68  	for key, val := range m {
    69  		switch val.(type) {
    70  		case map[interface{}]interface{}:
    71  			// nested map: cast and recursively insensitivise
    72  			val = cast.ToStringMap(val)
    73  			insensitiviseMap(val.(map[string]interface{}))
    74  		case map[string]interface{}:
    75  			// nested map: recursively insensitivise
    76  			insensitiviseMap(val.(map[string]interface{}))
    77  		}
    78  
    79  		lower := strings.ToLower(key)
    80  		if key != lower {
    81  			// remove old key (not lower-cased)
    82  			delete(m, key)
    83  		}
    84  		// update map
    85  		m[lower] = val
    86  	}
    87  }
    88  
    89  func absPathify(logger Logger, inPath string) string {
    90  	logger.Info("trying to resolve absolute path", "path", inPath)
    91  
    92  	if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) {
    93  		inPath = userHomeDir() + inPath[5:]
    94  	}
    95  
    96  	inPath = os.ExpandEnv(inPath)
    97  
    98  	if filepath.IsAbs(inPath) {
    99  		return filepath.Clean(inPath)
   100  	}
   101  
   102  	p, err := filepath.Abs(inPath)
   103  	if err == nil {
   104  		return filepath.Clean(p)
   105  	}
   106  
   107  	logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error())
   108  
   109  	return ""
   110  }
   111  
   112  func stringInSlice(a string, list []string) bool {
   113  	for _, b := range list {
   114  		if b == a {
   115  			return true
   116  		}
   117  	}
   118  	return false
   119  }
   120  
   121  func userHomeDir() string {
   122  	if runtime.GOOS == "windows" {
   123  		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
   124  		if home == "" {
   125  			home = os.Getenv("USERPROFILE")
   126  		}
   127  		return home
   128  	}
   129  	return os.Getenv("HOME")
   130  }
   131  
   132  func safeMul(a, b uint) uint {
   133  	c := a * b
   134  	if a > 1 && b > 1 && c/b != a {
   135  		return 0
   136  	}
   137  	return c
   138  }
   139  
   140  // parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes
   141  func parseSizeInBytes(sizeStr string) uint {
   142  	sizeStr = strings.TrimSpace(sizeStr)
   143  	lastChar := len(sizeStr) - 1
   144  	multiplier := uint(1)
   145  
   146  	if lastChar > 0 {
   147  		if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' {
   148  			if lastChar > 1 {
   149  				switch unicode.ToLower(rune(sizeStr[lastChar-1])) {
   150  				case 'k':
   151  					multiplier = 1 << 10
   152  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   153  				case 'm':
   154  					multiplier = 1 << 20
   155  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   156  				case 'g':
   157  					multiplier = 1 << 30
   158  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   159  				default:
   160  					multiplier = 1
   161  					sizeStr = strings.TrimSpace(sizeStr[:lastChar])
   162  				}
   163  			}
   164  		}
   165  	}
   166  
   167  	size := cast.ToInt(sizeStr)
   168  	if size < 0 {
   169  		size = 0
   170  	}
   171  
   172  	return safeMul(uint(size), multiplier)
   173  }
   174  
   175  // deepSearch scans deep maps, following the key indexes listed in the
   176  // sequence "path".
   177  // The last value is expected to be another map, and is returned.
   178  //
   179  // In case intermediate keys do not exist, or map to a non-map value,
   180  // a new map is created and inserted, and the search continues from there:
   181  // the initial map "m" may be modified!
   182  func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
   183  	for _, k := range path {
   184  		m2, ok := m[k]
   185  		if !ok {
   186  			// intermediate key does not exist
   187  			// => create it and continue from there
   188  			m3 := make(map[string]interface{})
   189  			m[k] = m3
   190  			m = m3
   191  			continue
   192  		}
   193  		m3, ok := m2.(map[string]interface{})
   194  		if !ok {
   195  			// intermediate key is a value
   196  			// => replace with a new map
   197  			m3 = make(map[string]interface{})
   198  			m[k] = m3
   199  		}
   200  		// continue search from here
   201  		m = m3
   202  	}
   203  	return m
   204  }