Repository navigation
Expand file tree
/
Copy pathparse.go
More file actions
142 lines (124 loc) · 3.62 KB
/
Copy pathparse.go
File metadata and controls
142 lines (124 loc) · 3.62 KB
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
package envconfig
import (
"bufio"
"fmt"
"os"
"strings"
)
// Parse reads a .env-style file at path and returns its contents as a map of
// key/value pairs.
//
// The file format supports:
//
// - Full-line comments: any line whose first non-whitespace character is
// '#' is skipped entirely.
// - Blank lines: lines containing only whitespace are skipped.
// - "KEY=value" assignments. The key is trimmed of surrounding
// whitespace. Everything after the first '=' is treated as the value.
// - Double-quoted values ("like this"): surrounding quotes are stripped
// and a small set of backslash escapes are processed inside the value:
// \n (newline), \t (tab), \r (carriage return), \" (literal double
// quote), \\ (literal backslash). Any other backslash sequence is left
// as-is (the backslash and following character are both kept).
// - Single-quoted values ('like this'): surrounding quotes are stripped.
// No escape processing is performed inside single quotes -- the
// content between the quotes is used verbatim, matching common shell
// and dotenv conventions.
// - Unquoted values: the value is trimmed of surrounding whitespace.
//
// Lines that do not contain an '=' are ignored. Parse does not mutate the
// process environment; it only returns the parsed key/value pairs. Callers
// that want the values to fall back to OS environment variables should use
// Load, which implements that precedence explicitly.
func Parse(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("envconfig: failed to open %q: %w", path, err)
}
defer f.Close()
result := make(map[string]string)
scanner := bufio.NewScanner(f)
lineNo := 0
for scanner.Scan() {
lineNo++
line := scanner.Text()
trimmed := strings.TrimSpace(line)
if trimmed == "" {
continue
}
if strings.HasPrefix(trimmed, "#") {
continue
}
eq := strings.Index(trimmed, "=")
if eq < 0 {
// Not a KEY=value line; ignore it rather than failing, so
// stray lines don't break otherwise-valid files.
continue
}
key := strings.TrimSpace(trimmed[:eq])
if key == "" {
continue
}
rawValue := strings.TrimSpace(trimmed[eq+1:])
value, err := parseValue(rawValue)
if err != nil {
return nil, fmt.Errorf("envconfig: %q line %d: %w", path, lineNo, err)
}
result[key] = value
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("envconfig: failed to read %q: %w", path, err)
}
return result, nil
}
// parseValue interprets the raw right-hand side of a KEY=value assignment,
// handling quoted and unquoted forms.
func parseValue(raw string) (string, error) {
if len(raw) >= 2 {
first := raw[0]
last := raw[len(raw)-1]
switch {
case first == '"' && last == '"':
return unescapeDouble(raw[1 : len(raw)-1]), nil
case first == '\'' && last == '\'':
return raw[1 : len(raw)-1], nil
}
}
return raw, nil
}
// unescapeDouble processes a small set of backslash escapes inside a
// double-quoted value: \n, \t, \r, \", \\. Any other backslash sequence is
// left untouched (both the backslash and the following rune are kept).
func unescapeDouble(s string) string {
var b strings.Builder
b.Grow(len(s))
runes := []rune(s)
for i := 0; i < len(runes); i++ {
c := runes[i]
if c != '\\' || i == len(runes)-1 {
b.WriteRune(c)
continue
}
next := runes[i+1]
switch next {
case 'n':
b.WriteRune('\n')
i++
case 't':
b.WriteRune('\t')
i++
case 'r':
b.WriteRune('\r')
i++
case '"':
b.WriteRune('"')
i++
case '\\':
b.WriteRune('\\')
i++
default:
b.WriteRune(c)
}
}
return b.String()
}