Skip to content
This repository was archived by the owner on Jan 12, 2022. It is now read-only.

Commit 5b4ca74

Browse files
authored
Merge pull request #6 from ulyssessouza/inherited-vars2
2 parents 934be71 + 088e226 commit 5b4ca74

File tree

5 files changed

+107
-21
lines changed

5 files changed

+107
-21
lines changed

fixtures/inherited-not-found.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VARIABLE_NOT_FOUND

fixtures/inherited.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VAR_TO_BE_LOADED_FROM_OS_ENV

godotenv.go

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,26 @@ import (
2828

2929
const doubleQuoteSpecialChars = "\\\n\r\"!$`"
3030

31+
// LookupFn represents a lookup function to resolve variables from
32+
type LookupFn func(string) (string, bool)
33+
34+
var noLookupFn = func(s string) (string, bool) {
35+
return "", false
36+
}
37+
3138
// Parse reads an env file from io.Reader, returning a map of keys and values.
3239
func Parse(r io.Reader) (map[string]string, error) {
40+
return ParseWithLookup(r, nil)
41+
}
42+
43+
// ParseWithLookup reads an env file from io.Reader, returning a map of keys and values.
44+
func ParseWithLookup(r io.Reader, lookupFn LookupFn) (map[string]string, error) {
3345
data, err := ioutil.ReadAll(r)
3446
if err != nil {
3547
return nil, err
3648
}
3749

38-
return UnmarshalBytes(data)
50+
return UnmarshalBytesWithLookup(data, lookupFn)
3951
}
4052

4153
// Load will read your env file(s) and load them into ENV for this process.
@@ -50,15 +62,7 @@ func Parse(r io.Reader) (map[string]string, error) {
5062
//
5163
// 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
5264
func Load(filenames ...string) (err error) {
53-
filenames = filenamesOrDefault(filenames)
54-
55-
for _, filename := range filenames {
56-
err = loadFile(filename, false)
57-
if err != nil {
58-
return // return early on a spazout
59-
}
60-
}
61-
return
65+
return load(false, filenames...)
6266
}
6367

6468
// Overload will read your env file(s) and load them into ENV for this process.
@@ -73,25 +77,29 @@ func Load(filenames ...string) (err error) {
7377
//
7478
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
7579
func Overload(filenames ...string) (err error) {
80+
return load(true, filenames...)
81+
}
82+
83+
func load(overload bool, filenames ...string) (err error) {
7684
filenames = filenamesOrDefault(filenames)
7785

7886
for _, filename := range filenames {
79-
err = loadFile(filename, true)
87+
err = loadFile(filename, overload)
8088
if err != nil {
8189
return // return early on a spazout
8290
}
8391
}
8492
return
8593
}
8694

87-
// Read all env (with same file loading semantics as Load) but return values as
95+
// ReadWithLookup gets all env vars from the files and/or lookup function and return values as
8896
// a map rather than automatically writing values into env
89-
func Read(filenames ...string) (envMap map[string]string, err error) {
97+
func ReadWithLookup(lookupFn LookupFn, filenames ...string) (envMap map[string]string, err error) {
9098
filenames = filenamesOrDefault(filenames)
9199
envMap = make(map[string]string)
92100

93101
for _, filename := range filenames {
94-
individualEnvMap, individualErr := readFile(filename)
102+
individualEnvMap, individualErr := readFile(filename, lookupFn)
95103

96104
if individualErr != nil {
97105
err = individualErr
@@ -106,15 +114,26 @@ func Read(filenames ...string) (envMap map[string]string, err error) {
106114
return
107115
}
108116

117+
// Read all env (with same file loading semantics as Load) but return values as
118+
// a map rather than automatically writing values into env
119+
func Read(filenames ...string) (envMap map[string]string, err error) {
120+
return ReadWithLookup(nil, filenames...)
121+
}
122+
109123
// Unmarshal reads an env file from a string, returning a map of keys and values.
110124
func Unmarshal(str string) (envMap map[string]string, err error) {
111125
return UnmarshalBytes([]byte(str))
112126
}
113127

114128
// UnmarshalBytes parses env file from byte slice of chars, returning a map of keys and values.
115129
func UnmarshalBytes(src []byte) (map[string]string, error) {
130+
return UnmarshalBytesWithLookup(src, nil)
131+
}
132+
133+
// UnmarshalBytesWithLookup parses env file from byte slice of chars, returning a map of keys and values.
134+
func UnmarshalBytesWithLookup(src []byte, lookupFn LookupFn) (map[string]string, error) {
116135
out := make(map[string]string)
117-
err := parseBytes(src, out)
136+
err := parseBytes(src, out, lookupFn)
118137
return out, err
119138
}
120139

@@ -178,7 +197,7 @@ func filenamesOrDefault(filenames []string) []string {
178197
}
179198

180199
func loadFile(filename string, overload bool) error {
181-
envMap, err := readFile(filename)
200+
envMap, err := readFile(filename, nil)
182201
if err != nil {
183202
return err
184203
}
@@ -199,14 +218,14 @@ func loadFile(filename string, overload bool) error {
199218
return nil
200219
}
201220

202-
func readFile(filename string) (envMap map[string]string, err error) {
221+
func readFile(filename string, lookupFn LookupFn) (envMap map[string]string, err error) {
203222
file, err := os.Open(filename)
204223
if err != nil {
205224
return
206225
}
207226
defer file.Close()
208227

209-
return Parse(file)
228+
return ParseWithLookup(file, lookupFn)
210229
}
211230

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

godotenv_test.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ func TestRoundtrip(t *testing.T) {
487487
fixtures := []string{"equals.env", "exported.env", "plain.env", "quoted.env"}
488488
for _, fixture := range fixtures {
489489
fixtureFilename := fmt.Sprintf("fixtures/%s", fixture)
490-
env, err := readFile(fixtureFilename)
490+
env, err := readFile(fixtureFilename, nil)
491491
if err != nil {
492492
t.Errorf("Expected '%s' to read without error (%v)", fixtureFilename, err)
493493
}
@@ -505,3 +505,52 @@ func TestRoundtrip(t *testing.T) {
505505

506506
}
507507
}
508+
509+
func TestInheritedEnvVariable(t *testing.T) {
510+
const envKey = "VAR_TO_BE_LOADED_FROM_OS_ENV"
511+
const envVal = "SOME_RANDOM_VALUE"
512+
os.Setenv(envKey, envVal)
513+
514+
envFileName := "fixtures/inherited.env"
515+
expectedValues := map[string]string{
516+
envKey: envVal,
517+
}
518+
519+
envMap, err := ReadWithLookup(os.LookupEnv, envFileName)
520+
if err != nil {
521+
t.Error("Error reading file")
522+
}
523+
if len(envMap) != len(expectedValues) {
524+
t.Error("Didn't get the right size map back")
525+
}
526+
for key, value := range expectedValues {
527+
if envMap[key] != value {
528+
t.Error("Read got one of the keys wrong")
529+
}
530+
}
531+
}
532+
533+
func TestInheritedEnvVariableNotFound(t *testing.T) {
534+
envMap, err := Read("fixtures/inherited-not-found.env")
535+
if envMap["VARIABLE_NOT_FOUND"] != "" || err != nil {
536+
t.Errorf("Expected 'VARIABLE_NOT_FOUND' to be '' with no errors")
537+
}
538+
}
539+
540+
func TestInheritedEnvVariableNotFoundWithLookup(t *testing.T) {
541+
notFoundMap := make(map[string]interface{})
542+
envMap, err := ReadWithLookup(func(v string)(string, bool){
543+
envVar, ok := os.LookupEnv(v)
544+
if !ok {
545+
notFoundMap[v] = nil
546+
}
547+
return envVar, ok
548+
}, "fixtures/inherited-not-found.env")
549+
if envMap["VARIABLE_NOT_FOUND"] != "" || err != nil {
550+
t.Errorf("Expected 'VARIABLE_NOT_FOUND' to be '' with no errors")
551+
}
552+
_, ok := notFoundMap["VARIABLE_NOT_FOUND"]
553+
if !ok {
554+
t.Errorf("Expected 'VARIABLE_NOT_FOUND' to be in the set of not found variables")
555+
}
556+
}

parser.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const (
1616
exportPrefix = "export"
1717
)
1818

19-
func parseBytes(src []byte, out map[string]string) error {
19+
func parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error {
2020
cutset := src
2121
for {
2222
cutset = getStatementStart(cutset)
@@ -29,6 +29,22 @@ func parseBytes(src []byte, out map[string]string) error {
2929
if err != nil {
3030
return err
3131
}
32+
if strings.Contains(key, " ") {
33+
return errors.New("key cannot contain a space")
34+
}
35+
36+
if left == nil {
37+
if lookupFn == nil {
38+
lookupFn = noLookupFn
39+
}
40+
41+
value, ok := lookupFn(key)
42+
if ok {
43+
out[key] = value
44+
cutset = left
45+
continue
46+
}
47+
}
3248

3349
value, left, err := extractVarValue(left, out)
3450
if err != nil {
@@ -80,7 +96,7 @@ loop:
8096
}
8197

8298
switch char {
83-
case '=', ':':
99+
case '=', ':', '\n':
84100
// library also supports yaml-style value declaration
85101
key = string(src[0:i])
86102
offset = i + 1

0 commit comments

Comments
 (0)