vendor/github.com/subosito/gotenv/gotenv.go
changeset 251 1c52a0eeb952
child 260 445e01aede7e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/github.com/subosito/gotenv/gotenv.go	Sun Feb 16 18:54:01 2020 +0100
@@ -0,0 +1,265 @@
+// Package gotenv provides functionality to dynamically load the environment variables
+package gotenv
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"regexp"
+	"strings"
+)
+
+const (
+	// Pattern for detecting valid line format
+	linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`
+
+	// Pattern for detecting valid variable within a value
+	variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
+)
+
+// Env holds key/value pair of valid environment variable
+type Env map[string]string
+
+/*
+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.
+When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
+Otherwise, it will loop over the filenames parameter and set the proper environment variables.
+*/
+func Load(filenames ...string) error {
+	return loadenv(false, filenames...)
+}
+
+/*
+OverLoad is a function to load a file or multiple files and then export and override the valid variables into environment variables.
+*/
+func OverLoad(filenames ...string) error {
+	return loadenv(true, filenames...)
+}
+
+/*
+Must is wrapper function that will panic when supplied function returns an error.
+*/
+func Must(fn func(filenames ...string) error, filenames ...string) {
+	if err := fn(filenames...); err != nil {
+		panic(err.Error())
+	}
+}
+
+/*
+Apply is a function to load an io Reader then export the valid variables into environment variables if they do not exist.
+*/
+func Apply(r io.Reader) error {
+	return parset(r, false)
+}
+
+/*
+OverApply is a function to load an io Reader then export and override the valid variables into environment variables.
+*/
+func OverApply(r io.Reader) error {
+	return parset(r, true)
+}
+
+func loadenv(override bool, filenames ...string) error {
+	if len(filenames) == 0 {
+		filenames = []string{".env"}
+	}
+
+	for _, filename := range filenames {
+		f, err := os.Open(filename)
+		if err != nil {
+			return err
+		}
+
+		err = parset(f, override)
+		if err != nil {
+			return err
+		}
+
+		f.Close()
+	}
+
+	return nil
+}
+
+// parse and set :)
+func parset(r io.Reader, override bool) error {
+	env, err := StrictParse(r)
+	if err != nil {
+		return err
+	}
+
+	for key, val := range env {
+		setenv(key, val, override)
+	}
+
+	return nil
+}
+
+func setenv(key, val string, override bool) {
+	if override {
+		os.Setenv(key, val)
+	} else {
+		if _, present := os.LookupEnv(key); !present {
+			os.Setenv(key, val)
+		}
+	}
+}
+
+// Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
+// It expands the value of a variable from the environment variable but does not set the value to the environment itself.
+// This function is skipping any invalid lines and only processing the valid one.
+func Parse(r io.Reader) Env {
+	env, _ := StrictParse(r)
+	return env
+}
+
+// StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
+// It expands the value of a variable from the environment variable but does not set the value to the environment itself.
+// This function is returning an error if there are any invalid lines.
+func StrictParse(r io.Reader) (Env, error) {
+	env := make(Env)
+	scanner := bufio.NewScanner(r)
+
+	i := 1
+	bom := string([]byte{239, 187, 191})
+
+	for scanner.Scan() {
+		line := scanner.Text()
+
+		if i == 1 {
+			line = strings.TrimPrefix(line, bom)
+		}
+
+		i++
+
+		err := parseLine(line, env)
+		if err != nil {
+			return env, err
+		}
+	}
+
+	return env, nil
+}
+
+func parseLine(s string, env Env) error {
+	rl := regexp.MustCompile(linePattern)
+	rm := rl.FindStringSubmatch(s)
+
+	if len(rm) == 0 {
+		return checkFormat(s, env)
+	}
+
+	key := rm[1]
+	val := rm[2]
+
+	// determine if string has quote prefix
+	hdq := strings.HasPrefix(val, `"`)
+
+	// determine if string has single quote prefix
+	hsq := strings.HasPrefix(val, `'`)
+
+	// trim whitespace
+	val = strings.Trim(val, " ")
+
+	// remove quotes '' or ""
+	rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`)
+	val = rq.ReplaceAllString(val, "$2")
+
+	if hdq {
+		val = strings.Replace(val, `\n`, "\n", -1)
+		val = strings.Replace(val, `\r`, "\r", -1)
+
+		// Unescape all characters except $ so variables can be escaped properly
+		re := regexp.MustCompile(`\\([^$])`)
+		val = re.ReplaceAllString(val, "$1")
+	}
+
+	rv := regexp.MustCompile(variablePattern)
+	fv := func(s string) string {
+		return varReplacement(s, hsq, env)
+	}
+
+	val = rv.ReplaceAllStringFunc(val, fv)
+	val = parseVal(val, env)
+
+	env[key] = val
+	return nil
+}
+
+func parseExport(st string, env Env) error {
+	if strings.HasPrefix(st, "export") {
+		vs := strings.SplitN(st, " ", 2)
+
+		if len(vs) > 1 {
+			if _, ok := env[vs[1]]; !ok {
+				return fmt.Errorf("line `%s` has an unset variable", st)
+			}
+		}
+	}
+
+	return nil
+}
+
+func varReplacement(s string, hsq bool, env Env) string {
+	if strings.HasPrefix(s, "\\") {
+		return strings.TrimPrefix(s, "\\")
+	}
+
+	if hsq {
+		return s
+	}
+
+	sn := `(\$)(\{?([A-Z0-9_]+)\}?)`
+	rn := regexp.MustCompile(sn)
+	mn := rn.FindStringSubmatch(s)
+
+	if len(mn) == 0 {
+		return s
+	}
+
+	v := mn[3]
+
+	replace, ok := env[v]
+	if !ok {
+		replace = os.Getenv(v)
+	}
+
+	return replace
+}
+
+func checkFormat(s string, env Env) error {
+	st := strings.TrimSpace(s)
+
+	if (st == "") || strings.HasPrefix(st, "#") {
+		return nil
+	}
+
+	if err := parseExport(st, env); err != nil {
+		return err
+	}
+
+	return fmt.Errorf("line `%s` doesn't match format", s)
+}
+
+func parseVal(val string, env Env) string {
+	if strings.Contains(val, "=") {
+		if !(val == "\n" || val == "\r") {
+			kv := strings.Split(val, "\n")
+
+			if len(kv) == 1 {
+				kv = strings.Split(val, "\r")
+			}
+
+			if len(kv) > 1 {
+				val = kv[0]
+
+				for i := 1; i < len(kv); i++ {
+					parseLine(kv[i], env)
+				}
+			}
+		}
+	}
+
+	return val
+}