vendor/github.com/subosito/gotenv/gotenv.go
changeset 260 445e01aede7e
parent 251 1c52a0eeb952
child 265 05c40b36d3b2
equal deleted inserted replaced
259:db4911b0c721 260:445e01aede7e
     4 import (
     4 import (
     5 	"bufio"
     5 	"bufio"
     6 	"fmt"
     6 	"fmt"
     7 	"io"
     7 	"io"
     8 	"os"
     8 	"os"
       
     9 	"path/filepath"
     9 	"regexp"
    10 	"regexp"
       
    11 	"sort"
       
    12 	"strconv"
    10 	"strings"
    13 	"strings"
    11 )
    14 )
    12 
    15 
    13 const (
    16 const (
    14 	// Pattern for detecting valid line format
    17 	// Pattern for detecting valid line format
    15 	linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`
    18 	linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`
    16 
    19 
    17 	// Pattern for detecting valid variable within a value
    20 	// Pattern for detecting valid variable within a value
    18 	variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
    21 	variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
       
    22 
       
    23 	// Byte order mark character
       
    24 	bom = "\xef\xbb\xbf"
    19 )
    25 )
    20 
    26 
    21 // Env holds key/value pair of valid environment variable
    27 // Env holds key/value pair of valid environment variable
    22 type Env map[string]string
    28 type Env map[string]string
    23 
    29 
    24 /*
    30 // 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.
    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.
    31 // When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
    26 When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
    32 // Otherwise, it will loop over the filenames parameter and set the proper environment variables.
    27 Otherwise, it will loop over the filenames parameter and set the proper environment variables.
       
    28 */
       
    29 func Load(filenames ...string) error {
    33 func Load(filenames ...string) error {
    30 	return loadenv(false, filenames...)
    34 	return loadenv(false, filenames...)
    31 }
    35 }
    32 
    36 
    33 /*
    37 // OverLoad is a function to load a file or multiple files and then export and override the valid variables into environment variables.
    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 {
    38 func OverLoad(filenames ...string) error {
    37 	return loadenv(true, filenames...)
    39 	return loadenv(true, filenames...)
    38 }
    40 }
    39 
    41 
    40 /*
    42 // Must is wrapper function that will panic when supplied function returns an error.
    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) {
    43 func Must(fn func(filenames ...string) error, filenames ...string) {
    44 	if err := fn(filenames...); err != nil {
    44 	if err := fn(filenames...); err != nil {
    45 		panic(err.Error())
    45 		panic(err.Error())
    46 	}
    46 	}
    47 }
    47 }
    48 
    48 
    49 /*
    49 // Apply is a function to load an io Reader then export the valid variables into environment variables if they do not exist.
    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 {
    50 func Apply(r io.Reader) error {
    53 	return parset(r, false)
    51 	return parset(r, false)
    54 }
    52 }
    55 
    53 
    56 /*
    54 // OverApply is a function to load an io Reader then export and override the valid variables into environment variables.
    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 {
    55 func OverApply(r io.Reader) error {
    60 	return parset(r, true)
    56 	return parset(r, true)
    61 }
    57 }
    62 
    58 
    63 func loadenv(override bool, filenames ...string) error {
    59 func loadenv(override bool, filenames ...string) error {
    70 		if err != nil {
    66 		if err != nil {
    71 			return err
    67 			return err
    72 		}
    68 		}
    73 
    69 
    74 		err = parset(f, override)
    70 		err = parset(f, override)
       
    71 		f.Close()
    75 		if err != nil {
    72 		if err != nil {
    76 			return err
    73 			return err
    77 		}
    74 		}
    78 
       
    79 		f.Close()
       
    80 	}
    75 	}
    81 
    76 
    82 	return nil
    77 	return nil
    83 }
    78 }
    84 
    79 
    85 // parse and set :)
    80 // parse and set :)
    86 func parset(r io.Reader, override bool) error {
    81 func parset(r io.Reader, override bool) error {
    87 	env, err := StrictParse(r)
    82 	env, err := strictParse(r, override)
    88 	if err != nil {
    83 	if err != nil {
    89 		return err
    84 		return err
    90 	}
    85 	}
    91 
    86 
    92 	for key, val := range env {
    87 	for key, val := range env {
   108 
   103 
   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.
   104 // 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.
   105 // 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.
   106 // This function is skipping any invalid lines and only processing the valid one.
   112 func Parse(r io.Reader) Env {
   107 func Parse(r io.Reader) Env {
   113 	env, _ := StrictParse(r)
   108 	env, _ := strictParse(r, false)
   114 	return env
   109 	return env
   115 }
   110 }
   116 
   111 
   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.
   112 // 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.
   113 // 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.
   114 // This function is returning an error if there are any invalid lines.
   120 func StrictParse(r io.Reader) (Env, error) {
   115 func StrictParse(r io.Reader) (Env, error) {
       
   116 	return strictParse(r, false)
       
   117 }
       
   118 
       
   119 // Read is a function to parse a file line by line and returns the valid Env key/value pair of valid variables.
       
   120 // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
       
   121 // This function is skipping any invalid lines and only processing the valid one.
       
   122 func Read(filename string) (Env, error) {
       
   123 	f, err := os.Open(filename)
       
   124 	if err != nil {
       
   125 		return nil, err
       
   126 	}
       
   127 	defer f.Close()
       
   128 	return strictParse(f, false)
       
   129 }
       
   130 
       
   131 // Unmarshal reads a string line by line and returns the valid Env key/value pair of valid variables.
       
   132 // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
       
   133 // This function is returning an error if there are any invalid lines.
       
   134 func Unmarshal(str string) (Env, error) {
       
   135 	return strictParse(strings.NewReader(str), false)
       
   136 }
       
   137 
       
   138 // Marshal outputs the given environment as a env file.
       
   139 // Variables will be sorted by name.
       
   140 func Marshal(env Env) (string, error) {
       
   141 	lines := make([]string, 0, len(env))
       
   142 	for k, v := range env {
       
   143 		if d, err := strconv.Atoi(v); err == nil {
       
   144 			lines = append(lines, fmt.Sprintf(`%s=%d`, k, d))
       
   145 		} else {
       
   146 			lines = append(lines, fmt.Sprintf(`%s=%q`, k, v))
       
   147 		}
       
   148 	}
       
   149 	sort.Strings(lines)
       
   150 	return strings.Join(lines, "\n"), nil
       
   151 }
       
   152 
       
   153 // Write serializes the given environment and writes it to a file
       
   154 func Write(env Env, filename string) error {
       
   155 	content, err := Marshal(env)
       
   156 	if err != nil {
       
   157 		return err
       
   158 	}
       
   159 	// ensure the path exists
       
   160 	if err := os.MkdirAll(filepath.Dir(filename), 0o775); err != nil {
       
   161 		return err
       
   162 	}
       
   163 	// create or truncate the file
       
   164 	file, err := os.Create(filename)
       
   165 	if err != nil {
       
   166 		return err
       
   167 	}
       
   168 	defer file.Close()
       
   169 	_, err = file.WriteString(content + "\n")
       
   170 	if err != nil {
       
   171 		return err
       
   172 	}
       
   173 
       
   174 	return file.Sync()
       
   175 }
       
   176 
       
   177 func strictParse(r io.Reader, override bool) (Env, error) {
   121 	env := make(Env)
   178 	env := make(Env)
   122 	scanner := bufio.NewScanner(r)
   179 	scanner := bufio.NewScanner(r)
   123 
   180 
   124 	i := 1
   181 	firstLine := true
   125 	bom := string([]byte{239, 187, 191})
       
   126 
   182 
   127 	for scanner.Scan() {
   183 	for scanner.Scan() {
   128 		line := scanner.Text()
   184 		line := strings.TrimSpace(scanner.Text())
   129 
   185 
   130 		if i == 1 {
   186 		if firstLine {
   131 			line = strings.TrimPrefix(line, bom)
   187 			line = strings.TrimPrefix(line, bom)
   132 		}
   188 			firstLine = false
   133 
   189 		}
   134 		i++
   190 
   135 
   191 		if line == "" || line[0] == '#' {
   136 		err := parseLine(line, env)
   192 			continue
       
   193 		}
       
   194 
       
   195 		quote := ""
       
   196 		// look for the delimiter character
       
   197 		idx := strings.Index(line, "=")
       
   198 		if idx == -1 {
       
   199 			idx = strings.Index(line, ":")
       
   200 		}
       
   201 		// look for a quote character
       
   202 		if idx > 0 && idx < len(line)-1 {
       
   203 			val := strings.TrimSpace(line[idx+1:])
       
   204 			if val[0] == '"' || val[0] == '\'' {
       
   205 				quote = val[:1]
       
   206 				// look for the closing quote character within the same line
       
   207 				idx = strings.LastIndex(strings.TrimSpace(val[1:]), quote)
       
   208 				if idx >= 0 && val[idx] != '\\' {
       
   209 					quote = ""
       
   210 				}
       
   211 			}
       
   212 		}
       
   213 		// look for the closing quote character
       
   214 		for quote != "" && scanner.Scan() {
       
   215 			l := scanner.Text()
       
   216 			line += "\n" + l
       
   217 			idx := strings.LastIndex(l, quote)
       
   218 			if idx > 0 && l[idx-1] == '\\' {
       
   219 				// foud a matching quote character but it's escaped
       
   220 				continue
       
   221 			}
       
   222 			if idx >= 0 {
       
   223 				// foud a matching quote
       
   224 				quote = ""
       
   225 			}
       
   226 		}
       
   227 
       
   228 		if quote != "" {
       
   229 			return env, fmt.Errorf("missing quotes")
       
   230 		}
       
   231 
       
   232 		err := parseLine(line, env, override)
   137 		if err != nil {
   233 		if err != nil {
   138 			return env, err
   234 			return env, err
   139 		}
   235 		}
   140 	}
   236 	}
   141 
   237 
   142 	return env, nil
   238 	return env, nil
   143 }
   239 }
   144 
   240 
   145 func parseLine(s string, env Env) error {
   241 var (
   146 	rl := regexp.MustCompile(linePattern)
   242 	lineRgx     = regexp.MustCompile(linePattern)
   147 	rm := rl.FindStringSubmatch(s)
   243 	unescapeRgx = regexp.MustCompile(`\\([^$])`)
       
   244 	varRgx      = regexp.MustCompile(variablePattern)
       
   245 )
       
   246 
       
   247 func parseLine(s string, env Env, override bool) error {
       
   248 	rm := lineRgx.FindStringSubmatch(s)
   148 
   249 
   149 	if len(rm) == 0 {
   250 	if len(rm) == 0 {
   150 		return checkFormat(s, env)
   251 		return checkFormat(s, env)
   151 	}
   252 	}
   152 
   253 
   153 	key := rm[1]
   254 	key := strings.TrimSpace(rm[1])
   154 	val := rm[2]
   255 	val := strings.TrimSpace(rm[2])
   155 
   256 
   156 	// determine if string has quote prefix
   257 	var hsq, hdq bool
   157 	hdq := strings.HasPrefix(val, `"`)
   258 
   158 
   259 	// check if the value is quoted
   159 	// determine if string has single quote prefix
   260 	if l := len(val); l >= 2 {
   160 	hsq := strings.HasPrefix(val, `'`)
   261 		l -= 1
   161 
   262 		// has double quotes
   162 	// trim whitespace
   263 		hdq = val[0] == '"' && val[l] == '"'
   163 	val = strings.Trim(val, " ")
   264 		// has single quotes
   164 
   265 		hsq = val[0] == '\'' && val[l] == '\''
   165 	// remove quotes '' or ""
   266 
   166 	rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`)
   267 		// remove quotes '' or ""
   167 	val = rq.ReplaceAllString(val, "$2")
   268 		if hsq || hdq {
       
   269 			val = val[1:l]
       
   270 		}
       
   271 	}
   168 
   272 
   169 	if hdq {
   273 	if hdq {
   170 		val = strings.Replace(val, `\n`, "\n", -1)
   274 		val = strings.ReplaceAll(val, `\n`, "\n")
   171 		val = strings.Replace(val, `\r`, "\r", -1)
   275 		val = strings.ReplaceAll(val, `\r`, "\r")
   172 
   276 
   173 		// Unescape all characters except $ so variables can be escaped properly
   277 		// Unescape all characters except $ so variables can be escaped properly
   174 		re := regexp.MustCompile(`\\([^$])`)
   278 		val = unescapeRgx.ReplaceAllString(val, "$1")
   175 		val = re.ReplaceAllString(val, "$1")
   279 	}
   176 	}
   280 
   177 
   281 	if !hsq {
   178 	rv := regexp.MustCompile(variablePattern)
   282 		fv := func(s string) string {
   179 	fv := func(s string) string {
   283 			return varReplacement(s, hsq, env, override)
   180 		return varReplacement(s, hsq, env)
   284 		}
   181 	}
   285 		val = varRgx.ReplaceAllStringFunc(val, fv)
   182 
   286 		val = parseVal(val, env, hdq, override)
   183 	val = rv.ReplaceAllStringFunc(val, fv)
   287 	}
   184 	val = parseVal(val, env)
       
   185 
   288 
   186 	env[key] = val
   289 	env[key] = val
   187 	return nil
   290 	return nil
   188 }
   291 }
   189 
   292 
   199 	}
   302 	}
   200 
   303 
   201 	return nil
   304 	return nil
   202 }
   305 }
   203 
   306 
   204 func varReplacement(s string, hsq bool, env Env) string {
   307 var varNameRgx = regexp.MustCompile(`(\$)(\{?([A-Z0-9_]+)\}?)`)
   205 	if strings.HasPrefix(s, "\\") {
   308 
   206 		return strings.TrimPrefix(s, "\\")
   309 func varReplacement(s string, hsq bool, env Env, override bool) string {
       
   310 	if s == "" {
       
   311 		return s
       
   312 	}
       
   313 
       
   314 	if s[0] == '\\' {
       
   315 		// the dollar sign is escaped
       
   316 		return s[1:]
   207 	}
   317 	}
   208 
   318 
   209 	if hsq {
   319 	if hsq {
   210 		return s
   320 		return s
   211 	}
   321 	}
   212 
   322 
   213 	sn := `(\$)(\{?([A-Z0-9_]+)\}?)`
   323 	mn := varNameRgx.FindStringSubmatch(s)
   214 	rn := regexp.MustCompile(sn)
       
   215 	mn := rn.FindStringSubmatch(s)
       
   216 
   324 
   217 	if len(mn) == 0 {
   325 	if len(mn) == 0 {
   218 		return s
   326 		return s
   219 	}
   327 	}
   220 
   328 
   221 	v := mn[3]
   329 	v := mn[3]
   222 
   330 
   223 	replace, ok := env[v]
   331 	if replace, ok := os.LookupEnv(v); ok && !override {
   224 	if !ok {
   332 		return replace
   225 		replace = os.Getenv(v)
   333 	}
   226 	}
   334 
   227 
   335 	if replace, ok := env[v]; ok {
   228 	return replace
   336 		return replace
       
   337 	}
       
   338 
       
   339 	return os.Getenv(v)
   229 }
   340 }
   230 
   341 
   231 func checkFormat(s string, env Env) error {
   342 func checkFormat(s string, env Env) error {
   232 	st := strings.TrimSpace(s)
   343 	st := strings.TrimSpace(s)
   233 
   344 
   234 	if (st == "") || strings.HasPrefix(st, "#") {
   345 	if st == "" || st[0] == '#' {
   235 		return nil
   346 		return nil
   236 	}
   347 	}
   237 
   348 
   238 	if err := parseExport(st, env); err != nil {
   349 	if err := parseExport(st, env); err != nil {
   239 		return err
   350 		return err
   240 	}
   351 	}
   241 
   352 
   242 	return fmt.Errorf("line `%s` doesn't match format", s)
   353 	return fmt.Errorf("line `%s` doesn't match format", s)
   243 }
   354 }
   244 
   355 
   245 func parseVal(val string, env Env) string {
   356 func parseVal(val string, env Env, ignoreNewlines bool, override bool) string {
   246 	if strings.Contains(val, "=") {
   357 	if strings.Contains(val, "=") && !ignoreNewlines {
   247 		if !(val == "\n" || val == "\r") {
   358 		kv := strings.Split(val, "\r")
   248 			kv := strings.Split(val, "\n")
   359 
   249 
   360 		if len(kv) > 1 {
   250 			if len(kv) == 1 {
   361 			val = kv[0]
   251 				kv = strings.Split(val, "\r")
   362 			for _, l := range kv[1:] {
   252 			}
   363 				_ = parseLine(l, env, override)
   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 			}
   364 			}
   261 		}
   365 		}
   262 	}
   366 	}
   263 
   367 
   264 	return val
   368 	return val