Multiline string support (#156)

* refactor dotenv parser in order to support multi-line variable values declaration

Signed-off-by: x1unix <denis0051@gmail.com>

* Add multi-line var values test case and update comment test

Signed-off-by: x1unix <denis0051@gmail.com>

* Expand fixture tests to include multiline strings

* Update go versions to test against

* Switch to GOINSECURE for power8 CI task

* When tests fail, show source version of string (inc special chars)

* Update parser.go

Co-authored-by: Austin Sasko <austintyler0239@yahoo.com>

* Fix up bad merge

* Add a full fixture for comments for extra piece of mind

* Fix up some lint/staticcheck recommendations

* Test against go 1.19 too

Signed-off-by: x1unix <denis0051@gmail.com>
Co-authored-by: x1unix <denis0051@gmail.com>
Co-authored-by: Austin Sasko <austintyler0239@yahoo.com>
This commit is contained in:
John Barton
2023-01-27 13:14:16 +11:00
committed by GitHub
parent 0f21d20acb
commit cc9e9b7de7
6 changed files with 325 additions and 70 deletions

View File

@ -14,10 +14,10 @@
package godotenv
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"regexp"
@ -28,6 +28,16 @@ import (
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) {
data, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
return UnmarshalBytes(data)
}
// 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).
@ -96,37 +106,17 @@ 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 Parse(strings.NewReader(str))
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)
@ -137,7 +127,9 @@ func Unmarshal(str string) (envMap map[string]string, err error) {
// 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...)
if err := Load(filenames...); err != nil {
return err
}
command := exec.Command(cmd, cmdArgs...)
command.Stdin = os.Stdin
@ -161,8 +153,7 @@ func Write(envMap map[string]string, filename string) error {
if err != nil {
return err
}
file.Sync()
return err
return file.Sync()
}
// Marshal outputs the given environment as a dotenv-formatted environment file.
@ -202,7 +193,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)
}
}
@ -259,15 +250,15 @@ func parseLine(line string, envMap map[string]string) (key string, value string,
}
if len(splitString) != 2 {
err = errors.New("Can't separate key from value")
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.TrimPrefix(key, "export")
key = strings.TrimSpace(key)
key = exportRegex.ReplaceAllString(splitString[0], "$1")
@ -343,11 +334,6 @@ func expandVariables(v string, m map[string]string) string {
})
}
func isIgnoredLine(line string) bool {
trimmedLine := strings.TrimSpace(line)
return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
}
func doubleQuoteEscape(line string) string {
for _, c := range doubleQuoteSpecialChars {
toReplace := "\\" + string(c)