Skip to content

Commit 8b190c3

Browse files
committed
Inherit variables
This adds the feature of inheriting values from variables in the current OS environment. If not existent in the current environment, it produces an error. Also removed the test with an empty key, since it's never present in the environment. Signed-off-by: Ulysses Souza <[email protected]>
1 parent ddf83eb commit 8b190c3

File tree

4 files changed

+73
-11
lines changed

4 files changed

+73
-11
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+
PATH

godotenv.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,31 +97,33 @@ func Read(filenames ...string) (envMap map[string]string, err error) {
9797
}
9898

9999
// Parse reads an env file from io.Reader, returning a map of keys and values.
100-
func Parse(r io.Reader) (envMap map[string]string, err error) {
101-
envMap = make(map[string]string)
100+
func Parse(r io.Reader) (map[string]string, error) {
101+
envMap := make(map[string]string)
102102

103103
var lines []string
104104
scanner := bufio.NewScanner(r)
105105
for scanner.Scan() {
106106
lines = append(lines, scanner.Text())
107107
}
108108

109-
if err = scanner.Err(); err != nil {
110-
return
109+
if err := scanner.Err(); err != nil {
110+
return envMap, err
111111
}
112112

113113
for _, fullLine := range lines {
114114
if !isIgnoredLine(fullLine) {
115115
var key, value string
116-
key, value, err = parseLine(fullLine, envMap)
117-
116+
key, value, err := parseLine(fullLine, envMap)
118117
if err != nil {
119-
return
118+
return envMap, err
119+
}
120+
if key == "" {
121+
continue
120122
}
121123
envMap[key] = value
122124
}
123125
}
124-
return
126+
return envMap, nil
125127
}
126128

127129
//Unmarshal reads an env file from a string, returning a map of keys and values.
@@ -258,6 +260,16 @@ func parseLine(line string, envMap map[string]string) (key string, value string,
258260
splitString = strings.SplitN(line, ":", 2)
259261
}
260262

263+
// Environment inherited variable
264+
if firstEquals < 0 && firstColon < 0 {
265+
v, ok := os.LookupEnv(strings.TrimSpace(splitString[0]))
266+
if !ok {
267+
err = fmt.Errorf("could not find variable %q in the environment", line)
268+
return
269+
}
270+
return line , v, nil
271+
}
272+
261273
if len(splitString) != 2 {
262274
err = errors.New("Can't separate key from value")
263275
return
@@ -272,16 +284,30 @@ func parseLine(line string, envMap map[string]string) (key string, value string,
272284

273285
key = exportRegex.ReplaceAllString(splitString[0], "$1")
274286

287+
err = validateVariableName(key)
288+
if err != nil {
289+
return
290+
}
291+
275292
// Parse the value
276293
value = parseValue(splitString[1], envMap)
277294
return
278295
}
279296

297+
func validateVariableName(line string) error {
298+
line = strings.TrimSpace(strings.TrimPrefix(line, "export "))
299+
if !variableNameRegex.MatchString(line) {
300+
return fmt.Errorf("invalid variable name %q", line)
301+
}
302+
return nil
303+
}
304+
280305
var (
281306
singleQuotesRegex = regexp.MustCompile(`\A'(.*)'\z`)
282307
doubleQuotesRegex = regexp.MustCompile(`\A"(.*)"\z`)
283308
escapeRegex = regexp.MustCompile(`\\.`)
284309
unescapeCharsRegex = regexp.MustCompile(`\\([^$])`)
310+
variableNameRegex = regexp.MustCompile(`^[_\\.a-zA-Z0-9]+$`)
285311
)
286312

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

godotenv_test.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,33 @@ func TestReadPlainEnv(t *testing.T) {
8282
"OPTION_G": "",
8383
}
8484

85+
envMap, err := Read(envFileName)
86+
if err != nil {
87+
t.Errorf("Error reading file: %q", err)
88+
}
89+
90+
if len(envMap) != len(expectedValues) {
91+
t.Error("Didn't get the right size map back")
92+
}
93+
94+
for key, value := range expectedValues {
95+
if envMap[key] != value {
96+
t.Error("Read got one of the keys wrong")
97+
}
98+
}
99+
}
100+
101+
func TestInheritedEnvVariable(t *testing.T) {
102+
pathVariable, ok := os.LookupEnv("PATH")
103+
if !ok {
104+
t.Error("Error getting path variable")
105+
}
106+
107+
envFileName := "fixtures/inherited.env"
108+
expectedValues := map[string]string{
109+
"PATH": pathVariable,
110+
}
111+
85112
envMap, err := Read(envFileName)
86113
if err != nil {
87114
t.Error("Error reading file")
@@ -98,6 +125,14 @@ func TestReadPlainEnv(t *testing.T) {
98125
}
99126
}
100127

128+
func TestInheritedEnvVariableNotFound(t *testing.T) {
129+
envFileName := "fixtures/inherited-not-found.env"
130+
_, err := Read(envFileName)
131+
if err == nil {
132+
t.Errorf("Expected an error on 'Read' variable 'VARIABLE_NOT_FOUND' should not be found in environment")
133+
}
134+
}
135+
101136
func TestParse(t *testing.T) {
102137
envMap, err := Parse(bytes.NewReader([]byte("ONE=1\nTWO='2'\nTHREE = \"3\"")))
103138
expectedValues := map[string]string{
@@ -358,7 +393,6 @@ func TestParsing(t *testing.T) {
358393
parseAndCompare(t, `FOO="bar\\\n\ b\az"`, "FOO", "bar\\\n baz")
359394
parseAndCompare(t, `FOO="bar\\r\ b\az"`, "FOO", "bar\\r baz")
360395

361-
parseAndCompare(t, `="value"`, "", "value")
362396
parseAndCompare(t, `KEY="`, "KEY", "\"")
363397
parseAndCompare(t, `KEY="value`, "KEY", "\"value")
364398

@@ -370,8 +404,8 @@ func TestParsing(t *testing.T) {
370404
// it 'throws an error if line format is incorrect' do
371405
// expect{env('lol$wut')}.to raise_error(Dotenv::FormatError)
372406
badlyFormattedLine := "lol$wut"
373-
_, _, err := parseLine(badlyFormattedLine, noopPresets)
374-
if err == nil {
407+
k, _, _ := parseLine(badlyFormattedLine, noopPresets)
408+
if k != "" {
375409
t.Errorf("Expected \"%v\" to return error, but it didn't", badlyFormattedLine)
376410
}
377411
}

0 commit comments

Comments
 (0)