Codebase list golang-github-subosito-gotenv / upstream/1.2.0+git20190917.de67a66
New upstream version 1.2.0+git20190917.de67a66 Andreas Henriksson 4 years ago
16 changed file(s) with 876 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 HELLO=world
0 lol$wut
0 name: Go workflow
1 on: [push]
2 jobs:
3 test:
4 name: Test on ${{ matrix.os }}
5 runs-on: ${{ matrix.os }}
6 strategy:
7 matrix:
8 os: [ubuntu-latest, windows-latest, macos-latest]
9 go: ['1.12', '1.13']
10 steps:
11 - name: Go ${{ matrix.go }}
12 uses: actions/setup-go@v1
13 with:
14 version: ${{ matrix.go }}
15 - name: Checkout source code
16 uses: actions/checkout@master
17 - name: Get dependencies
18 run: go get -t -v
19 - name: Run test
20 run: go test -test.v -coverprofile=coverage.out -covermode=count
21 - name: Publish coverage
22 if: matrix.os != 'windows-latest'
23 env:
24 CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
25 run: bash <(curl -s https://codecov.io/bash)
0 *.test
1 *.out
2 annotate.json
0 # Changelog
1
2 ## [1.2.0] - 2019-08-03
3
4 ### Added
5
6 - Add `Must` helper to raise an error as panic. It can be used with `Load` and `OverLoad`.
7 - Add more tests to be 100% coverage.
8 - Add CHANGELOG
9 - Add more OS for the test: OSX and Windows
10
11 ### Changed
12
13 - Reduce complexity and improve source code for having `A+` score in [goreportcard](https://goreportcard.com/report/github.com/subosito/gotenv).
14 - Updated README with mentions to all available functions
15
16 ### Removed
17
18 - Remove `ErrFormat`
19 - Remove `MustLoad` and `MustOverload`, replaced with `Must` helper.
20
21 ## [1.1.1] - 2018-06-05
22
23 ### Changed
24
25 - Replace `os.Getenv` with `os.LookupEnv` to ensure that the environment variable is not set, by [radding](https://github.com/radding)
26
27 ## [1.1.0] - 2017-03-20
28
29 ### Added
30
31 - Supports carriage return in env
32 - Handle files with UTF-8 BOM
33
34 ### Changed
35
36 - Whitespace handling
37
38 ### Fixed
39
40 - Incorrect variable expansion
41 - Handling escaped '$' characters
42
43 ## [1.0.0] - 2014-10-05
44
45 First stable release.
46
0 The MIT License (MIT)
1
2 Copyright (c) 2013 Alif Rachmawadi
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
0 # gotenv
1
2 [![Build Status](https://github.com/subosito/gotenv/workflows/Go%20workflow/badge.svg)](https://github.com/subosito/gotenv/actions)
3 [![Coverage Status](https://badgen.net/codecov/c/github/subosito/gotenv)](https://codecov.io/gh/subosito/gotenv)
4 [![Go Report Card](https://goreportcard.com/badge/github.com/subosito/gotenv)](https://goreportcard.com/report/github.com/subosito/gotenv)
5 [![GoDoc](https://godoc.org/github.com/subosito/gotenv?status.svg)](https://godoc.org/github.com/subosito/gotenv)
6
7 Load environment variables from `.env` or `io.Reader` in Go.
8
9 ## Usage
10
11 Put the gotenv package on your `import` statement:
12
13 ```go
14 import "github.com/subosito/gotenv"
15 ```
16
17 To modify your app environment variables, `gotenv` expose 2 main functions:
18
19 - `gotenv.Load`
20 - `gotenv.Apply`
21
22 By default, `gotenv.Load` will look for a file called `.env` in the current working directory.
23
24 Behind the scene, it will then load `.env` file and export the valid variables to the environment variables. Make sure you call the method as soon as possible to ensure it loads all variables, say, put it on `init()` function.
25
26 Once loaded you can use `os.Getenv()` to get the value of the variable.
27
28 Let's say you have `.env` file:
29
30 ```
31 APP_ID=1234567
32 APP_SECRET=abcdef
33 ```
34
35 Here's the example of your app:
36
37 ```go
38 package main
39
40 import (
41 "github.com/subosito/gotenv"
42 "log"
43 "os"
44 )
45
46 func init() {
47 gotenv.Load()
48 }
49
50 func main() {
51 log.Println(os.Getenv("APP_ID")) // "1234567"
52 log.Println(os.Getenv("APP_SECRET")) // "abcdef"
53 }
54 ```
55
56 You can also load other than `.env` file if you wish. Just supply filenames when calling `Load()`. It will load them in order and the first value set for a variable will win.:
57
58 ```go
59 gotenv.Load(".env.production", "credentials")
60 ```
61
62 While `gotenv.Load` loads entries from `.env` file, `gotenv.Apply` allows you to use any `io.Reader`:
63
64 ```go
65 gotenv.Apply(strings.NewReader("APP_ID=1234567"))
66
67 log.Println(os.Getenv("APP_ID"))
68 // Output: "1234567"
69 ```
70
71 Both `gotenv.Load` and `gotenv.Apply` **DO NOT** overrides existing environment variables. If you want to override existing ones, you can see section below.
72
73 ### Environment Overrides
74
75 Besides above functions, `gotenv` also provides another functions that overrides existing:
76
77 - `gotenv.OverLoad`
78 - `gotenv.OverApply`
79
80
81 Here's the example of this overrides behavior:
82
83 ```go
84 os.Setenv("HELLO", "world")
85
86 // NOTE: using Apply existing value will be reserved
87 gotenv.Apply(strings.NewReader("HELLO=universe"))
88 fmt.Println(os.Getenv("HELLO"))
89 // Output: "world"
90
91 // NOTE: using OverApply existing value will be overridden
92 gotenv.OverApply(strings.NewReader("HELLO=universe"))
93 fmt.Println(os.Getenv("HELLO"))
94 // Output: "universe"
95 ```
96
97 ### Throw a Panic
98
99 Both `gotenv.Load` and `gotenv.OverLoad` returns an error on something wrong occurred, like your env file is not exist, and so on. To make it easier to use, `gotenv` also provides `gotenv.Must` helper, to let it panic when an error returned.
100
101 ```go
102 err := gotenv.Load(".env-is-not-exist")
103 fmt.Println("error", err)
104 // error: open .env-is-not-exist: no such file or directory
105
106 gotenv.Must(gotenv.Load, ".env-is-not-exist")
107 // it will throw a panic
108 // panic: open .env-is-not-exist: no such file or directory
109 ```
110
111 ### Another Scenario
112
113 Just in case you want to parse environment variables from any `io.Reader`, gotenv keeps its `Parse` and `StrictParse` function as public API so you can use that.
114
115 ```go
116 // import "strings"
117
118 pairs := gotenv.Parse(strings.NewReader("FOO=test\nBAR=$FOO"))
119 // gotenv.Env{"FOO": "test", "BAR": "test"}
120
121 err, pairs = gotenv.StrictParse(strings.NewReader(`FOO="bar"`))
122 // gotenv.Env{"FOO": "bar"}
123 ```
124
125 `Parse` ignores invalid lines and returns `Env` of valid environment variables, while `StrictParse` returns an error for invalid lines.
126
127 ## Notes
128
129 The gotenv package is a Go port of [`dotenv`](https://github.com/bkeepers/dotenv) project with some additions made for Go. For general features, it aims to be compatible as close as possible.
0 BOM=UTF-8
0 export OPTION_A=2
1 export OPTION_B='\n'
0 OPTION_A=1
1 OPTION_B=2
2 OPTION_C= 3
3 OPTION_D =4
4 OPTION_E = 5
0 OPTION_A='1'
1 OPTION_B='2'
2 OPTION_C=''
3 OPTION_D='\n'
4 OPTION_E="1"
5 OPTION_F="2"
6 OPTION_G=""
7 OPTION_H="\n"
0 OPTION_A: 1
1 OPTION_B: '2'
2 OPTION_C: ''
3 OPTION_D: '\n'
0 module github.com/subosito/gotenv
1
2 go 1.13
3
4 require github.com/stretchr/testify v1.4.0
0 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
1 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
5 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
6 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
7 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
10 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
0 // Package gotenv provides functionality to dynamically load the environment variables
1 package gotenv
2
3 import (
4 "bufio"
5 "fmt"
6 "io"
7 "os"
8 "regexp"
9 "strings"
10 )
11
12 const (
13 // Pattern for detecting valid line format
14 linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z`
15
16 // Pattern for detecting valid variable within a value
17 variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)`
18 )
19
20 // Env holds key/value pair of valid environment variable
21 type Env map[string]string
22
23 /*
24 Load is a function to load a file or multiple files and then export the valid variables into environment variables if they do not exist.
25 When it's called with no argument, it will load `.env` file on the current path and set the environment variables.
26 Otherwise, it will loop over the filenames parameter and set the proper environment variables.
27 */
28 func Load(filenames ...string) error {
29 return loadenv(false, filenames...)
30 }
31
32 /*
33 OverLoad is a function to load a file or multiple files and then export and override the valid variables into environment variables.
34 */
35 func OverLoad(filenames ...string) error {
36 return loadenv(true, filenames...)
37 }
38
39 /*
40 Must is wrapper function that will panic when supplied function returns an error.
41 */
42 func Must(fn func(filenames ...string) error, filenames ...string) {
43 if err := fn(filenames...); err != nil {
44 panic(err.Error())
45 }
46 }
47
48 /*
49 Apply is a function to load an io Reader then export the valid variables into environment variables if they do not exist.
50 */
51 func Apply(r io.Reader) error {
52 return parset(r, false)
53 }
54
55 /*
56 OverApply is a function to load an io Reader then export and override the valid variables into environment variables.
57 */
58 func OverApply(r io.Reader) error {
59 return parset(r, true)
60 }
61
62 func loadenv(override bool, filenames ...string) error {
63 if len(filenames) == 0 {
64 filenames = []string{".env"}
65 }
66
67 for _, filename := range filenames {
68 f, err := os.Open(filename)
69 if err != nil {
70 return err
71 }
72
73 err = parset(f, override)
74 if err != nil {
75 return err
76 }
77
78 f.Close()
79 }
80
81 return nil
82 }
83
84 // parse and set :)
85 func parset(r io.Reader, override bool) error {
86 env, err := StrictParse(r)
87 if err != nil {
88 return err
89 }
90
91 for key, val := range env {
92 setenv(key, val, override)
93 }
94
95 return nil
96 }
97
98 func setenv(key, val string, override bool) {
99 if override {
100 os.Setenv(key, val)
101 } else {
102 if _, present := os.LookupEnv(key); !present {
103 os.Setenv(key, val)
104 }
105 }
106 }
107
108 // Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
109 // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
110 // This function is skipping any invalid lines and only processing the valid one.
111 func Parse(r io.Reader) Env {
112 env, _ := StrictParse(r)
113 return env
114 }
115
116 // StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables.
117 // It expands the value of a variable from the environment variable but does not set the value to the environment itself.
118 // This function is returning an error if there are any invalid lines.
119 func StrictParse(r io.Reader) (Env, error) {
120 env := make(Env)
121 scanner := bufio.NewScanner(r)
122
123 i := 1
124 bom := string([]byte{239, 187, 191})
125
126 for scanner.Scan() {
127 line := scanner.Text()
128
129 if i == 1 {
130 line = strings.TrimPrefix(line, bom)
131 }
132
133 i++
134
135 err := parseLine(line, env)
136 if err != nil {
137 return env, err
138 }
139 }
140
141 return env, nil
142 }
143
144 func parseLine(s string, env Env) error {
145 rl := regexp.MustCompile(linePattern)
146 rm := rl.FindStringSubmatch(s)
147
148 if len(rm) == 0 {
149 return checkFormat(s, env)
150 }
151
152 key := rm[1]
153 val := rm[2]
154
155 // determine if string has quote prefix
156 hdq := strings.HasPrefix(val, `"`)
157
158 // determine if string has single quote prefix
159 hsq := strings.HasPrefix(val, `'`)
160
161 // trim whitespace
162 val = strings.Trim(val, " ")
163
164 // remove quotes '' or ""
165 rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`)
166 val = rq.ReplaceAllString(val, "$2")
167
168 if hdq {
169 val = strings.Replace(val, `\n`, "\n", -1)
170 val = strings.Replace(val, `\r`, "\r", -1)
171
172 // Unescape all characters except $ so variables can be escaped properly
173 re := regexp.MustCompile(`\\([^$])`)
174 val = re.ReplaceAllString(val, "$1")
175 }
176
177 rv := regexp.MustCompile(variablePattern)
178 fv := func(s string) string {
179 return varReplacement(s, hsq, env)
180 }
181
182 val = rv.ReplaceAllStringFunc(val, fv)
183 val = parseVal(val, env)
184
185 env[key] = val
186 return nil
187 }
188
189 func parseExport(st string, env Env) error {
190 if strings.HasPrefix(st, "export") {
191 vs := strings.SplitN(st, " ", 2)
192
193 if len(vs) > 1 {
194 if _, ok := env[vs[1]]; !ok {
195 return fmt.Errorf("line `%s` has an unset variable", st)
196 }
197 }
198 }
199
200 return nil
201 }
202
203 func varReplacement(s string, hsq bool, env Env) string {
204 if strings.HasPrefix(s, "\\") {
205 return strings.TrimPrefix(s, "\\")
206 }
207
208 if hsq {
209 return s
210 }
211
212 sn := `(\$)(\{?([A-Z0-9_]+)\}?)`
213 rn := regexp.MustCompile(sn)
214 mn := rn.FindStringSubmatch(s)
215
216 if len(mn) == 0 {
217 return s
218 }
219
220 v := mn[3]
221
222 replace, ok := env[v]
223 if !ok {
224 replace = os.Getenv(v)
225 }
226
227 return replace
228 }
229
230 func checkFormat(s string, env Env) error {
231 st := strings.TrimSpace(s)
232
233 if (st == "") || strings.HasPrefix(st, "#") {
234 return nil
235 }
236
237 if err := parseExport(st, env); err != nil {
238 return err
239 }
240
241 return fmt.Errorf("line `%s` doesn't match format", s)
242 }
243
244 func parseVal(val string, env Env) string {
245 if strings.Contains(val, "=") {
246 if !(val == "\n" || val == "\r") {
247 kv := strings.Split(val, "\n")
248
249 if len(kv) == 1 {
250 kv = strings.Split(val, "\r")
251 }
252
253 if len(kv) > 1 {
254 val = kv[0]
255
256 for i := 1; i < len(kv); i++ {
257 parseLine(kv[i], env)
258 }
259 }
260 }
261 }
262
263 return val
264 }
0 package gotenv_test
1
2 import (
3 "bufio"
4 "os"
5 "strings"
6 "testing"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/subosito/gotenv"
10 )
11
12 var formats = []struct {
13 in string
14 out gotenv.Env
15 preset bool
16 }{
17 // parses unquoted values
18 {`FOO=bar`, gotenv.Env{"FOO": "bar"}, false},
19
20 // parses values with spaces around equal sign
21 {`FOO =bar`, gotenv.Env{"FOO": "bar"}, false},
22 {`FOO= bar`, gotenv.Env{"FOO": "bar"}, false},
23
24 // parses values with leading spaces
25 {` FOO=bar`, gotenv.Env{"FOO": "bar"}, false},
26
27 // parses values with following spaces
28 {`FOO=bar `, gotenv.Env{"FOO": "bar"}, false},
29
30 // parses double quoted values
31 {`FOO="bar"`, gotenv.Env{"FOO": "bar"}, false},
32
33 // parses double quoted values with following spaces
34 {`FOO="bar" `, gotenv.Env{"FOO": "bar"}, false},
35
36 // parses single quoted values
37 {`FOO='bar'`, gotenv.Env{"FOO": "bar"}, false},
38
39 // parses single quoted values with following spaces
40 {`FOO='bar' `, gotenv.Env{"FOO": "bar"}, false},
41
42 // parses escaped double quotes
43 {`FOO="escaped\"bar"`, gotenv.Env{"FOO": `escaped"bar`}, false},
44
45 // parses empty values
46 {`FOO=`, gotenv.Env{"FOO": ""}, false},
47
48 // expands variables found in values
49 {"FOO=test\nBAR=$FOO", gotenv.Env{"FOO": "test", "BAR": "test"}, false},
50
51 // parses variables wrapped in brackets
52 {"FOO=test\nBAR=${FOO}bar", gotenv.Env{"FOO": "test", "BAR": "testbar"}, false},
53
54 // reads variables from ENV when expanding if not found in local env
55 {`BAR=$FOO`, gotenv.Env{"BAR": "test"}, true},
56
57 // expands undefined variables to an empty string
58 {`BAR=$FOO`, gotenv.Env{"BAR": ""}, false},
59
60 // expands variables in quoted strings
61 {"FOO=test\nBAR=\"quote $FOO\"", gotenv.Env{"FOO": "test", "BAR": "quote test"}, false},
62
63 // does not expand variables in single quoted strings
64 {"BAR='quote $FOO'", gotenv.Env{"BAR": "quote $FOO"}, false},
65
66 // does not expand escaped variables
67 {`FOO="foo\$BAR"`, gotenv.Env{"FOO": "foo$BAR"}, false},
68 {`FOO="foo\${BAR}"`, gotenv.Env{"FOO": "foo${BAR}"}, false},
69 {"FOO=test\nBAR=\"foo\\${FOO} ${FOO}\"", gotenv.Env{"FOO": "test", "BAR": "foo${FOO} test"}, false},
70
71 // parses yaml style options
72 {"OPTION_A: 1", gotenv.Env{"OPTION_A": "1"}, false},
73
74 // parses export keyword
75 {"export OPTION_A=2", gotenv.Env{"OPTION_A": "2"}, false},
76
77 // allows export line if you want to do it that way
78 {"OPTION_A=2\nexport OPTION_A", gotenv.Env{"OPTION_A": "2"}, false},
79
80 // expands newlines in quoted strings
81 {`FOO="bar\nbaz"`, gotenv.Env{"FOO": "bar\nbaz"}, false},
82
83 // parses variables with "." in the name
84 {`FOO.BAR=foobar`, gotenv.Env{"FOO.BAR": "foobar"}, false},
85
86 // strips unquoted values
87 {`foo=bar `, gotenv.Env{"foo": "bar"}, false}, // not 'bar '
88
89 // ignores empty lines
90 {"\n \t \nfoo=bar\n \nfizz=buzz", gotenv.Env{"foo": "bar", "fizz": "buzz"}, false},
91
92 // ignores inline comments
93 {"foo=bar # this is foo", gotenv.Env{"foo": "bar"}, false},
94
95 // allows # in quoted value
96 {`foo="bar#baz" # comment`, gotenv.Env{"foo": "bar#baz"}, false},
97
98 // ignores comment lines
99 {"\n\n\n # HERE GOES FOO \nfoo=bar", gotenv.Env{"foo": "bar"}, false},
100
101 // parses # in quoted values
102 {`foo="ba#r"`, gotenv.Env{"foo": "ba#r"}, false},
103 {"foo='ba#r'", gotenv.Env{"foo": "ba#r"}, false},
104
105 // parses # in quoted values with following spaces
106 {`foo="ba#r" `, gotenv.Env{"foo": "ba#r"}, false},
107 {`foo='ba#r' `, gotenv.Env{"foo": "ba#r"}, false},
108
109 // supports carriage return
110 {"FOO=bar\rbaz=fbb", gotenv.Env{"FOO": "bar", "baz": "fbb"}, false},
111
112 // supports carriage return combine with new line
113 {"FOO=bar\r\nbaz=fbb", gotenv.Env{"FOO": "bar", "baz": "fbb"}, false},
114
115 // expands carriage return in quoted strings
116 {`FOO="bar\rbaz"`, gotenv.Env{"FOO": "bar\rbaz"}, false},
117
118 // escape $ properly when no alphabets/numbers/_ are followed by it
119 {`FOO="bar\\$ \\$\\$"`, gotenv.Env{"FOO": "bar$ $$"}, false},
120
121 // ignore $ when it is not escaped and no variable is followed by it
122 {`FOO="bar $ "`, gotenv.Env{"FOO": "bar $ "}, false},
123
124 // parses unquoted values with spaces after separator
125 {`FOO= bar`, gotenv.Env{"FOO": "bar"}, false},
126
127 // allows # in quoted value with spaces after separator
128 {`foo= "bar#baz" # comment`, gotenv.Env{"foo": "bar#baz"}, false},
129 }
130
131 var errorFormats = []struct {
132 in string
133 out gotenv.Env
134 err string
135 }{
136 // allows export line if you want to do it that way and checks for unset variables
137 {"OPTION_A=2\nexport OH_NO_NOT_SET", gotenv.Env{"OPTION_A": "2"}, "line `export OH_NO_NOT_SET` has an unset variable"},
138
139 // throws an error if line format is incorrect
140 {`lol$wut`, gotenv.Env{}, "line `lol$wut` doesn't match format"},
141 }
142
143 var fixtures = []struct {
144 filename string
145 results gotenv.Env
146 }{
147 {
148 "fixtures/exported.env",
149 gotenv.Env{
150 "OPTION_A": "2",
151 "OPTION_B": `\n`,
152 },
153 },
154 {
155 "fixtures/plain.env",
156 gotenv.Env{
157 "OPTION_A": "1",
158 "OPTION_B": "2",
159 "OPTION_C": "3",
160 "OPTION_D": "4",
161 "OPTION_E": "5",
162 },
163 },
164 {
165 "fixtures/quoted.env",
166 gotenv.Env{
167 "OPTION_A": "1",
168 "OPTION_B": "2",
169 "OPTION_C": "",
170 "OPTION_D": `\n`,
171 "OPTION_E": "1",
172 "OPTION_F": "2",
173 "OPTION_G": "",
174 "OPTION_H": "\n",
175 },
176 },
177 {
178 "fixtures/yaml.env",
179 gotenv.Env{
180 "OPTION_A": "1",
181 "OPTION_B": "2",
182 "OPTION_C": "",
183 "OPTION_D": `\n`,
184 },
185 },
186 }
187
188 func TestParse(t *testing.T) {
189 for _, tt := range formats {
190 if tt.preset {
191 os.Setenv("FOO", "test")
192 }
193
194 exp := gotenv.Parse(strings.NewReader(tt.in))
195 assert.Equal(t, tt.out, exp)
196 os.Clearenv()
197 }
198 }
199
200 func TestStrictParse(t *testing.T) {
201 for _, tt := range errorFormats {
202 env, err := gotenv.StrictParse(strings.NewReader(tt.in))
203 assert.Equal(t, tt.err, err.Error())
204 assert.Equal(t, tt.out, env)
205 }
206 }
207
208 func TestLoad(t *testing.T) {
209 for _, tt := range fixtures {
210 err := gotenv.Load(tt.filename)
211 assert.Nil(t, err)
212
213 for key, val := range tt.results {
214 assert.Equal(t, val, os.Getenv(key))
215 }
216
217 os.Clearenv()
218 }
219 }
220
221 func TestLoad_default(t *testing.T) {
222 k := "HELLO"
223 v := "world"
224
225 err := gotenv.Load()
226 assert.Nil(t, err)
227 assert.Equal(t, v, os.Getenv(k))
228 os.Clearenv()
229 }
230
231 func TestLoad_overriding(t *testing.T) {
232 k := "HELLO"
233 v := "universe"
234
235 os.Setenv(k, v)
236 err := gotenv.Load()
237 assert.Nil(t, err)
238 assert.Equal(t, v, os.Getenv(k))
239 os.Clearenv()
240 }
241
242 func TestLoad_Env(t *testing.T) {
243 err := gotenv.Load(".env.invalid")
244 assert.NotNil(t, err)
245 }
246
247 func TestLoad_nonExist(t *testing.T) {
248 file := ".env.not.exist"
249
250 err := gotenv.Load(file)
251 if err == nil {
252 t.Errorf("gotenv.Load(`%s`) => error: `no such file or directory` != nil", file)
253 }
254 }
255
256 func TestLoad_unicodeBOMFixture(t *testing.T) {
257 file := "fixtures/bom.env"
258
259 f, err := os.Open(file)
260 assert.Nil(t, err)
261
262 scanner := bufio.NewScanner(f)
263
264 i := 1
265 bom := string([]byte{239, 187, 191})
266
267 for scanner.Scan() {
268 if i == 1 {
269 line := scanner.Text()
270 assert.True(t, strings.HasPrefix(line, bom))
271 }
272 }
273 }
274
275 func TestLoad_unicodeBOM(t *testing.T) {
276 file := "fixtures/bom.env"
277
278 err := gotenv.Load(file)
279 assert.Nil(t, err)
280 assert.Equal(t, "UTF-8", os.Getenv("BOM"))
281 os.Clearenv()
282 }
283
284 func TestMust_Load(t *testing.T) {
285 for _, tt := range fixtures {
286 assert.NotPanics(t, func() {
287 gotenv.Must(gotenv.Load, tt.filename)
288
289 for key, val := range tt.results {
290 assert.Equal(t, val, os.Getenv(key))
291 }
292
293 os.Clearenv()
294 }, "Caling gotenv.Must with gotenv.Load should NOT panic")
295 }
296 }
297
298 func TestMust_Load_default(t *testing.T) {
299 assert.NotPanics(t, func() {
300 gotenv.Must(gotenv.Load)
301
302 tkey := "HELLO"
303 val := "world"
304
305 assert.Equal(t, val, os.Getenv(tkey))
306 os.Clearenv()
307 }, "Caling gotenv.Must with gotenv.Load without arguments should NOT panic")
308 }
309
310 func TestMust_Load_nonExist(t *testing.T) {
311 assert.Panics(t, func() { gotenv.Must(gotenv.Load, ".env.not.exist") }, "Caling gotenv.Must with gotenv.Load and non exist file SHOULD panic")
312 }
313
314 func TestOverLoad_overriding(t *testing.T) {
315 k := "HELLO"
316 v := "universe"
317
318 os.Setenv(k, v)
319 err := gotenv.OverLoad()
320 assert.Nil(t, err)
321 assert.Equal(t, "world", os.Getenv(k))
322 os.Clearenv()
323 }
324
325 func TestMustOverLoad_nonExist(t *testing.T) {
326 assert.Panics(t, func() { gotenv.Must(gotenv.OverLoad, ".env.not.exist") }, "Caling gotenv.Must with Overgotenv.Load and non exist file SHOULD panic")
327 }
328
329 func TestApply(t *testing.T) {
330 os.Setenv("HELLO", "world")
331 r := strings.NewReader("HELLO=universe")
332 err := gotenv.Apply(r)
333 assert.Nil(t, err)
334 assert.Equal(t, "world", os.Getenv("HELLO"))
335 os.Clearenv()
336 }
337
338 func TestOverApply(t *testing.T) {
339 os.Setenv("HELLO", "world")
340 r := strings.NewReader("HELLO=universe")
341 err := gotenv.OverApply(r)
342 assert.Nil(t, err)
343 assert.Equal(t, "universe", os.Getenv("HELLO"))
344 os.Clearenv()
345 }