From 25bc9d63c5db4218e7f917fd75cf2cc07efd8cbb Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Thu, 12 Dec 2024 17:28:32 -0500 Subject: [PATCH] Refactor parser to avoid repeated scan/gsub --- lib/dotenv/parser.rb | 72 +++++++++++++++++++----------------------- lib/dotenv/template.rb | 2 +- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/lib/dotenv/parser.rb b/lib/dotenv/parser.rb index a7c0cea..402074b 100644 --- a/lib/dotenv/parser.rb +++ b/lib/dotenv/parser.rb @@ -14,21 +14,23 @@ class Parser ] LINE = / - (?:^|\A) # beginning of line - \s* # leading whitespace - (?:export\s+)? # optional export - ([\w.]+) # key - (?:\s*=\s*?|:\s+?) # separator - ( # optional value begin - \s*'(?:\\'|[^'])*' # single quoted value - | # or - \s*"(?:\\"|[^"])*" # double quoted value - | # or - [^\#\r\n]+ # unquoted value - )? # value end - \s* # trailing whitespace - (?:\#.*)? # optional comment - (?:$|\z) # end of line + (?:^|\A) # beginning of line + \s* # leading whitespace + (?export\s+)? # optional export + (?[\w.]+) # key + (?: # optional separator and value + (?:\s*=\s*?|:\s+?) # separator + (? # optional value begin + \s*'(?:\\'|[^'])*' # single quoted value + | # or + \s*"(?:\\"|[^"])*" # double quoted value + | # or + [^\#\n]+ # unquoted value + )? # value end + )? # separator and value end + \s* # trailing whitespace + (?:\#.*)? # optional comment + (?:$|\z) # end of line /x class << self @@ -40,25 +42,29 @@ def call(...) end def initialize(string, overwrite: false) - @string = string + # Convert line breaks to same format + @string = string.gsub(/[\n\r]+/, "\n") @hash = {} @overwrite = overwrite end def call - # Convert line breaks to same format - lines = @string.gsub(/\r\n?/, "\n") - # Process matches - lines.scan(LINE).each do |key, value| - # Skip parsing values that will be ignored - next if ignore?(key) + @string.scan(LINE) do + match = $LAST_MATCH_INFO - @hash[key] = parse_value(value || "") - end - # Process non-matches - lines.gsub(LINE, "").split(/[\n\r]+/).each do |line| - parse_line(line) + # Skip parsing values that will be ignored + next if ignore?(match[:key]) + + # Check for exported variable with no value + if match[:export] && !match[:value] + if !@hash.member?(match[:key]) + raise FormatError, "Line #{match.to_s.inspect} has an unset variable" + end + else + @hash[match[:key]] = parse_value(match[:value] || "") + end end + @hash end @@ -69,14 +75,6 @@ def ignore?(key) !@overwrite && key != "DOTENV_LINEBREAK_MODE" && ENV.key?(key) end - def parse_line(line) - if line.split.first == "export" - if variable_not_set?(line) - raise FormatError, "Line #{line.inspect} has an unset variable" - end - end - end - QUOTED_STRING = /\A(['"])(.*)\1\z/m def parse_value(value) # Remove surrounding quotes @@ -106,9 +104,5 @@ def expand_newlines(value) value.gsub('\n', "\\\\\\n").gsub('\r', "\\\\\\r") end end - - def variable_not_set?(line) - !line.split[1..].all? { |var| @hash.member?(var) } - end end end diff --git a/lib/dotenv/template.rb b/lib/dotenv/template.rb index 2e79376..1dda920 100644 --- a/lib/dotenv/template.rb +++ b/lib/dotenv/template.rb @@ -34,7 +34,7 @@ def is_comment?(line) def var_defined?(line) match = Dotenv::Parser::LINE.match(line) - match && match[1] + match && match[:key] end def line_blank?(line)