Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / github.com / joho / godotenv / godotenv.go
1 // Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
2 //
3 // Examples/readme can be found on the github page at https://github.com/joho/godotenv
4 //
5 // The TL;DR is that you make a .env file that looks something like
6 //
7 //              SOME_ENV_VAR=somevalue
8 //
9 // and then in your go code you can call
10 //
11 //              godotenv.Load()
12 //
13 // and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
14 package godotenv
15
16 import (
17         "bufio"
18         "errors"
19         "fmt"
20         "io"
21         "os"
22         "os/exec"
23         "regexp"
24         "sort"
25         "strings"
26 )
27
28 const doubleQuoteSpecialChars = "\\\n\r\"!$`"
29
30 // Load will read your env file(s) and load them into ENV for this process.
31 //
32 // Call this function as close as possible to the start of your program (ideally in main)
33 //
34 // If you call Load without any args it will default to loading .env in the current path
35 //
36 // You can otherwise tell it which files to load (there can be more than one) like
37 //
38 //              godotenv.Load("fileone", "filetwo")
39 //
40 // 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
41 func Load(filenames ...string) (err error) {
42         filenames = filenamesOrDefault(filenames)
43
44         for _, filename := range filenames {
45                 err = loadFile(filename, false)
46                 if err != nil {
47                         return // return early on a spazout
48                 }
49         }
50         return
51 }
52
53 // Overload will read your env file(s) and load them into ENV for this process.
54 //
55 // Call this function as close as possible to the start of your program (ideally in main)
56 //
57 // If you call Overload without any args it will default to loading .env in the current path
58 //
59 // You can otherwise tell it which files to load (there can be more than one) like
60 //
61 //              godotenv.Overload("fileone", "filetwo")
62 //
63 // It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
64 func Overload(filenames ...string) (err error) {
65         filenames = filenamesOrDefault(filenames)
66
67         for _, filename := range filenames {
68                 err = loadFile(filename, true)
69                 if err != nil {
70                         return // return early on a spazout
71                 }
72         }
73         return
74 }
75
76 // Read all env (with same file loading semantics as Load) but return values as
77 // a map rather than automatically writing values into env
78 func Read(filenames ...string) (envMap map[string]string, err error) {
79         filenames = filenamesOrDefault(filenames)
80         envMap = make(map[string]string)
81
82         for _, filename := range filenames {
83                 individualEnvMap, individualErr := readFile(filename)
84
85                 if individualErr != nil {
86                         err = individualErr
87                         return // return early on a spazout
88                 }
89
90                 for key, value := range individualEnvMap {
91                         envMap[key] = value
92                 }
93         }
94
95         return
96 }
97
98 // Parse reads an env file from io.Reader, returning a map of keys and values.
99 func Parse(r io.Reader) (envMap map[string]string, err error) {
100         envMap = make(map[string]string)
101
102         var lines []string
103         scanner := bufio.NewScanner(r)
104         for scanner.Scan() {
105                 lines = append(lines, scanner.Text())
106         }
107
108         if err = scanner.Err(); err != nil {
109                 return
110         }
111
112         for _, fullLine := range lines {
113                 if !isIgnoredLine(fullLine) {
114                         var key, value string
115                         key, value, err = parseLine(fullLine, envMap)
116
117                         if err != nil {
118                                 return
119                         }
120                         envMap[key] = value
121                 }
122         }
123         return
124 }
125
126 //Unmarshal reads an env file from a string, returning a map of keys and values.
127 func Unmarshal(str string) (envMap map[string]string, err error) {
128         return Parse(strings.NewReader(str))
129 }
130
131 // Exec loads env vars from the specified filenames (empty map falls back to default)
132 // then executes the cmd specified.
133 //
134 // Simply hooks up os.Stdin/err/out to the command and calls Run()
135 //
136 // If you want more fine grained control over your command it's recommended
137 // that you use `Load()` or `Read()` and the `os/exec` package yourself.
138 func Exec(filenames []string, cmd string, cmdArgs []string) error {
139         Load(filenames...)
140
141         command := exec.Command(cmd, cmdArgs...)
142         command.Stdin = os.Stdin
143         command.Stdout = os.Stdout
144         command.Stderr = os.Stderr
145         return command.Run()
146 }
147
148 // Write serializes the given environment and writes it to a file
149 func Write(envMap map[string]string, filename string) error {
150         content, error := Marshal(envMap)
151         if error != nil {
152                 return error
153         }
154         file, error := os.Create(filename)
155         if error != nil {
156                 return error
157         }
158         _, err := file.WriteString(content)
159         return err
160 }
161
162 // Marshal outputs the given environment as a dotenv-formatted environment file.
163 // Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped.
164 func Marshal(envMap map[string]string) (string, error) {
165         lines := make([]string, 0, len(envMap))
166         for k, v := range envMap {
167                 lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v)))
168         }
169         sort.Strings(lines)
170         return strings.Join(lines, "\n"), nil
171 }
172
173 func filenamesOrDefault(filenames []string) []string {
174         if len(filenames) == 0 {
175                 return []string{".env"}
176         }
177         return filenames
178 }
179
180 func loadFile(filename string, overload bool) error {
181         envMap, err := readFile(filename)
182         if err != nil {
183                 return err
184         }
185
186         currentEnv := map[string]bool{}
187         rawEnv := os.Environ()
188         for _, rawEnvLine := range rawEnv {
189                 key := strings.Split(rawEnvLine, "=")[0]
190                 currentEnv[key] = true
191         }
192
193         for key, value := range envMap {
194                 if !currentEnv[key] || overload {
195                         os.Setenv(key, value)
196                 }
197         }
198
199         return nil
200 }
201
202 func readFile(filename string) (envMap map[string]string, err error) {
203         file, err := os.Open(filename)
204         if err != nil {
205                 return
206         }
207         defer file.Close()
208
209         return Parse(file)
210 }
211
212 func parseLine(line string, envMap map[string]string) (key string, value string, err error) {
213         if len(line) == 0 {
214                 err = errors.New("zero length string")
215                 return
216         }
217
218         // ditch the comments (but keep quoted hashes)
219         if strings.Contains(line, "#") {
220                 segmentsBetweenHashes := strings.Split(line, "#")
221                 quotesAreOpen := false
222                 var segmentsToKeep []string
223                 for _, segment := range segmentsBetweenHashes {
224                         if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
225                                 if quotesAreOpen {
226                                         quotesAreOpen = false
227                                         segmentsToKeep = append(segmentsToKeep, segment)
228                                 } else {
229                                         quotesAreOpen = true
230                                 }
231                         }
232
233                         if len(segmentsToKeep) == 0 || quotesAreOpen {
234                                 segmentsToKeep = append(segmentsToKeep, segment)
235                         }
236                 }
237
238                 line = strings.Join(segmentsToKeep, "#")
239         }
240
241         firstEquals := strings.Index(line, "=")
242         firstColon := strings.Index(line, ":")
243         splitString := strings.SplitN(line, "=", 2)
244         if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) {
245                 //this is a yaml-style line
246                 splitString = strings.SplitN(line, ":", 2)
247         }
248
249         if len(splitString) != 2 {
250                 err = errors.New("Can't separate key from value")
251                 return
252         }
253
254         // Parse the key
255         key = splitString[0]
256         if strings.HasPrefix(key, "export") {
257                 key = strings.TrimPrefix(key, "export")
258         }
259         key = strings.Trim(key, " ")
260
261         // Parse the value
262         value = parseValue(splitString[1], envMap)
263         return
264 }
265
266 func parseValue(value string, envMap map[string]string) string {
267
268         // trim
269         value = strings.Trim(value, " ")
270
271         // check if we've got quoted values or possible escapes
272         if len(value) > 1 {
273                 rs := regexp.MustCompile(`\A'(.*)'\z`)
274                 singleQuotes := rs.FindStringSubmatch(value)
275
276                 rd := regexp.MustCompile(`\A"(.*)"\z`)
277                 doubleQuotes := rd.FindStringSubmatch(value)
278
279                 if singleQuotes != nil || doubleQuotes != nil {
280                         // pull the quotes off the edges
281                         value = value[1 : len(value)-1]
282                 }
283
284                 if doubleQuotes != nil {
285                         // expand newlines
286                         escapeRegex := regexp.MustCompile(`\\.`)
287                         value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string {
288                                 c := strings.TrimPrefix(match, `\`)
289                                 switch c {
290                                 case "n":
291                                         return "\n"
292                                 case "r":
293                                         return "\r"
294                                 default:
295                                         return match
296                                 }
297                         })
298                         // unescape characters
299                         e := regexp.MustCompile(`\\([^$])`)
300                         value = e.ReplaceAllString(value, "$1")
301                 }
302
303                 if singleQuotes == nil {
304                         value = expandVariables(value, envMap)
305                 }
306         }
307
308         return value
309 }
310
311 func expandVariables(v string, m map[string]string) string {
312         r := regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)
313
314         return r.ReplaceAllStringFunc(v, func(s string) string {
315                 submatch := r.FindStringSubmatch(s)
316
317                 if submatch == nil {
318                         return s
319                 }
320                 if submatch[1] == "\\" || submatch[2] == "(" {
321                         return submatch[0][1:]
322                 } else if submatch[4] != "" {
323                         return m[submatch[4]]
324                 }
325                 return s
326         })
327 }
328
329 func isIgnoredLine(line string) bool {
330         trimmedLine := strings.Trim(line, " \n\t")
331         return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
332 }
333
334 func doubleQuoteEscape(line string) string {
335         for _, c := range doubleQuoteSpecialChars {
336                 toReplace := "\\" + string(c)
337                 if c == '\n' {
338                         toReplace = `\n`
339                 }
340                 if c == '\r' {
341                         toReplace = `\r`
342                 }
343                 line = strings.Replace(line, string(c), toReplace, -1)
344         }
345         return line
346 }