vendor/github.com/subosito/gotenv/gotenv.go
changeset 251 1c52a0eeb952
child 260 445e01aede7e
equal deleted inserted replaced
250:c040f992052f 251:1c52a0eeb952
       
     1 // Package gotenv provides functionality to dynamically load the environment variables
       
     2 package gotenv
       
     3 
       
     4 import (
       
     5 	"bufio"
       
     6 	"fmt"
       
     7 	"io"
       
     8 	"os"
       
     9 	"regexp"
       
    10 	"strings"
       
    11 )
       
    12 
       
    13 const (
       
    14 	// Pattern for detecting valid line format
       
    15 	linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`
       
    16 
       
    17 	// Pattern for detecting valid variable within a value
       
    18 	variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
       
    19 )
       
    20 
       
    21 // Env holds key/value pair of valid environment variable
       
    22 type Env map[string]string
       
    23 
       
    24 /*
       
    25 Load is a function to load a file or multiple files and then export the valid variables into environment variables if they do not exist.
       
    26 When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
       
    27 Otherwise, it will loop over the filenames parameter and set the proper environment variables.
       
    28 */
       
    29 func Load(filenames ...string) error {
       
    30 	return loadenv(false, filenames...)
       
    31 }
       
    32 
       
    33 /*
       
    34 OverLoad is a function to load a file or multiple files and then export and override the valid variables into environment variables.
       
    35 */
       
    36 func OverLoad(filenames ...string) error {
       
    37 	return loadenv(true, filenames...)
       
    38 }
       
    39 
       
    40 /*
       
    41 Must is wrapper function that will panic when supplied function returns an error.
       
    42 */
       
    43 func Must(fn func(filenames ...string) error, filenames ...string) {
       
    44 	if err := fn(filenames...); err != nil {
       
    45 		panic(err.Error())
       
    46 	}
       
    47 }
       
    48 
       
    49 /*
       
    50 Apply is a function to load an io Reader then export the valid variables into environment variables if they do not exist.
       
    51 */
       
    52 func Apply(r io.Reader) error {
       
    53 	return parset(r, false)
       
    54 }
       
    55 
       
    56 /*
       
    57 OverApply is a function to load an io Reader then export and override the valid variables into environment variables.
       
    58 */
       
    59 func OverApply(r io.Reader) error {
       
    60 	return parset(r, true)
       
    61 }
       
    62 
       
    63 func loadenv(override bool, filenames ...string) error {
       
    64 	if len(filenames) == 0 {
       
    65 		filenames = []string{".env"}
       
    66 	}
       
    67 
       
    68 	for _, filename := range filenames {
       
    69 		f, err := os.Open(filename)
       
    70 		if err != nil {
       
    71 			return err
       
    72 		}
       
    73 
       
    74 		err = parset(f, override)
       
    75 		if err != nil {
       
    76 			return err
       
    77 		}
       
    78 
       
    79 		f.Close()
       
    80 	}
       
    81 
       
    82 	return nil
       
    83 }
       
    84 
       
    85 // parse and set :)
       
    86 func parset(r io.Reader, override bool) error {
       
    87 	env, err := StrictParse(r)
       
    88 	if err != nil {
       
    89 		return err
       
    90 	}
       
    91 
       
    92 	for key, val := range env {
       
    93 		setenv(key, val, override)
       
    94 	}
       
    95 
       
    96 	return nil
       
    97 }
       
    98 
       
    99 func setenv(key, val string, override bool) {
       
   100 	if override {
       
   101 		os.Setenv(key, val)
       
   102 	} else {
       
   103 		if _, present := os.LookupEnv(key); !present {
       
   104 			os.Setenv(key, val)
       
   105 		}
       
   106 	}
       
   107 }
       
   108 
       
   109 // Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
       
   110 // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
       
   111 // This function is skipping any invalid lines and only processing the valid one.
       
   112 func Parse(r io.Reader) Env {
       
   113 	env, _ := StrictParse(r)
       
   114 	return env
       
   115 }
       
   116 
       
   117 // StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
       
   118 // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
       
   119 // This function is returning an error if there are any invalid lines.
       
   120 func StrictParse(r io.Reader) (Env, error) {
       
   121 	env := make(Env)
       
   122 	scanner := bufio.NewScanner(r)
       
   123 
       
   124 	i := 1
       
   125 	bom := string([]byte{239, 187, 191})
       
   126 
       
   127 	for scanner.Scan() {
       
   128 		line := scanner.Text()
       
   129 
       
   130 		if i == 1 {
       
   131 			line = strings.TrimPrefix(line, bom)
       
   132 		}
       
   133 
       
   134 		i++
       
   135 
       
   136 		err := parseLine(line, env)
       
   137 		if err != nil {
       
   138 			return env, err
       
   139 		}
       
   140 	}
       
   141 
       
   142 	return env, nil
       
   143 }
       
   144 
       
   145 func parseLine(s string, env Env) error {
       
   146 	rl := regexp.MustCompile(linePattern)
       
   147 	rm := rl.FindStringSubmatch(s)
       
   148 
       
   149 	if len(rm) == 0 {
       
   150 		return checkFormat(s, env)
       
   151 	}
       
   152 
       
   153 	key := rm[1]
       
   154 	val := rm[2]
       
   155 
       
   156 	// determine if string has quote prefix
       
   157 	hdq := strings.HasPrefix(val, `"`)
       
   158 
       
   159 	// determine if string has single quote prefix
       
   160 	hsq := strings.HasPrefix(val, `'`)
       
   161 
       
   162 	// trim whitespace
       
   163 	val = strings.Trim(val, " ")
       
   164 
       
   165 	// remove quotes '' or ""
       
   166 	rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`)
       
   167 	val = rq.ReplaceAllString(val, "$2")
       
   168 
       
   169 	if hdq {
       
   170 		val = strings.Replace(val, `\n`, "\n", -1)
       
   171 		val = strings.Replace(val, `\r`, "\r", -1)
       
   172 
       
   173 		// Unescape all characters except $ so variables can be escaped properly
       
   174 		re := regexp.MustCompile(`\\([^$])`)
       
   175 		val = re.ReplaceAllString(val, "$1")
       
   176 	}
       
   177 
       
   178 	rv := regexp.MustCompile(variablePattern)
       
   179 	fv := func(s string) string {
       
   180 		return varReplacement(s, hsq, env)
       
   181 	}
       
   182 
       
   183 	val = rv.ReplaceAllStringFunc(val, fv)
       
   184 	val = parseVal(val, env)
       
   185 
       
   186 	env[key] = val
       
   187 	return nil
       
   188 }
       
   189 
       
   190 func parseExport(st string, env Env) error {
       
   191 	if strings.HasPrefix(st, "export") {
       
   192 		vs := strings.SplitN(st, " ", 2)
       
   193 
       
   194 		if len(vs) > 1 {
       
   195 			if _, ok := env[vs[1]]; !ok {
       
   196 				return fmt.Errorf("line `%s` has an unset variable", st)
       
   197 			}
       
   198 		}
       
   199 	}
       
   200 
       
   201 	return nil
       
   202 }
       
   203 
       
   204 func varReplacement(s string, hsq bool, env Env) string {
       
   205 	if strings.HasPrefix(s, "\\") {
       
   206 		return strings.TrimPrefix(s, "\\")
       
   207 	}
       
   208 
       
   209 	if hsq {
       
   210 		return s
       
   211 	}
       
   212 
       
   213 	sn := `(\$)(\{?([A-Z0-9_]+)\}?)`
       
   214 	rn := regexp.MustCompile(sn)
       
   215 	mn := rn.FindStringSubmatch(s)
       
   216 
       
   217 	if len(mn) == 0 {
       
   218 		return s
       
   219 	}
       
   220 
       
   221 	v := mn[3]
       
   222 
       
   223 	replace, ok := env[v]
       
   224 	if !ok {
       
   225 		replace = os.Getenv(v)
       
   226 	}
       
   227 
       
   228 	return replace
       
   229 }
       
   230 
       
   231 func checkFormat(s string, env Env) error {
       
   232 	st := strings.TrimSpace(s)
       
   233 
       
   234 	if (st == "") || strings.HasPrefix(st, "#") {
       
   235 		return nil
       
   236 	}
       
   237 
       
   238 	if err := parseExport(st, env); err != nil {
       
   239 		return err
       
   240 	}
       
   241 
       
   242 	return fmt.Errorf("line `%s` doesn't match format", s)
       
   243 }
       
   244 
       
   245 func parseVal(val string, env Env) string {
       
   246 	if strings.Contains(val, "=") {
       
   247 		if !(val == "\n" || val == "\r") {
       
   248 			kv := strings.Split(val, "\n")
       
   249 
       
   250 			if len(kv) == 1 {
       
   251 				kv = strings.Split(val, "\r")
       
   252 			}
       
   253 
       
   254 			if len(kv) > 1 {
       
   255 				val = kv[0]
       
   256 
       
   257 				for i := 1; i < len(kv); i++ {
       
   258 					parseLine(kv[i], env)
       
   259 				}
       
   260 			}
       
   261 		}
       
   262 	}
       
   263 
       
   264 	return val
       
   265 }