-
Notifications
You must be signed in to change notification settings - Fork 1
/
file_loader.go
148 lines (118 loc) · 3.17 KB
/
file_loader.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package env
// A functions to load environment variables from `.env`.
import (
"bufio"
"errors"
"log"
"os"
"regexp"
"strings"
)
const (
defaultFileName = ".env"
separatorChar = "="
commentChar = "#"
exportPrefix = "export"
)
//ErrNoSeparator this error will return if no separator char found in line
var ErrNoSeparator = errors.New("no separator")
//Load environment variables from files.
//If the variable already exists, its value will not change.
func Load(fileNames ...string) error {
for _, filename := range fileNamesOrDefault(fileNames) {
if err := loadVariablesFromFile(filename, false); err != nil {
return err
}
}
return nil
}
//LoadWithOverriding environment variables from files.
//If the variable already exists, its value will change.
func LoadWithOverriding(fileNames ...string) error {
for _, filename := range fileNamesOrDefault(fileNames) {
if err := loadVariablesFromFile(filename, true); err != nil {
return err
}
}
return nil
}
//loadVariablesFromFile parse & set environment variables from file.
//If overload is TRUE value of exists variable will change
func loadVariablesFromFile(fileName string, overload bool) error {
file, err := os.Open(fileName)
if err != nil {
return err
}
defer func() {
if err := file.Close(); err != nil {
log.Println(err)
}
}()
scanner := bufio.NewScanner(file)
envs := map[string]bool{}
for _, rawEnvLine := range os.Environ() {
envs[strings.Split(rawEnvLine, "=")[0]] = true
}
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// ignore line if empty or commented
if strings.HasPrefix(line, commentChar) || len(line) == 0 {
continue
}
k, v, err := parseLine(line)
if err != nil {
return err
}
if _, ok := envs[k]; ok && !overload {
continue
}
if err := os.Setenv(k, v); err != nil {
return err
}
}
if err = scanner.Err(); err != nil {
return err
}
return nil
}
//parseLine parse line to key value
func parseLine(line string) (string, string, error) {
if !strings.Contains(line, separatorChar) {
return "", "", ErrNoSeparator
}
ss := strings.SplitN(line, separatorChar, 2)
k := ss[0]
if strings.HasPrefix(k, exportPrefix) {
k = strings.TrimPrefix(k, exportPrefix)
}
return strings.TrimSpace(k), parseValue(ss[1]), nil
}
//parseValue parse variable value
func parseValue(val string) string {
singleQuotes := regexp.MustCompile(`\A'(.*)'\z`).FindStringSubmatch(val)
doubleQuotes := regexp.MustCompile(`\A"(.*)"\z`).FindStringSubmatch(val)
if singleQuotes != nil || doubleQuotes != nil {
val = val[1 : len(val)-1] // pull the quotes off the edges
}
if doubleQuotes != nil {
val = regexp.MustCompile(`\\.`).ReplaceAllStringFunc(val, func(match string) string {
switch strings.TrimPrefix(match, `\`) {
case "n":
return "\n"
case "r":
return "\r"
default:
return match
}
})
val = regexp.MustCompile(`\\([^$])`).ReplaceAllString(val, "$1") // unescape characters
}
return val
}
//fileNamesOrDefault if slice is empty return slice with default filename
func fileNamesOrDefault(fileNames []string) []string {
if len(fileNames) == 0 {
fileNames = append(fileNames, defaultFileName)
}
return fileNames
}