github.com/best4tires/kit@v1.0.5/env/env.go (about)

     1  // Package env provides a uniform way of dealing with environment such as .env files, os.Environ and (command line) flags.
     2  // The goal is, that applications don't have to care about the source from a variable but just handle the values.
     3  package env
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/best4tires/kit/convert"
    11  )
    12  
    13  // merge merges "from" env into "to" env, keeping already existing values
    14  func merge(from map[string]any, to map[string]any) {
    15  	for k, v := range from {
    16  		if _, ok := to[k]; !ok {
    17  			to[k] = v
    18  		}
    19  	}
    20  }
    21  
    22  func unquote(s string) string {
    23  	if (strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`)) ||
    24  		(strings.HasPrefix(s, `'`) && strings.HasSuffix(s, `'`)) {
    25  		return s[1 : len(s)-1]
    26  	}
    27  	return s
    28  }
    29  
    30  type Var struct {
    31  	Key   string
    32  	Value any
    33  }
    34  
    35  func MkVar(k string, v any) Var {
    36  	return Var{Key: k, Value: v}
    37  }
    38  
    39  type Env map[string]any
    40  
    41  func (env Env) add(k string, v any) {
    42  	k = strings.TrimSpace(k)
    43  	if k == "" {
    44  		return
    45  	}
    46  	env[k] = v
    47  }
    48  
    49  // Load loads environment variables from all available sources. It takes additional vars, which may be passed to the environment at runtime.
    50  func Load(vars ...Var) Env {
    51  	env := Env{}
    52  	for _, osev := range os.Environ() {
    53  		k, v, _ := strings.Cut(osev, "=")
    54  		k = strings.TrimSpace(k)
    55  		if k == "" {
    56  			continue
    57  		}
    58  		v = unquote(strings.TrimSpace(v))
    59  		if v == "" {
    60  			env.add(k, true)
    61  		} else {
    62  			env.add(k, v)
    63  		}
    64  	}
    65  	denv := LoadDotenv()
    66  	for k, v := range denv {
    67  		env.add(k, v)
    68  	}
    69  	flags := ParseFlags(os.Args)
    70  	for k, v := range flags {
    71  		env.add(k, v)
    72  	}
    73  	for _, v := range vars {
    74  		env.add(v.Key, v.Value)
    75  	}
    76  
    77  	// expand all values to allow for "inline" string vars
    78  	repl := env.Expander()
    79  	for k, v := range env {
    80  		if s, ok := v.(string); ok {
    81  			env[k] = repl.Replace(s)
    82  		}
    83  	}
    84  	return env
    85  }
    86  
    87  func (env Env) Expander() *strings.Replacer {
    88  	var oldnew []string
    89  	for k := range env {
    90  		new, ok := env.String(k)
    91  		if !ok {
    92  			continue
    93  		}
    94  		//new = escape(new)
    95  		oldnew = append(oldnew, fmt.Sprintf("{%s}", k), new)
    96  	}
    97  	repl := strings.NewReplacer(oldnew...)
    98  	return repl
    99  }
   100  
   101  // Var returns the value for the passed key if exists, otherwise, false
   102  func (env Env) Var(key string) (any, bool) {
   103  	v, ok := env[key]
   104  	if !ok {
   105  		return nil, false
   106  	}
   107  	return v, true
   108  }
   109  
   110  // String returns the string-value for the passed key if exists, otherwise, false
   111  func (env Env) String(key string) (string, bool) {
   112  	s, ok := env[key]
   113  	if !ok {
   114  		return "", false
   115  	}
   116  	return fmt.Sprintf("%v", s), true
   117  }
   118  
   119  // Int returns the int-value for the passed key if exists, otherwise, false
   120  func (env Env) Int(key string) (int, bool) {
   121  	v, ok := env[key]
   122  	if !ok {
   123  		return 0, false
   124  	}
   125  	return convert.ToInt(v)
   126  }
   127  
   128  // StringOrDefault first tries to lookup the passed key, otherwise return def
   129  func (env Env) StringOrDefault(key string, def string) string {
   130  	if v, ok := env.String(key); ok {
   131  		return v
   132  	}
   133  	return def
   134  }
   135  
   136  // IntOrDefault first tries to lookup the passed key, otherwise return def
   137  func (env Env) IntOrDefault(key string, def int) int {
   138  	if v, ok := env.Int(key); ok {
   139  		return v
   140  	}
   141  	return def
   142  }
   143  
   144  // StringWithTag first to lookup "key.tag", otherwise return Env.String
   145  func (env Env) StringWithTag(key string, tag string) (string, bool) {
   146  	tagProbe := fmt.Sprintf("%s.%s", key, tag)
   147  	if tagVal, ok := env.String(tagProbe); ok {
   148  		return tagVal, true
   149  	}
   150  	return env.String(key)
   151  }
   152  
   153  // StringWithTagOrDefault first tries to lookup "key.tag", otherwise return Env.StringOrDefault
   154  func (env Env) StringWithTagOrDefault(key string, tag string, def string) string {
   155  	tagProbe := fmt.Sprintf("%s.%s", key, tag)
   156  	if tagVal, ok := env.String(tagProbe); ok {
   157  		return tagVal
   158  	}
   159  	return env.StringOrDefault(key, def)
   160  }