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 149 150
|
// Package dotenv implements the parsing of the .env format.
//
// There is no formal definition of the format but it has been introduced by
// https://github.com/bkeepers/dotenv which is thus canonical.
package dotenv
import (
"fmt"
"os"
"regexp"
"strings"
)
// LINE is the regexp matching a single line
const LINE = `
\A
\s*
(?:|#.*| # comment line
(?:export\s+)? # optional export
([\w\.]+) # key
(?:\s*=\s*|:\s+?) # separator
( # optional value begin
'(?:\'|[^'])*' # single quoted value
| # or
"(?:\"|[^"])*" # double quoted value
| # or
[^\s#\n]+ # unquoted value
)? # value end
\s*
(?:\#.*)? # optional comment
)
\z
`
var linesRe = regexp.MustCompile(`[\r\n]+`)
var lineRe = regexp.MustCompile(
regexp.MustCompile(`\s+`).ReplaceAllLiteralString(
regexp.MustCompile(`\s+# .*`).ReplaceAllLiteralString(LINE, ""), ""))
// Parse reads a string in the .env format and returns a map of the extracted key=values.
//
// Ported from https://github.com/bkeepers/dotenv/blob/84f33f48107c492c3a99bd41c1059e7b4c1bb67a/lib/dotenv/parser.rb
func Parse(data string) (map[string]string, error) {
var dotenv = make(map[string]string)
for _, line := range linesRe.Split(data, -1) {
if !lineRe.MatchString(line) {
return nil, fmt.Errorf("invalid line: %s", line)
}
match := lineRe.FindStringSubmatch(line)
// commented or empty line
if len(match) == 0 {
continue
}
if len(match[1]) == 0 {
continue
}
key := match[1]
value := match[2]
parseValue(key, value, dotenv)
}
return dotenv, nil
}
// MustParse works the same as Parse but panics on error
func MustParse(data string) map[string]string {
env, err := Parse(data)
if err != nil {
panic(err)
}
return env
}
func parseValue(key string, value string, dotenv map[string]string) {
if len(value) <= 1 {
dotenv[key] = value
return
}
singleQuoted := false
if value[0:1] == "'" && value[len(value)-1:] == "'" {
// single-quoted string, do not expand
singleQuoted = true
value = value[1 : len(value)-1]
} else if value[0:1] == `"` && value[len(value)-1:] == `"` {
value = value[1 : len(value)-1]
value = expandNewLines(value)
value = unescapeCharacters(value)
}
if !singleQuoted {
value = expandEnv(value, dotenv)
}
dotenv[key] = value
}
var escRe = regexp.MustCompile(`\\([^$])`)
func unescapeCharacters(value string) string {
return escRe.ReplaceAllString(value, "$1")
}
func expandNewLines(value string) string {
value = strings.ReplaceAll(value, "\\n", "\n")
value = strings.ReplaceAll(value, "\\r", "\r")
return value
}
func expandEnv(value string, dotenv map[string]string) string {
expander := func(value string) string {
envKey, defaultValue, hasDefault := splitKeyAndDefault(value, ":-")
expanded, found := lookupDotenv(envKey, dotenv)
if found {
return expanded
}
return getFromEnvOrDefault(envKey, defaultValue, hasDefault)
}
return os.Expand(value, expander)
}
func splitKeyAndDefault(value string, sep string) (string, string, bool) {
var i = strings.Index(value, sep)
if i == -1 {
return value, "", false
}
return value[0:i], value[i+len(sep):], true
}
func lookupDotenv(value string, dotenv map[string]string) (string, bool) {
retval, ok := dotenv[value]
return retval, ok
}
func getFromEnvOrDefault(envKey string, defaultValue string, hasDefault bool) string {
var envValue = os.Getenv(envKey)
if len(envValue) == 0 && hasDefault {
return defaultValue
}
return envValue
}
|