From 6504fc822313b18ed470d6438ffee32f37855518 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:30:01 +0000 Subject: [PATCH] chore(deps): bump github.com/joho/godotenv from 1.3.0 to 1.5.1 Bumps [github.com/joho/godotenv](https://github.com/joho/godotenv) from 1.3.0 to 1.5.1. - [Release notes](https://github.com/joho/godotenv/releases) - [Commits](https://github.com/joho/godotenv/compare/v1.3.0...v1.5.1) --- updated-dependencies: - dependency-name: github.com/joho/godotenv dependency-version: 1.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/joho/godotenv/.travis.yml | 8 - vendor/github.com/joho/godotenv/README.md | 65 ++++- vendor/github.com/joho/godotenv/godotenv.go | 246 +++++------------- vendor/github.com/joho/godotenv/parser.go | 271 ++++++++++++++++++++ vendor/modules.txt | 4 +- 7 files changed, 392 insertions(+), 208 deletions(-) delete mode 100644 vendor/github.com/joho/godotenv/.travis.yml create mode 100644 vendor/github.com/joho/godotenv/parser.go diff --git a/go.mod b/go.mod index 0d224fee05..171107ec6c 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/invopop/jsonschema v0.4.0 github.com/jessevdk/go-flags v1.4.0 - github.com/joho/godotenv v1.3.0 + github.com/joho/godotenv v1.5.1 github.com/json-iterator/go v1.1.12 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 github.com/loft-sh/go-github-selfupdate v1.0.0 diff --git a/go.sum b/go.sum index 5930af4bf0..73b23b76e8 100644 --- a/go.sum +++ b/go.sum @@ -294,8 +294,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= diff --git a/vendor/github.com/joho/godotenv/.travis.yml b/vendor/github.com/joho/godotenv/.travis.yml deleted file mode 100644 index f0db1adcdb..0000000000 --- a/vendor/github.com/joho/godotenv/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: go - -go: - - 1.x - -os: - - linux - - osx diff --git a/vendor/github.com/joho/godotenv/README.md b/vendor/github.com/joho/godotenv/README.md index 4e8fcf2e9c..bfbe66a0e9 100644 --- a/vendor/github.com/joho/godotenv/README.md +++ b/vendor/github.com/joho/godotenv/README.md @@ -1,6 +1,6 @@ -# GoDotEnv [![Build Status](https://travis-ci.org/joho/godotenv.svg?branch=master)](https://travis-ci.org/joho/godotenv) [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4?svg=true)](https://ci.appveyor.com/project/joho/godotenv) [![Go Report Card](https://goreportcard.com/badge/github.com/joho/godotenv)](https://goreportcard.com/report/github.com/joho/godotenv) +# GoDotEnv ![CI](https://github.com/joho/godotenv/workflows/CI/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/joho/godotenv)](https://goreportcard.com/report/github.com/joho/godotenv) -A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file) +A Go (golang) port of the Ruby [dotenv](https://github.com/bkeepers/dotenv) project (which loads env vars from a .env file). From the original Library: @@ -8,9 +8,9 @@ From the original Library: > > But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped. -It can be used as a library (for loading in env for your own daemons etc) or as a bin command. +It can be used as a library (for loading in env for your own daemons etc.) or as a bin command. -There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows. +There is test coverage and CI for both linuxish and Windows environments, but I make no guarantees about the bin version working on Windows. ## Installation @@ -21,6 +21,13 @@ go get github.com/joho/godotenv ``` or if you want to use it as a bin command + +go >= 1.17 +```shell +go install github.com/joho/godotenv/cmd/godotenv@latest +``` + +go < 1.17 ```shell go get github.com/joho/godotenv/cmd/godotenv ``` @@ -40,9 +47,10 @@ Then in your Go app you can do something like package main import ( - "github.com/joho/godotenv" "log" "os" + + "github.com/joho/godotenv" ) func main() { @@ -67,8 +75,8 @@ import _ "github.com/joho/godotenv/autoload" While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit ```go -_ = godotenv.Load("somerandomfile") -_ = godotenv.Load("filenumberone.env", "filenumbertwo.env") +godotenv.Load("somerandomfile") +godotenv.Load("filenumberone.env", "filenumbertwo.env") ``` If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) @@ -110,6 +118,31 @@ content := getRemoteFileContent() myEnv, err := godotenv.Unmarshal(content) ``` +### Precedence & Conventions + +Existing envs take precedence of envs that are loaded later. + +The [convention](https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use) +for managing multiple environments (i.e. development, test, production) +is to create an env named `{YOURAPP}_ENV` and load envs in this order: + +```go +env := os.Getenv("FOO_ENV") +if "" == env { + env = "development" +} + +godotenv.Load(".env." + env + ".local") +if "test" != env { + godotenv.Load(".env.local") +} +godotenv.Load(".env." + env) +godotenv.Load() // The Original .env +``` + +If you need to, you can also use `godotenv.Overload()` to defy this convention +and overwrite existing envs instead of only supplanting them. Use with caution. + ### Command Mode Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH` @@ -120,6 +153,8 @@ godotenv -f /some/path/to/.env some_command with some args If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD` +By default, it won't override existing environment variables; you can do that with the `-o` flag. + ### Writing Env Files Godotenv can also write a map representing the environment to a correctly-formatted and escaped file @@ -138,9 +173,17 @@ content, err := godotenv.Marshal(env) ## Contributing -Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases. +Contributions are welcome, but with some caveats. -*code changes without tests will not be accepted* +This library has been declared feature complete (see [#182](https://github.com/joho/godotenv/issues/182) for background) and will not be accepting issues or pull requests adding new functionality or breaking the library API. + +Contributions would be gladly accepted that: + +* bring this library's parsing into closer compatibility with the mainline dotenv implementations, in particular [Ruby's dotenv](https://github.com/bkeepers/dotenv) and [Node.js' dotenv](https://github.com/motdotla/dotenv) +* keep the library up to date with the go ecosystem (ie CI bumps, documentation changes, changes in the core libraries) +* bug fixes for use cases that pertain to the library's purpose of easing development of codebases deployed into twelve factor environments + +*code changes without tests and references to peer dotenv implementations will not be accepted* 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) @@ -154,10 +197,6 @@ Releases should follow [Semver](http://semver.org/) though the first couple of r Use [annotated tags for all releases](https://github.com/joho/godotenv/issues/30). Example `git tag -a v1.2.1` -## CI - -Linux: [![Build Status](https://travis-ci.org/joho/godotenv.svg?branch=master)](https://travis-ci.org/joho/godotenv) Windows: [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4)](https://ci.appveyor.com/project/joho/godotenv) - ## Who? The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](https://johnbarton.co/) based off the tests/fixtures in the original library. diff --git a/vendor/github.com/joho/godotenv/godotenv.go b/vendor/github.com/joho/godotenv/godotenv.go index 29b436c77c..61b0ebbafa 100644 --- a/vendor/github.com/joho/godotenv/godotenv.go +++ b/vendor/github.com/joho/godotenv/godotenv.go @@ -1,43 +1,53 @@ // Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv) // -// Examples/readme can be found on the github page at https://github.com/joho/godotenv +// Examples/readme can be found on the GitHub page at https://github.com/joho/godotenv // // The TL;DR is that you make a .env file that looks something like // -// SOME_ENV_VAR=somevalue +// SOME_ENV_VAR=somevalue // // and then in your go code you can call // -// godotenv.Load() +// godotenv.Load() // // and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR") package godotenv import ( - "bufio" - "errors" + "bytes" "fmt" "io" "os" "os/exec" - "regexp" "sort" + "strconv" "strings" ) const doubleQuoteSpecialChars = "\\\n\r\"!$`" +// Parse reads an env file from io.Reader, returning a map of keys and values. +func Parse(r io.Reader) (map[string]string, error) { + var buf bytes.Buffer + _, err := io.Copy(&buf, r) + if err != nil { + return nil, err + } + + return UnmarshalBytes(buf.Bytes()) +} + // Load will read your env file(s) and load them into ENV for this process. // -// Call this function as close as possible to the start of your program (ideally in main) +// Call this function as close as possible to the start of your program (ideally in main). // -// If you call Load without any args it will default to loading .env in the current path +// If you call Load without any args it will default to loading .env in the current path. // -// You can otherwise tell it which files to load (there can be more than one) like +// You can otherwise tell it which files to load (there can be more than one) like: // -// godotenv.Load("fileone", "filetwo") +// godotenv.Load("fileone", "filetwo") // -// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults +// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults. func Load(filenames ...string) (err error) { filenames = filenamesOrDefault(filenames) @@ -52,15 +62,15 @@ func Load(filenames ...string) (err error) { // Overload will read your env file(s) and load them into ENV for this process. // -// Call this function as close as possible to the start of your program (ideally in main) +// Call this function as close as possible to the start of your program (ideally in main). // -// If you call Overload without any args it will default to loading .env in the current path +// If you call Overload without any args it will default to loading .env in the current path. // -// You can otherwise tell it which files to load (there can be more than one) like +// You can otherwise tell it which files to load (there can be more than one) like: // -// godotenv.Overload("fileone", "filetwo") +// godotenv.Overload("fileone", "filetwo") // -// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars. +// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefully set all vars. func Overload(filenames ...string) (err error) { filenames = filenamesOrDefault(filenames) @@ -95,48 +105,34 @@ func Read(filenames ...string) (envMap map[string]string, err error) { return } -// Parse reads an env file from io.Reader, returning a map of keys and values. -func Parse(r io.Reader) (envMap map[string]string, err error) { - envMap = make(map[string]string) - - var lines []string - scanner := bufio.NewScanner(r) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - - if err = scanner.Err(); err != nil { - return - } - - for _, fullLine := range lines { - if !isIgnoredLine(fullLine) { - var key, value string - key, value, err = parseLine(fullLine, envMap) - - if err != nil { - return - } - envMap[key] = value - } - } - return +// Unmarshal reads an env file from a string, returning a map of keys and values. +func Unmarshal(str string) (envMap map[string]string, err error) { + return UnmarshalBytes([]byte(str)) } -//Unmarshal reads an env file from a string, returning a map of keys and values. -func Unmarshal(str string) (envMap map[string]string, err error) { - return Parse(strings.NewReader(str)) +// UnmarshalBytes parses env file from byte slice of chars, returning a map of keys and values. +func UnmarshalBytes(src []byte) (map[string]string, error) { + out := make(map[string]string) + err := parseBytes(src, out) + + return out, err } // Exec loads env vars from the specified filenames (empty map falls back to default) // then executes the cmd specified. // -// Simply hooks up os.Stdin/err/out to the command and calls Run() +// Simply hooks up os.Stdin/err/out to the command and calls Run(). // // If you want more fine grained control over your command it's recommended -// that you use `Load()` or `Read()` and the `os/exec` package yourself. -func Exec(filenames []string, cmd string, cmdArgs []string) error { - Load(filenames...) +// that you use `Load()`, `Overload()` or `Read()` and the `os/exec` package yourself. +func Exec(filenames []string, cmd string, cmdArgs []string, overload bool) error { + op := Load + if overload { + op = Overload + } + if err := op(filenames...); err != nil { + return err + } command := exec.Command(cmd, cmdArgs...) command.Stdin = os.Stdin @@ -145,18 +141,22 @@ func Exec(filenames []string, cmd string, cmdArgs []string) error { return command.Run() } -// Write serializes the given environment and writes it to a file +// Write serializes the given environment and writes it to a file. func Write(envMap map[string]string, filename string) error { - content, error := Marshal(envMap) - if error != nil { - return error + content, err := Marshal(envMap) + if err != nil { + return err + } + file, err := os.Create(filename) + if err != nil { + return err } - file, error := os.Create(filename) - if error != nil { - return error + defer file.Close() + _, err = file.WriteString(content + "\n") + if err != nil { + return err } - _, err := file.WriteString(content) - return err + return file.Sync() } // Marshal outputs the given environment as a dotenv-formatted environment file. @@ -164,7 +164,11 @@ func Write(envMap map[string]string, filename string) error { func Marshal(envMap map[string]string) (string, error) { lines := make([]string, 0, len(envMap)) for k, v := range envMap { - lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) + if d, err := strconv.Atoi(v); err == nil { + lines = append(lines, fmt.Sprintf(`%s=%d`, k, d)) + } else { + lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) + } } sort.Strings(lines) return strings.Join(lines, "\n"), nil @@ -192,7 +196,7 @@ func loadFile(filename string, overload bool) error { for key, value := range envMap { if !currentEnv[key] || overload { - os.Setenv(key, value) + _ = os.Setenv(key, value) } } @@ -209,128 +213,6 @@ func readFile(filename string) (envMap map[string]string, err error) { return Parse(file) } -func parseLine(line string, envMap map[string]string) (key string, value string, err error) { - if len(line) == 0 { - err = errors.New("zero length string") - return - } - - // ditch the comments (but keep quoted hashes) - if strings.Contains(line, "#") { - segmentsBetweenHashes := strings.Split(line, "#") - quotesAreOpen := false - var segmentsToKeep []string - for _, segment := range segmentsBetweenHashes { - if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { - if quotesAreOpen { - quotesAreOpen = false - segmentsToKeep = append(segmentsToKeep, segment) - } else { - quotesAreOpen = true - } - } - - if len(segmentsToKeep) == 0 || quotesAreOpen { - segmentsToKeep = append(segmentsToKeep, segment) - } - } - - line = strings.Join(segmentsToKeep, "#") - } - - firstEquals := strings.Index(line, "=") - firstColon := strings.Index(line, ":") - splitString := strings.SplitN(line, "=", 2) - if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) { - //this is a yaml-style line - splitString = strings.SplitN(line, ":", 2) - } - - if len(splitString) != 2 { - err = errors.New("Can't separate key from value") - return - } - - // Parse the key - key = splitString[0] - if strings.HasPrefix(key, "export") { - key = strings.TrimPrefix(key, "export") - } - key = strings.Trim(key, " ") - - // Parse the value - value = parseValue(splitString[1], envMap) - return -} - -func parseValue(value string, envMap map[string]string) string { - - // trim - value = strings.Trim(value, " ") - - // check if we've got quoted values or possible escapes - if len(value) > 1 { - rs := regexp.MustCompile(`\A'(.*)'\z`) - singleQuotes := rs.FindStringSubmatch(value) - - rd := regexp.MustCompile(`\A"(.*)"\z`) - doubleQuotes := rd.FindStringSubmatch(value) - - if singleQuotes != nil || doubleQuotes != nil { - // pull the quotes off the edges - value = value[1 : len(value)-1] - } - - if doubleQuotes != nil { - // expand newlines - escapeRegex := regexp.MustCompile(`\\.`) - value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string { - c := strings.TrimPrefix(match, `\`) - switch c { - case "n": - return "\n" - case "r": - return "\r" - default: - return match - } - }) - // unescape characters - e := regexp.MustCompile(`\\([^$])`) - value = e.ReplaceAllString(value, "$1") - } - - if singleQuotes == nil { - value = expandVariables(value, envMap) - } - } - - return value -} - -func expandVariables(v string, m map[string]string) string { - r := regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) - - return r.ReplaceAllStringFunc(v, func(s string) string { - submatch := r.FindStringSubmatch(s) - - if submatch == nil { - return s - } - if submatch[1] == "\\" || submatch[2] == "(" { - return submatch[0][1:] - } else if submatch[4] != "" { - return m[submatch[4]] - } - return s - }) -} - -func isIgnoredLine(line string) bool { - trimmedLine := strings.Trim(line, " \n\t") - return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") -} - func doubleQuoteEscape(line string) string { for _, c := range doubleQuoteSpecialChars { toReplace := "\\" + string(c) diff --git a/vendor/github.com/joho/godotenv/parser.go b/vendor/github.com/joho/godotenv/parser.go new file mode 100644 index 0000000000..cc709af897 --- /dev/null +++ b/vendor/github.com/joho/godotenv/parser.go @@ -0,0 +1,271 @@ +package godotenv + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strings" + "unicode" +) + +const ( + charComment = '#' + prefixSingleQuote = '\'' + prefixDoubleQuote = '"' + + exportPrefix = "export" +) + +func parseBytes(src []byte, out map[string]string) error { + src = bytes.Replace(src, []byte("\r\n"), []byte("\n"), -1) + cutset := src + for { + cutset = getStatementStart(cutset) + if cutset == nil { + // reached end of file + break + } + + key, left, err := locateKeyName(cutset) + if err != nil { + return err + } + + value, left, err := extractVarValue(left, out) + if err != nil { + return err + } + + out[key] = value + cutset = left + } + + return nil +} + +// getStatementPosition returns position of statement begin. +// +// It skips any comment line or non-whitespace character. +func getStatementStart(src []byte) []byte { + pos := indexOfNonSpaceChar(src) + if pos == -1 { + return nil + } + + src = src[pos:] + if src[0] != charComment { + return src + } + + // skip comment section + pos = bytes.IndexFunc(src, isCharFunc('\n')) + if pos == -1 { + return nil + } + + return getStatementStart(src[pos:]) +} + +// locateKeyName locates and parses key name and returns rest of slice +func locateKeyName(src []byte) (key string, cutset []byte, err error) { + // trim "export" and space at beginning + src = bytes.TrimLeftFunc(src, isSpace) + if bytes.HasPrefix(src, []byte(exportPrefix)) { + trimmed := bytes.TrimPrefix(src, []byte(exportPrefix)) + if bytes.IndexFunc(trimmed, isSpace) == 0 { + src = bytes.TrimLeftFunc(trimmed, isSpace) + } + } + + // locate key name end and validate it in single loop + offset := 0 +loop: + for i, char := range src { + rchar := rune(char) + if isSpace(rchar) { + continue + } + + switch char { + case '=', ':': + // library also supports yaml-style value declaration + key = string(src[0:i]) + offset = i + 1 + break loop + case '_': + default: + // variable name should match [A-Za-z0-9_.] + if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) || rchar == '.' { + continue + } + + return "", nil, fmt.Errorf( + `unexpected character %q in variable name near %q`, + string(char), string(src)) + } + } + + if len(src) == 0 { + return "", nil, errors.New("zero length string") + } + + // trim whitespace + key = strings.TrimRightFunc(key, unicode.IsSpace) + cutset = bytes.TrimLeftFunc(src[offset:], isSpace) + return key, cutset, nil +} + +// extractVarValue extracts variable value and returns rest of slice +func extractVarValue(src []byte, vars map[string]string) (value string, rest []byte, err error) { + quote, hasPrefix := hasQuotePrefix(src) + if !hasPrefix { + // unquoted value - read until end of line + endOfLine := bytes.IndexFunc(src, isLineEnd) + + // Hit EOF without a trailing newline + if endOfLine == -1 { + endOfLine = len(src) + + if endOfLine == 0 { + return "", nil, nil + } + } + + // Convert line to rune away to do accurate countback of runes + line := []rune(string(src[0:endOfLine])) + + // Assume end of line is end of var + endOfVar := len(line) + if endOfVar == 0 { + return "", src[endOfLine:], nil + } + + // Work backwards to check if the line ends in whitespace then + // a comment (ie asdasd # some comment) + for i := endOfVar - 1; i >= 0; i-- { + if line[i] == charComment && i > 0 { + if isSpace(line[i-1]) { + endOfVar = i + break + } + } + } + + trimmed := strings.TrimFunc(string(line[0:endOfVar]), isSpace) + + return expandVariables(trimmed, vars), src[endOfLine:], nil + } + + // lookup quoted string terminator + for i := 1; i < len(src); i++ { + if char := src[i]; char != quote { + continue + } + + // skip escaped quote symbol (\" or \', depends on quote) + if prevChar := src[i-1]; prevChar == '\\' { + continue + } + + // trim quotes + trimFunc := isCharFunc(rune(quote)) + value = string(bytes.TrimLeftFunc(bytes.TrimRightFunc(src[0:i], trimFunc), trimFunc)) + if quote == prefixDoubleQuote { + // unescape newlines for double quote (this is compat feature) + // and expand environment variables + value = expandVariables(expandEscapes(value), vars) + } + + return value, src[i+1:], nil + } + + // return formatted error if quoted string is not terminated + valEndIndex := bytes.IndexFunc(src, isCharFunc('\n')) + if valEndIndex == -1 { + valEndIndex = len(src) + } + + return "", nil, fmt.Errorf("unterminated quoted value %s", src[:valEndIndex]) +} + +func expandEscapes(str string) string { + out := escapeRegex.ReplaceAllStringFunc(str, func(match string) string { + c := strings.TrimPrefix(match, `\`) + switch c { + case "n": + return "\n" + case "r": + return "\r" + default: + return match + } + }) + return unescapeCharsRegex.ReplaceAllString(out, "$1") +} + +func indexOfNonSpaceChar(src []byte) int { + return bytes.IndexFunc(src, func(r rune) bool { + return !unicode.IsSpace(r) + }) +} + +// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character +func hasQuotePrefix(src []byte) (prefix byte, isQuored bool) { + if len(src) == 0 { + return 0, false + } + + switch prefix := src[0]; prefix { + case prefixDoubleQuote, prefixSingleQuote: + return prefix, true + default: + return 0, false + } +} + +func isCharFunc(char rune) func(rune) bool { + return func(v rune) bool { + return v == char + } +} + +// isSpace reports whether the rune is a space character but not line break character +// +// this differs from unicode.IsSpace, which also applies line break as space +func isSpace(r rune) bool { + switch r { + case '\t', '\v', '\f', '\r', ' ', 0x85, 0xA0: + return true + } + return false +} + +func isLineEnd(r rune) bool { + if r == '\n' || r == '\r' { + return true + } + return false +} + +var ( + escapeRegex = regexp.MustCompile(`\\.`) + expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) + unescapeCharsRegex = regexp.MustCompile(`\\([^$])`) +) + +func expandVariables(v string, m map[string]string) string { + return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string { + submatch := expandVarRegex.FindStringSubmatch(s) + + if submatch == nil { + return s + } + if submatch[1] == "\\" || submatch[2] == "(" { + return submatch[0][1:] + } else if submatch[4] != "" { + return m[submatch[4]] + } + return s + }) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ed4496dfd0..bd8181ef0d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -378,8 +378,8 @@ github.com/jbenet/go-context/io # github.com/jessevdk/go-flags v1.4.0 ## explicit github.com/jessevdk/go-flags -# github.com/joho/godotenv v1.3.0 -## explicit +# github.com/joho/godotenv v1.5.1 +## explicit; go 1.12 github.com/joho/godotenv # github.com/josharian/intern v1.0.0 ## explicit; go 1.5