diff --git a/godotenv.go b/godotenv.go index 277bde2..94b2676 100644 --- a/godotenv.go +++ b/godotenv.go @@ -36,7 +36,30 @@ func Load(filenames ...string) (err error) { filenames = filenamesOrDefault(filenames) for _, filename := range filenames { - err = loadFile(filename) + 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 forcefilly 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 } @@ -90,14 +113,14 @@ func filenamesOrDefault(filenames []string) []string { return filenames } -func loadFile(filename string) error { +func loadFile(filename string, overload bool) error { envMap, err := readFile(filename) if err != nil { return err } for key, value := range envMap { - if os.Getenv(key) == "" { + if os.Getenv(key) == "" || overload { os.Setenv(key, value) } } diff --git a/godotenv_test.go b/godotenv_test.go index 85b012f..e93539e 100644 --- a/godotenv_test.go +++ b/godotenv_test.go @@ -5,6 +5,8 @@ import ( "testing" ) +var noopPresets = make(map[string]string) + func parseAndCompare(t *testing.T, rawEnvLine string, expectedKey string, expectedValue string) { key, value, _ := parseLine(rawEnvLine) if key != expectedKey || value != expectedValue { @@ -12,11 +14,15 @@ func parseAndCompare(t *testing.T, rawEnvLine string, expectedKey string, expect } } -func loadEnvAndCompareValues(t *testing.T, envFileName string, expectedValues map[string]string) { +func loadEnvAndCompareValues(t *testing.T, loader func(files ...string) error, envFileName string, expectedValues map[string]string, presets map[string]string) { // first up, clear the env os.Clearenv() - err := Load(envFileName) + for k, v := range presets { + os.Setenv(k, v) + } + + err := loader(envFileName) if err != nil { t.Fatalf("Error loading %v", envFileName) } @@ -38,6 +44,14 @@ func TestLoadWithNoArgsLoadsDotEnv(t *testing.T) { } } +func TestOverloadWithNoArgsOverloadsDotEnv(t *testing.T) { + err := Overload() + pathError := err.(*os.PathError) + if pathError == nil || pathError.Op != "open" || pathError.Path != ".env" { + t.Errorf("Didn't try and open .env by default") + } +} + func TestLoadFileNotFound(t *testing.T) { err := Load("somefilethatwillneverexistever.env") if err == nil { @@ -45,6 +59,13 @@ func TestLoadFileNotFound(t *testing.T) { } } +func TestOverloadFileNotFound(t *testing.T) { + err := Overload("somefilethatwillneverexistever.env") + if err == nil { + t.Error("File wasn't found but Overload didn't return an error") + } +} + func TestReadPlainEnv(t *testing.T) { envFileName := "fixtures/plain.env" expectedValues := map[string]string{ @@ -71,6 +92,34 @@ func TestReadPlainEnv(t *testing.T) { } } +func TestLoadDoesNotOverride(t *testing.T) { + envFileName := "fixtures/plain.env" + + // ensure NO overload + presets := map[string]string{ + "OPTION_A": "do_not_override", + } + + expectedValues := map[string]string{ + "OPTION_A": "do_not_override", + } + loadEnvAndCompareValues(t, Load, envFileName, expectedValues, presets) +} + +func TestOveroadDoesOverride(t *testing.T) { + envFileName := "fixtures/plain.env" + + // ensure NO overload + presets := map[string]string{ + "OPTION_A": "do_not_override", + } + + expectedValues := map[string]string{ + "OPTION_A": "1", + } + loadEnvAndCompareValues(t, Overload, envFileName, expectedValues, presets) +} + func TestLoadPlainEnv(t *testing.T) { envFileName := "fixtures/plain.env" expectedValues := map[string]string{ @@ -81,7 +130,7 @@ func TestLoadPlainEnv(t *testing.T) { "OPTION_E": "5", } - loadEnvAndCompareValues(t, envFileName, expectedValues) + loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets) } func TestLoadExportedEnv(t *testing.T) { @@ -91,7 +140,7 @@ func TestLoadExportedEnv(t *testing.T) { "OPTION_B": "\n", } - loadEnvAndCompareValues(t, envFileName, expectedValues) + loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets) } func TestLoadEqualsEnv(t *testing.T) { @@ -100,7 +149,7 @@ func TestLoadEqualsEnv(t *testing.T) { "OPTION_A": "postgres://localhost:5432/database?sslmode=disable", } - loadEnvAndCompareValues(t, envFileName, expectedValues) + loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets) } func TestLoadQuotedEnv(t *testing.T) { @@ -116,7 +165,7 @@ func TestLoadQuotedEnv(t *testing.T) { "OPTION_H": "\n", } - loadEnvAndCompareValues(t, envFileName, expectedValues) + loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets) } func TestActualEnvVarsAreLeftAlone(t *testing.T) {