...

Source file src/github.com/spf13/viper/util.go

Documentation: github.com/spf13/viper

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

View as plain text