-
Notifications
You must be signed in to change notification settings - Fork 35
/
gotenv.go
119 lines (94 loc) · 2.76 KB
/
gotenv.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Package gotenv provides functionality to dynamically load the environment variables
package gotenv
import (
"bufio"
"io"
"os"
"regexp"
"strings"
)
const (
// Pattern for detecting valid line format
linePattern = `\A(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?(?:\s*\#.*)?\z`
// Pattern for detecting valid variable within a value
variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)\}?)`
)
// Holds key/value pair of valid environment variable
type Env map[string]string
/*
Load is function to load a file or multiple files and then export the valid variables which found into environment variables.
When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
Otherwise, it will loop over the filenames parameter and set the proper environment variables.
// processing `.env`
gotenv.Load()
// processing multiple files
gotenv.Load("production.env", "credentials")
*/
func Load(filenames ...string) error {
if len(filenames) == 0 {
filenames = []string{".env"}
}
for _, filename := range filenames {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
// set environment
env := Parse(f)
for key, val := range env {
os.Setenv(key, val)
}
}
return nil
}
// Parse if a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
// It expands the value of a variable from environment variable, but does not set the value to the environment itself.
// This function is skipping any invalid lines and only processing the valid one.
func Parse(r io.Reader) Env {
env := make(Env)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
parseLine(scanner.Text(), env)
}
return env
}
func parseLine(s string, env Env) {
r := regexp.MustCompile(linePattern)
matches := r.FindStringSubmatch(s)
if len(matches) == 0 {
return
}
key := matches[1]
val := matches[2]
// determine if string has quote prefix
hq := strings.HasPrefix(val, `"`)
// trim whitespace
val = strings.Trim(val, " ")
// remove quotes '' or ""
rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`)
val = rq.ReplaceAllString(val, "$2")
if hq {
val = strings.Replace(val, `\n`, "\n", -1)
// Unescape all characters except $ so variables can be escaped properly
re := regexp.MustCompile(`\\([^$])`)
val = re.ReplaceAllString(val, "$1")
}
rv := regexp.MustCompile(variablePattern)
xv := rv.FindStringSubmatch(val)
if len(xv) > 0 {
var replace string
var ok bool
if xv[1] == "\\" {
replace = strings.Join(xv[2:4], "")
} else {
replace, ok = env[xv[4]]
if !ok {
replace = os.Getenv(xv[4])
}
}
val = strings.Replace(val, strings.Join(xv[0:1], ""), replace, -1)
}
env[key] = val
return
}