From 94d0a62f92fb746205d7dd51299bbb4c80cbdcb1 Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Thu, 12 Dec 2024 16:23:14 -0500 Subject: [PATCH] Small refactoring of parser to make it easier to grok --- lib/dotenv/parser.rb | 51 ++++++++++++++-------------- lib/dotenv/substitutions/variable.rb | 21 +++++------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/lib/dotenv/parser.rb b/lib/dotenv/parser.rb index d489681..a7c0cea 100644 --- a/lib/dotenv/parser.rb +++ b/lib/dotenv/parser.rb @@ -8,8 +8,10 @@ class FormatError < SyntaxError; end # Parses the `.env` file format into key/value pairs. # It allows for variable substitutions, command substitutions, and exporting of variables. class Parser - @substitutions = - [Dotenv::Substitutions::Variable, Dotenv::Substitutions::Command] + @substitutions = [ + Dotenv::Substitutions::Variable, + Dotenv::Substitutions::Command + ] LINE = / (?:^|\A) # beginning of line @@ -40,7 +42,7 @@ def call(...) def initialize(string, overwrite: false) @string = string @hash = {} - @variables_to_ignore = overwrite ? nil : ENV.except("DOTENV_LINEBREAK_MODE") + @overwrite = overwrite end def call @@ -48,7 +50,8 @@ def call lines = @string.gsub(/\r\n?/, "\n") # Process matches lines.scan(LINE).each do |key, value| - next if @variables_to_ignore&.include?(key) + # Skip parsing values that will be ignored + next if ignore?(key) @hash[key] = parse_value(value || "") end @@ -61,6 +64,11 @@ def call private + # Determine if the key can be ignored. + 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) @@ -69,12 +77,22 @@ def parse_line(line) end end + QUOTED_STRING = /\A(['"])(.*)\1\z/m def parse_value(value) # Remove surrounding quotes - value = value.strip.sub(/\A(['"])(.*)\1\z/m, '\2') + value = value.strip.sub(QUOTED_STRING, '\2') maybe_quote = Regexp.last_match(1) - value = unescape_value(value, maybe_quote) - perform_substitutions(value, maybe_quote) + + # Expand new lines in double quoted values + value = expand_newlines(value) if maybe_quote == '"' + + # Unescape characters and performs substitutions unless value is single quoted + if maybe_quote != "'" + value = unescape_characters(value) + self.class.substitutions.each { |proc| value = proc.call(value, @hash) } + end + + value end def unescape_characters(value) @@ -92,24 +110,5 @@ def expand_newlines(value) def variable_not_set?(line) !line.split[1..].all? { |var| @hash.member?(var) } end - - def unescape_value(value, maybe_quote) - if maybe_quote == '"' - unescape_characters(expand_newlines(value)) - elsif maybe_quote.nil? - unescape_characters(value) - else - value - end - end - - def perform_substitutions(value, maybe_quote) - if maybe_quote != "'" - self.class.substitutions.each do |proc| - value = proc.call(value, @hash) - end - end - value - end end end diff --git a/lib/dotenv/substitutions/variable.rb b/lib/dotenv/substitutions/variable.rb index ee98c4e..062e516 100644 --- a/lib/dotenv/substitutions/variable.rb +++ b/lib/dotenv/substitutions/variable.rb @@ -12,7 +12,7 @@ class << self VARIABLE = / (\\)? # is it escaped with a backslash? (\$) # literal $ - (?!\() # shouldnt be followed by paranthesis + (?!\() # shouldn't be followed by parenthesis \{? # allow brace wrapping ([A-Z0-9_]+)? # optional alpha nums \}? # closing brace @@ -21,19 +21,14 @@ class << self def call(value, env) value.gsub(VARIABLE) do |variable| match = $LAST_MATCH_INFO - substitute(match, variable, env) - end - end - - private - def substitute(match, variable, env) - if match[1] == "\\" - variable[1..] - elsif match[3] - env[match[3]] || ENV[match[3]] || "" - else - variable + if match[1] == "\\" + variable[1..] + elsif match[3] + env[match[3]] || ENV[match[3]] || "" + else + variable + end end end end