Skip to content

Illustrating: Var expansion fallback to os.Getenv #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions godotenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func Read(filenames ...string) (envMap map[string]string, err error) {
envMap = make(map[string]string)

for _, filename := range filenames {
individualEnvMap, individualErr := readFile(filename)
individualEnvMap, individualErr := readFile(filename, false)

if individualErr != nil {
err = individualErr
Expand All @@ -97,6 +97,10 @@ func Read(filenames ...string) (envMap map[string]string, err error) {

// 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) {
return parse(r, false)
}

func parse(r io.Reader, overload bool) (envMap map[string]string, err error) {
envMap = make(map[string]string)

var lines []string
Expand All @@ -112,7 +116,7 @@ func Parse(r io.Reader) (envMap map[string]string, err error) {
for _, fullLine := range lines {
if !isIgnoredLine(fullLine) {
var key, value string
key, value, err = parseLine(fullLine, envMap)
key, value, err = parseLine(fullLine, envMap, overload)

if err != nil {
return
Expand Down Expand Up @@ -183,7 +187,7 @@ func filenamesOrDefault(filenames []string) []string {
}

func loadFile(filename string, overload bool) error {
envMap, err := readFile(filename)
envMap, err := readFile(filename, overload)
if err != nil {
return err
}
Expand All @@ -204,19 +208,19 @@ func loadFile(filename string, overload bool) error {
return nil
}

func readFile(filename string) (envMap map[string]string, err error) {
func readFile(filename string, overload bool) (envMap map[string]string, err error) {
file, err := os.Open(filename)
if err != nil {
return
}
defer file.Close()

return Parse(file)
return parse(file, overload)
}

var exportRegex = regexp.MustCompile(`^\s*(?:export\s+)?(.*?)\s*$`)

func parseLine(line string, envMap map[string]string) (key string, value string, err error) {
func parseLine(line string, envMap map[string]string, overload bool) (key string, value string, err error) {
if len(line) == 0 {
err = errors.New("zero length string")
return
Expand Down Expand Up @@ -268,7 +272,7 @@ func parseLine(line string, envMap map[string]string) (key string, value string,
key = exportRegex.ReplaceAllString(splitString[0], "$1")

// Parse the value
value = parseValue(splitString[1], envMap)
value = parseValue(splitString[1], envMap, overload)
return
}

Expand All @@ -279,7 +283,7 @@ var (
unescapeCharsRegex = regexp.MustCompile(`\\([^$])`)
)

func parseValue(value string, envMap map[string]string) string {
func parseValue(value string, envMap map[string]string, overload bool) string {

// trim
value = strings.Trim(value, " ")
Expand Down Expand Up @@ -313,7 +317,7 @@ func parseValue(value string, envMap map[string]string) string {
}

if singleQuotes == nil {
value = expandVariables(value, envMap)
value = expandVariables(value, envMap, overload)
}
}

Expand All @@ -322,7 +326,7 @@ func parseValue(value string, envMap map[string]string) string {

var expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)

func expandVariables(v string, m map[string]string) string {
func expandVariables(v string, m map[string]string, overload bool) string {
return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string {
submatch := expandVarRegex.FindStringSubmatch(s)

Expand All @@ -332,6 +336,10 @@ func expandVariables(v string, m map[string]string) string {
if submatch[1] == "\\" || submatch[2] == "(" {
return submatch[0][1:]
} else if submatch[4] != "" {
if osEnv := os.Getenv(submatch[4]); osEnv != "" && (!overload || m[submatch[4]] == "") {
return osEnv
}

return m[submatch[4]]
}
return s
Expand Down
13 changes: 10 additions & 3 deletions godotenv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
var noopPresets = make(map[string]string)

func parseAndCompare(t *testing.T, rawEnvLine string, expectedKey string, expectedValue string) {
key, value, _ := parseLine(rawEnvLine, noopPresets)
key, value, _ := parseLine(rawEnvLine, noopPresets, false)
if key != expectedKey || value != expectedValue {
t.Errorf("Expected '%v' to parse as '%v' => '%v', got '%v' => '%v' instead", rawEnvLine, expectedKey, expectedValue, key, value)
}
Expand Down Expand Up @@ -208,6 +208,8 @@ func TestSubstitutions(t *testing.T) {
}

func TestExpanding(t *testing.T) {
os.Setenv("GODOTENV_TEST", "testing")

tests := []struct {
name string
input string
Expand All @@ -223,6 +225,11 @@ func TestExpanding(t *testing.T) {
"FOO=test\nBAR=${FOO}bar",
map[string]string{"FOO": "test", "BAR": "testbar"},
},
{
"expands previously existing variables to their values from os.Getenv",
"BAR=$GODOTENV_TEST",
map[string]string{"BAR": "testing"},
},
{
"expands undefined variables to an empty string",
"BAR=$FOO",
Expand Down Expand Up @@ -370,7 +377,7 @@ func TestParsing(t *testing.T) {
// it 'throws an error if line format is incorrect' do
// expect{env('lol$wut')}.to raise_error(Dotenv::FormatError)
badlyFormattedLine := "lol$wut"
_, _, err := parseLine(badlyFormattedLine, noopPresets)
_, _, err := parseLine(badlyFormattedLine, noopPresets, false)
if err == nil {
t.Errorf("Expected \"%v\" to return error, but it didn't", badlyFormattedLine)
}
Expand Down Expand Up @@ -452,7 +459,7 @@ func TestRoundtrip(t *testing.T) {
fixtures := []string{"equals.env", "exported.env", "plain.env", "quoted.env"}
for _, fixture := range fixtures {
fixtureFilename := fmt.Sprintf("fixtures/%s", fixture)
env, err := readFile(fixtureFilename)
env, err := readFile(fixtureFilename, false)
if err != nil {
t.Errorf("Expected '%s' to read without error (%v)", fixtureFilename, err)
}
Expand Down