|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- package env
-
- import (
- "bufio"
- "fmt"
- "os"
- "path/filepath"
- "reflect"
- "regexp"
- "strconv"
- "strings"
-
- log "github.com/sirupsen/logrus"
- "github.com/goanywhere/fs"
- )
-
- const tag string = "env"
-
- var pattern = regexp.MustCompile(`(export\s*)?(?P<key>\w+)\s*(=)\s*(?P<value>("|')?[\w\s-$,:/\.\+]+)("|')?`)
-
- // findKeyValue finds '=' separated key/value pair from the given string.
- func findKeyValue(line string) (key, value string) {
- // Intentionally insert a linebreak here to avoid non-stop characters on [[:graph:]].
- line = fmt.Sprintf("%s\n", line)
- match := pattern.FindStringSubmatch(line)
- if len(match) != 0 {
- result := make(map[string]string)
- for index, name := range pattern.SubexpNames() {
- result[name] = match[index]
- }
- // preserve those quoted spaces for value.
- key, value = result["key"], strings.TrimSpace(result["value"])
- value = strings.Trim(value, "'")
- value = strings.Trim(value, "\"")
- }
- return
- }
-
- // Load parses & set the values in the given files into os environment.
- func Load(dotenv string) {
- if fs.Exists(dotenv) {
- dotenv, _ = filepath.Abs(dotenv)
-
- if file, err := os.Open(dotenv); err == nil {
- defer file.Close()
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- k, v := findKeyValue(scanner.Text())
- if k != "" && v != "" {
- Set(k, v)
- } else {
- continue
- }
- }
- }
- }
- }
-
- // Get retrieves the string value of the environment variable named by the key.
- // It returns the value, which will be empty if the variable is not present.
- func Get(key string) (value string, exists bool) {
- if v := os.Getenv(key); v != "" {
- value, exists = v, true
- }
- return
- }
-
- // Set stores the value of the environment variable named by the key. It returns an error, if any.
- func Set(key string, value interface{}) error {
- var sv string
-
- switch T := value.(type) {
- case bool:
- sv = strconv.FormatBool(T)
-
- case float32, float64:
- sv = strconv.FormatFloat(reflect.ValueOf(value).Float(), 'g', -1, 64)
-
- case int, int8, int16, int32, int64:
- sv = strconv.FormatInt(reflect.ValueOf(value).Int(), 10)
-
- case uint, uint8, uint16, uint32, uint64:
- sv = strconv.FormatUint(reflect.ValueOf(value).Uint(), 10)
-
- case string:
- sv = value.(string)
-
- case []string:
- sv = strings.Join(value.([]string), ",")
-
- default:
- return fmt.Errorf("Unsupported type: %v", T)
- }
- return os.Setenv(key, sv)
- }
-
- // String retrieves the string value from environment named by the key.
- func String(key string, fallback ...string) (value string) {
- if str, exists := Get(key); exists {
- value = str
- } else if len(fallback) > 0 {
- value = fallback[0]
- }
- return
- }
-
- // Strings retrieves the string values separated by comma from the environment.
- func Strings(key string, fallback ...[]string) (value []string) {
- if v, exists := Get(key); exists {
- for _, item := range strings.Split(strings.TrimSpace(v), ",") {
- value = append(value, strings.TrimSpace(item))
- }
- } else if len(fallback) > 0 {
- value = fallback[0]
- }
- return
- }
-
- // Int retrieves the integer values separated by comma from the environment.
- func Int(key string, fallback ...int) (value int) {
- if str, exists := Get(key); exists {
- if v, e := strconv.ParseInt(str, 10, 0); e == nil {
- value = int(v)
- }
- } else if len(fallback) > 0 {
- value = fallback[0]
- }
- return
- }
-
- // Int retrieves the 64-bit integer values separated by comma from the environment.
- func Int64(key string, fallback ...int64) (value int64) {
- if str, exists := Get(key); exists {
- if v, e := strconv.ParseInt(str, 10, 64); e == nil {
- value = v
- }
- } else if len(fallback) > 0 {
- value = fallback[0]
- }
- return
- }
-
- // Uint retrieves the unsigned integer values separated by comma from the environment.
- func Uint(key string, fallback ...uint) (value uint) {
- if str, exists := Get(key); exists {
- if v, e := strconv.ParseUint(str, 10, 0); e == nil {
- value = uint(v)
- }
- } else if len(fallback) > 0 {
- value = fallback[0]
- }
- return
- }
-
- // Uint64 retrieves the 64-bit unsigned integer values separated by comma from the environment.
- func Uint64(key string, fallback ...uint64) (value uint64) {
- if str, exists := Get(key); exists {
- if v, e := strconv.ParseUint(str, 10, 64); e == nil {
- value = v
- }
- } else if len(fallback) > 0 {
- value = fallback[0]
- }
- return
- }
-
- // Bool retrieves boolean value from the environment.
- func Bool(key string, fallback ...bool) (value bool) {
- if str, exists := Get(key); exists {
- if v, e := strconv.ParseBool(str); e == nil {
- value = v
- }
- } else if len(fallback) > 0 {
- value = fallback[0]
- }
- return
- }
-
- // Float retrieves float (64) value from the environment.
- func Float(key string, fallback ...float64) (value float64) {
- if str, exists := Get(key); exists {
- if v, e := strconv.ParseFloat(str, 64); e == nil {
- value = v
- }
- } else if len(fallback) > 0 {
- value = fallback[0]
- }
- return
- }
-
- // Map fetches the key/value pair from os.Environ into the given spec.
- // Tags are supported via `env:ACTUAL_OS_KEY`.
- func Map(spec interface{}) error {
- value := reflect.ValueOf(spec)
- s := value.Elem()
-
- var stype reflect.Type = s.Type()
- var field reflect.Value
-
- for index := 0; index < s.NumField(); index++ {
- field = s.Field(index)
- if field.CanSet() {
-
- key := stype.Field(index).Tag.Get(tag)
- if key == "" {
- key = stype.Field(index).Name
- }
-
- value, exists := Get(key)
- if !exists {
- continue
- }
- // converts the environmental value from string to its real type.
- // Supports: String | Strings | Bool | Float | Integer | Unsiged Integer
- switch field.Kind() {
- case reflect.String:
- field.SetString(value)
-
- case reflect.Bool:
- field.SetBool(Bool(key))
-
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- field.SetInt(Int64(key))
-
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- field.SetUint(Uint64(key))
-
- case reflect.Float32, reflect.Float64:
- field.SetFloat(Float(key))
-
- case reflect.Slice:
- switch field.Interface().(type) {
- case []string:
- field.Set(reflect.ValueOf(Strings(key)))
-
- default:
- log.Fatalf("Only string slice is supported")
- }
- }
- }
- }
- return nil
- }
|