github.com/gozelle/viper@v1.14.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 insensitiviseVal(val interface{}) interface{} {
    68  	switch val.(type) {
    69  	case map[interface{}]interface{}:
    70  		// nested map: cast and recursively insensitivise
    71  		val = cast.ToStringMap(val)
    72  		insensitiviseMap(val.(map[string]interface{}))
    73  	case map[string]interface{}:
    74  		// nested map: recursively insensitivise
    75  		insensitiviseMap(val.(map[string]interface{}))
    76  	case []interface{}:
    77  		// nested array: recursively insensitivise
    78  		insensitiveArray(val.([]interface{}))
    79  	}
    80  	return val
    81  }
    82  
    83  func insensitiviseMap(m map[string]interface{}) {
    84  	for key, val := range m {
    85  		val = insensitiviseVal(val)
    86  		lower := strings.ToLower(key)
    87  		if key != lower {
    88  			// remove old key (not lower-cased)
    89  			delete(m, key)
    90  		}
    91  		// update map
    92  		m[lower] = val
    93  	}
    94  }
    95  
    96  func insensitiveArray(a []interface{}) {
    97  	for i, val := range a {
    98  		a[i] = insensitiviseVal(val)
    99  	}
   100  }
   101  
   102  func absPathify(logger Logger, inPath string) string {
   103  	logger.Info("trying to resolve absolute path", "path", inPath)
   104  
   105  	if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) {
   106  		inPath = userHomeDir() + inPath[5:]
   107  	}
   108  
   109  	inPath = os.ExpandEnv(inPath)
   110  
   111  	if filepath.IsAbs(inPath) {
   112  		return filepath.Clean(inPath)
   113  	}
   114  
   115  	p, err := filepath.Abs(inPath)
   116  	if err == nil {
   117  		return filepath.Clean(p)
   118  	}
   119  
   120  	logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error())
   121  
   122  	return ""
   123  }
   124  
   125  func stringInSlice(a string, list []string) bool {
   126  	for _, b := range list {
   127  		if b == a {
   128  			return true
   129  		}
   130  	}
   131  	return false
   132  }
   133  
   134  func userHomeDir() string {
   135  	if runtime.GOOS == "windows" {
   136  		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
   137  		if home == "" {
   138  			home = os.Getenv("USERPROFILE")
   139  		}
   140  		return home
   141  	}
   142  	return os.Getenv("HOME")
   143  }
   144  
   145  func safeMul(a, b uint) uint {
   146  	c := a * b
   147  	if a > 1 && b > 1 && c/b != a {
   148  		return 0
   149  	}
   150  	return c
   151  }
   152  
   153  // parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes
   154  func parseSizeInBytes(sizeStr string) uint {
   155  	sizeStr = strings.TrimSpace(sizeStr)
   156  	lastChar := len(sizeStr) - 1
   157  	multiplier := uint(1)
   158  
   159  	if lastChar > 0 {
   160  		if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' {
   161  			if lastChar > 1 {
   162  				switch unicode.ToLower(rune(sizeStr[lastChar-1])) {
   163  				case 'k':
   164  					multiplier = 1 << 10
   165  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   166  				case 'm':
   167  					multiplier = 1 << 20
   168  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   169  				case 'g':
   170  					multiplier = 1 << 30
   171  					sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
   172  				default:
   173  					multiplier = 1
   174  					sizeStr = strings.TrimSpace(sizeStr[:lastChar])
   175  				}
   176  			}
   177  		}
   178  	}
   179  
   180  	size := cast.ToInt(sizeStr)
   181  	if size < 0 {
   182  		size = 0
   183  	}
   184  
   185  	return safeMul(uint(size), multiplier)
   186  }
   187  
   188  // deepSearch scans deep maps, following the key indexes listed in the
   189  // sequence "path".
   190  // The last value is expected to be another map, and is returned.
   191  //
   192  // In case intermediate keys do not exist, or map to a non-map value,
   193  // a new map is created and inserted, and the search continues from there:
   194  // the initial map "m" may be modified!
   195  func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
   196  	for _, k := range path {
   197  		m2, ok := m[k]
   198  		if !ok {
   199  			// intermediate key does not exist
   200  			// => create it and continue from there
   201  			m3 := make(map[string]interface{})
   202  			m[k] = m3
   203  			m = m3
   204  			continue
   205  		}
   206  		m3, ok := m2.(map[string]interface{})
   207  		if !ok {
   208  			// intermediate key is a value
   209  			// => replace with a new map
   210  			m3 = make(map[string]interface{})
   211  			m[k] = m3
   212  		}
   213  		// continue search from here
   214  		m = m3
   215  	}
   216  	return m
   217  }