Skip to content

Commit 25bc9d6

Browse files
committed
Refactor parser to avoid repeated scan/gsub
1 parent 94d0a62 commit 25bc9d6

File tree

2 files changed

+34
-40
lines changed

2 files changed

+34
-40
lines changed

lib/dotenv/parser.rb

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,23 @@ class Parser
1414
]
1515

1616
LINE = /
17-
(?:^|\A) # beginning of line
18-
\s* # leading whitespace
19-
(?:export\s+)? # optional export
20-
([\w.]+) # key
21-
(?:\s*=\s*?|:\s+?) # separator
22-
( # optional value begin
23-
\s*'(?:\\'|[^'])*' # single quoted value
24-
| # or
25-
\s*"(?:\\"|[^"])*" # double quoted value
26-
| # or
27-
[^\#\r\n]+ # unquoted value
28-
)? # value end
29-
\s* # trailing whitespace
30-
(?:\#.*)? # optional comment
31-
(?:$|\z) # end of line
17+
(?:^|\A) # beginning of line
18+
\s* # leading whitespace
19+
(?<export>export\s+)? # optional export
20+
(?<key>[\w.]+) # key
21+
(?: # optional separator and value
22+
(?:\s*=\s*?|:\s+?) # separator
23+
(?<value> # optional value begin
24+
\s*'(?:\\'|[^'])*' # single quoted value
25+
| # or
26+
\s*"(?:\\"|[^"])*" # double quoted value
27+
| # or
28+
[^\#\n]+ # unquoted value
29+
)? # value end
30+
)? # separator and value end
31+
\s* # trailing whitespace
32+
(?:\#.*)? # optional comment
33+
(?:$|\z) # end of line
3234
/x
3335

3436
class << self
@@ -40,25 +42,29 @@ def call(...)
4042
end
4143

4244
def initialize(string, overwrite: false)
43-
@string = string
45+
# Convert line breaks to same format
46+
@string = string.gsub(/[\n\r]+/, "\n")
4447
@hash = {}
4548
@overwrite = overwrite
4649
end
4750

4851
def call
49-
# Convert line breaks to same format
50-
lines = @string.gsub(/\r\n?/, "\n")
51-
# Process matches
52-
lines.scan(LINE).each do |key, value|
53-
# Skip parsing values that will be ignored
54-
next if ignore?(key)
52+
@string.scan(LINE) do
53+
match = $LAST_MATCH_INFO
5554

56-
@hash[key] = parse_value(value || "")
57-
end
58-
# Process non-matches
59-
lines.gsub(LINE, "").split(/[\n\r]+/).each do |line|
60-
parse_line(line)
55+
# Skip parsing values that will be ignored
56+
next if ignore?(match[:key])
57+
58+
# Check for exported variable with no value
59+
if match[:export] && !match[:value]
60+
if !@hash.member?(match[:key])
61+
raise FormatError, "Line #{match.to_s.inspect} has an unset variable"
62+
end
63+
else
64+
@hash[match[:key]] = parse_value(match[:value] || "")
65+
end
6166
end
67+
6268
@hash
6369
end
6470

@@ -69,14 +75,6 @@ def ignore?(key)
6975
!@overwrite && key != "DOTENV_LINEBREAK_MODE" && ENV.key?(key)
7076
end
7177

72-
def parse_line(line)
73-
if line.split.first == "export"
74-
if variable_not_set?(line)
75-
raise FormatError, "Line #{line.inspect} has an unset variable"
76-
end
77-
end
78-
end
79-
8078
QUOTED_STRING = /\A(['"])(.*)\1\z/m
8179
def parse_value(value)
8280
# Remove surrounding quotes
@@ -106,9 +104,5 @@ def expand_newlines(value)
106104
value.gsub('\n', "\\\\\\n").gsub('\r', "\\\\\\r")
107105
end
108106
end
109-
110-
def variable_not_set?(line)
111-
!line.split[1..].all? { |var| @hash.member?(var) }
112-
end
113107
end
114108
end

lib/dotenv/template.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def is_comment?(line)
3434

3535
def var_defined?(line)
3636
match = Dotenv::Parser::LINE.match(line)
37-
match && match[1]
37+
match && match[:key]
3838
end
3939

4040
def line_blank?(line)

0 commit comments

Comments
 (0)