summaryrefslogtreecommitdiff
path: root/vendor/github.com/joho
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/joho')
-rw-r--r--vendor/github.com/joho/godotenv/.gitignore1
-rw-r--r--vendor/github.com/joho/godotenv/LICENCE23
-rw-r--r--vendor/github.com/joho/godotenv/README.md202
-rw-r--r--vendor/github.com/joho/godotenv/cmd/godotenv/cmd.go56
-rw-r--r--vendor/github.com/joho/godotenv/godotenv.go228
-rw-r--r--vendor/github.com/joho/godotenv/parser.go271
6 files changed, 781 insertions, 0 deletions
diff --git a/vendor/github.com/joho/godotenv/.gitignore b/vendor/github.com/joho/godotenv/.gitignore
new file mode 100644
index 0000000..e43b0f9
--- /dev/null
+++ b/vendor/github.com/joho/godotenv/.gitignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/vendor/github.com/joho/godotenv/LICENCE b/vendor/github.com/joho/godotenv/LICENCE
new file mode 100644
index 0000000..e7ddd51
--- /dev/null
+++ b/vendor/github.com/joho/godotenv/LICENCE
@@ -0,0 +1,23 @@
+Copyright (c) 2013 John Barton
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/vendor/github.com/joho/godotenv/README.md b/vendor/github.com/joho/godotenv/README.md
new file mode 100644
index 0000000..bfbe66a
--- /dev/null
+++ b/vendor/github.com/joho/godotenv/README.md
@@ -0,0 +1,202 @@
+# 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](https://github.com/bkeepers/dotenv) project (which loads env vars from a .env file).
+
+From the original Library:
+
+> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables.
+>
+> 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.
+
+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
+
+As a library
+
+```shell
+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
+```
+
+## Usage
+
+Add your application configuration to your `.env` file in the root of your project:
+
+```shell
+S3_BUCKET=YOURS3BUCKET
+SECRET_KEY=YOURSECRETKEYGOESHERE
+```
+
+Then in your Go app you can do something like
+
+```go
+package main
+
+import (
+ "log"
+ "os"
+
+ "github.com/joho/godotenv"
+)
+
+func main() {
+ err := godotenv.Load()
+ if err != nil {
+ log.Fatal("Error loading .env file")
+ }
+
+ s3Bucket := os.Getenv("S3_BUCKET")
+ secretKey := os.Getenv("SECRET_KEY")
+
+ // now do something with s3 or whatever
+}
+```
+
+If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import
+
+```go
+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")
+```
+
+If you want to be really fancy with your env file you can do comments and exports (below is a valid env file)
+
+```shell
+# I am a comment and that is OK
+SOME_VAR=someval
+FOO=BAR # comments at line end are OK too
+export BAR=BAZ
+```
+
+Or finally you can do YAML(ish) style
+
+```yaml
+FOO: bar
+BAR: baz
+```
+
+as a final aside, if you don't want godotenv munging your env you can just get a map back instead
+
+```go
+var myEnv map[string]string
+myEnv, err := godotenv.Read()
+
+s3Bucket := myEnv["S3_BUCKET"]
+```
+
+... or from an `io.Reader` instead of a local file
+
+```go
+reader := getRemoteFile()
+myEnv, err := godotenv.Parse(reader)
+```
+
+... or from a `string` if you so desire
+
+```go
+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`
+
+```
+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
+
+```go
+env, err := godotenv.Unmarshal("KEY=value")
+err := godotenv.Write(env, "./.env")
+```
+
+... or to a string
+
+```go
+env, err := godotenv.Unmarshal("KEY=value")
+content, err := godotenv.Marshal(env)
+```
+
+## Contributing
+
+Contributions are welcome, but with some caveats.
+
+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`)
+3. Commit your changes (`git commit -am 'Added some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
+
+## Releases
+
+Releases should follow [Semver](http://semver.org/) though the first couple of releases are `v1` and `v1.1`.
+
+Use [annotated tags for all releases](https://github.com/joho/godotenv/issues/30). Example `git tag -a v1.2.1`
+
+## 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/cmd/godotenv/cmd.go b/vendor/github.com/joho/godotenv/cmd/godotenv/cmd.go
new file mode 100644
index 0000000..2a7b2d8
--- /dev/null
+++ b/vendor/github.com/joho/godotenv/cmd/godotenv/cmd.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+
+ "strings"
+
+ "github.com/joho/godotenv"
+)
+
+func main() {
+ var showHelp bool
+ flag.BoolVar(&showHelp, "h", false, "show help")
+ var rawEnvFilenames string
+ flag.StringVar(&rawEnvFilenames, "f", "", "comma separated paths to .env files")
+ var overload bool
+ flag.BoolVar(&overload, "o", false, "override existing .env variables")
+
+ flag.Parse()
+
+ usage := `
+Run a process with an env setup from a .env file
+
+godotenv [-o] [-f ENV_FILE_PATHS] COMMAND_ARGS
+
+ENV_FILE_PATHS: comma separated paths to .env files
+COMMAND_ARGS: command and args you want to run
+
+example
+ godotenv -f /path/to/something/.env,/another/path/.env fortune
+`
+ // if no args or -h flag
+ // print usage and return
+ args := flag.Args()
+ if showHelp || len(args) == 0 {
+ fmt.Println(usage)
+ return
+ }
+
+ // load env
+ var envFilenames []string
+ if rawEnvFilenames != "" {
+ envFilenames = strings.Split(rawEnvFilenames, ",")
+ }
+
+ // take rest of args and "exec" them
+ cmd := args[0]
+ cmdArgs := args[1:]
+
+ err := godotenv.Exec(envFilenames, cmd, cmdArgs, overload)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/vendor/github.com/joho/godotenv/godotenv.go b/vendor/github.com/joho/godotenv/godotenv.go
new file mode 100644
index 0000000..61b0ebb
--- /dev/null
+++ b/vendor/github.com/joho/godotenv/godotenv.go
@@ -0,0 +1,228 @@
+// 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
+//
+// The TL;DR is that you make a .env file that looks something like
+//
+// SOME_ENV_VAR=somevalue
+//
+// and then in your go code you can call
+//
+// godotenv.Load()
+//
+// and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
+package godotenv
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "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).
+//
+// 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:
+//
+// 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.
+func Load(filenames ...string) (err error) {
+ filenames = filenamesOrDefault(filenames)
+
+ for _, filename := range filenames {
+ err = loadFile(filename, false)
+ if err != nil {
+ return // return early on a spazout
+ }
+ }
+ return
+}
+
+// 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).
+//
+// 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:
+//
+// godotenv.Overload("fileone", "filetwo")
+//
+// 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)
+
+ for _, filename := range filenames {
+ err = loadFile(filename, true)
+ if err != nil {
+ return // return early on a spazout
+ }
+ }
+ return
+}
+
+// Read all env (with same file loading semantics as Load) but return values as
+// a map rather than automatically writing values into env
+func Read(filenames ...string) (envMap map[string]string, err error) {
+ filenames = filenamesOrDefault(filenames)
+ envMap = make(map[string]string)
+
+ for _, filename := range filenames {
+ individualEnvMap, individualErr := readFile(filename)
+
+ if individualErr != nil {
+ err = individualErr
+ return // return early on a spazout
+ }
+
+ for key, value := range individualEnvMap {
+ 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))
+}
+
+// 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().
+//
+// If you want more fine grained control over your command it's recommended
+// 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
+ command.Stdout = os.Stdout
+ command.Stderr = os.Stderr
+ return command.Run()
+}
+
+// Write serializes the given environment and writes it to a file.
+func Write(envMap map[string]string, filename string) error {
+ content, err := Marshal(envMap)
+ if err != nil {
+ return err
+ }
+ 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()
+}
+
+// Marshal outputs the given environment as a dotenv-formatted environment file.
+// Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped.
+func Marshal(envMap map[string]string) (string, error) {
+ lines := make([]string, 0, len(envMap))
+ for k, v := range envMap {
+ 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
+}
+
+func filenamesOrDefault(filenames []string) []string {
+ if len(filenames) == 0 {
+ return []string{".env"}
+ }
+ return filenames
+}
+
+func loadFile(filename string, overload bool) error {
+ envMap, err := readFile(filename)
+ if err != nil {
+ return err
+ }
+
+ currentEnv := map[string]bool{}
+ rawEnv := os.Environ()
+ for _, rawEnvLine := range rawEnv {
+ key := strings.Split(rawEnvLine, "=")[0]
+ currentEnv[key] = true
+ }
+
+ for key, value := range envMap {
+ if !currentEnv[key] || overload {
+ _ = os.Setenv(key, value)
+ }
+ }
+
+ return nil
+}
+
+func readFile(filename string) (envMap map[string]string, err error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return
+ }
+ defer file.Close()
+
+ return Parse(file)
+}
+
+func doubleQuoteEscape(line string) string {
+ for _, c := range doubleQuoteSpecialChars {
+ toReplace := "\\" + string(c)
+ if c == '\n' {
+ toReplace = `\n`
+ }
+ if c == '\r' {
+ toReplace = `\r`
+ }
+ line = strings.Replace(line, string(c), toReplace, -1)
+ }
+ return line
+}
diff --git a/vendor/github.com/joho/godotenv/parser.go b/vendor/github.com/joho/godotenv/parser.go
new file mode 100644
index 0000000..cc709af
--- /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
+ })
+}