Skip to content

Commit

Permalink
Small refactoring of parser to make it easier to grok
Browse files Browse the repository at this point in the history
  • Loading branch information
bkeepers committed Dec 12, 2024
1 parent b396779 commit 94d0a62
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 39 deletions.
51 changes: 25 additions & 26 deletions lib/dotenv/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -40,15 +42,16 @@ 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
# Convert line breaks to same format
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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
21 changes: 8 additions & 13 deletions lib/dotenv/substitutions/variable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 94d0a62

Please sign in to comment.