diff -r db4911b0c721 -r 445e01aede7e vendor/github.com/subosito/gotenv/gotenv.go --- a/vendor/github.com/subosito/gotenv/gotenv.go Tue Aug 23 22:33:28 2022 +0200 +++ b/vendor/github.com/subosito/gotenv/gotenv.go Tue Aug 23 22:39:43 2022 +0200 @@ -6,7 +6,10 @@ "fmt" "io" "os" + "path/filepath" "regexp" + "sort" + "strconv" "strings" ) @@ -16,46 +19,39 @@ // Pattern for detecting valid variable within a value variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)` + + // Byte order mark character + bom = "\xef\xbb\xbf" ) // 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. -*/ +// 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. -*/ +// 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. -*/ +// 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. -*/ +// 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. -*/ +// 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) } @@ -72,11 +68,10 @@ } err = parset(f, override) + f.Close() if err != nil { return err } - - f.Close() } return nil @@ -84,7 +79,7 @@ // parse and set :) func parset(r io.Reader, override bool) error { - env, err := StrictParse(r) + env, err := strictParse(r, override) if err != nil { return err } @@ -110,7 +105,7 @@ // 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) + env, _ := strictParse(r, false) return env } @@ -118,22 +113,123 @@ // 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) { + return strictParse(r, false) +} + +// Read is a function to parse a file line by line 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 Read(filename string) (Env, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + return strictParse(f, false) +} + +// Unmarshal reads a string line by line 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 Unmarshal(str string) (Env, error) { + return strictParse(strings.NewReader(str), false) +} + +// Marshal outputs the given environment as a env file. +// Variables will be sorted by name. +func Marshal(env Env) (string, error) { + lines := make([]string, 0, len(env)) + for k, v := range env { + if d, err := strconv.Atoi(v); err == nil { + lines = append(lines, fmt.Sprintf(`%s=%d`, k, d)) + } else { + lines = append(lines, fmt.Sprintf(`%s=%q`, k, v)) + } + } + sort.Strings(lines) + return strings.Join(lines, "\n"), nil +} + +// Write serializes the given environment and writes it to a file +func Write(env Env, filename string) error { + content, err := Marshal(env) + if err != nil { + return err + } + // ensure the path exists + if err := os.MkdirAll(filepath.Dir(filename), 0o775); err != nil { + return err + } + // create or truncate the file + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(content + "\n") + if err != nil { + return err + } + + return file.Sync() +} + +func strictParse(r io.Reader, override bool) (Env, error) { env := make(Env) scanner := bufio.NewScanner(r) - i := 1 - bom := string([]byte{239, 187, 191}) + firstLine := true for scanner.Scan() { - line := scanner.Text() + line := strings.TrimSpace(scanner.Text()) - if i == 1 { + if firstLine { line = strings.TrimPrefix(line, bom) + firstLine = false + } + + if line == "" || line[0] == '#' { + continue } - i++ + quote := "" + // look for the delimiter character + idx := strings.Index(line, "=") + if idx == -1 { + idx = strings.Index(line, ":") + } + // look for a quote character + if idx > 0 && idx < len(line)-1 { + val := strings.TrimSpace(line[idx+1:]) + if val[0] == '"' || val[0] == '\'' { + quote = val[:1] + // look for the closing quote character within the same line + idx = strings.LastIndex(strings.TrimSpace(val[1:]), quote) + if idx >= 0 && val[idx] != '\\' { + quote = "" + } + } + } + // look for the closing quote character + for quote != "" && scanner.Scan() { + l := scanner.Text() + line += "\n" + l + idx := strings.LastIndex(l, quote) + if idx > 0 && l[idx-1] == '\\' { + // foud a matching quote character but it's escaped + continue + } + if idx >= 0 { + // foud a matching quote + quote = "" + } + } - err := parseLine(line, env) + if quote != "" { + return env, fmt.Errorf("missing quotes") + } + + err := parseLine(line, env, override) if err != nil { return env, err } @@ -142,47 +238,54 @@ return env, nil } -func parseLine(s string, env Env) error { - rl := regexp.MustCompile(linePattern) - rm := rl.FindStringSubmatch(s) +var ( + lineRgx = regexp.MustCompile(linePattern) + unescapeRgx = regexp.MustCompile(`\\([^$])`) + varRgx = regexp.MustCompile(variablePattern) +) + +func parseLine(s string, env Env, override bool) error { + rm := lineRgx.FindStringSubmatch(s) if len(rm) == 0 { return checkFormat(s, env) } - key := rm[1] - val := rm[2] + key := strings.TrimSpace(rm[1]) + val := strings.TrimSpace(rm[2]) - // determine if string has quote prefix - hdq := strings.HasPrefix(val, `"`) + var hsq, hdq bool - // determine if string has single quote prefix - hsq := strings.HasPrefix(val, `'`) + // check if the value is quoted + if l := len(val); l >= 2 { + l -= 1 + // has double quotes + hdq = val[0] == '"' && val[l] == '"' + // has single quotes + hsq = val[0] == '\'' && val[l] == '\'' - // trim whitespace - val = strings.Trim(val, " ") - - // remove quotes '' or "" - rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`) - val = rq.ReplaceAllString(val, "$2") + // remove quotes '' or "" + if hsq || hdq { + val = val[1:l] + } + } if hdq { - val = strings.Replace(val, `\n`, "\n", -1) - val = strings.Replace(val, `\r`, "\r", -1) + val = strings.ReplaceAll(val, `\n`, "\n") + val = strings.ReplaceAll(val, `\r`, "\r") // Unescape all characters except $ so variables can be escaped properly - re := regexp.MustCompile(`\\([^$])`) - val = re.ReplaceAllString(val, "$1") + val = unescapeRgx.ReplaceAllString(val, "$1") } - rv := regexp.MustCompile(variablePattern) - fv := func(s string) string { - return varReplacement(s, hsq, env) + if !hsq { + fv := func(s string) string { + return varReplacement(s, hsq, env, override) + } + val = varRgx.ReplaceAllStringFunc(val, fv) + val = parseVal(val, env, hdq, override) } - val = rv.ReplaceAllStringFunc(val, fv) - val = parseVal(val, env) - env[key] = val return nil } @@ -201,18 +304,23 @@ return nil } -func varReplacement(s string, hsq bool, env Env) string { - if strings.HasPrefix(s, "\\") { - return strings.TrimPrefix(s, "\\") +var varNameRgx = regexp.MustCompile(`(\$)(\{?([A-Z0-9_]+)\}?)`) + +func varReplacement(s string, hsq bool, env Env, override bool) string { + if s == "" { + return s + } + + if s[0] == '\\' { + // the dollar sign is escaped + return s[1:] } if hsq { return s } - sn := `(\$)(\{?([A-Z0-9_]+)\}?)` - rn := regexp.MustCompile(sn) - mn := rn.FindStringSubmatch(s) + mn := varNameRgx.FindStringSubmatch(s) if len(mn) == 0 { return s @@ -220,18 +328,21 @@ v := mn[3] - replace, ok := env[v] - if !ok { - replace = os.Getenv(v) + if replace, ok := os.LookupEnv(v); ok && !override { + return replace } - return replace + if replace, ok := env[v]; ok { + return replace + } + + return os.Getenv(v) } func checkFormat(s string, env Env) error { st := strings.TrimSpace(s) - if (st == "") || strings.HasPrefix(st, "#") { + if st == "" || st[0] == '#' { return nil } @@ -242,21 +353,14 @@ 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") +func parseVal(val string, env Env, ignoreNewlines bool, override bool) string { + if strings.Contains(val, "=") && !ignoreNewlines { + kv := strings.Split(val, "\r") - 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) - } + if len(kv) > 1 { + val = kv[0] + for _, l := range kv[1:] { + _ = parseLine(l, env, override) } } }