Skip to content

Commit

Permalink
Merge pull request #515 from andrewts129/andrewts129/faster-parsing
Browse files Browse the repository at this point in the history
Optimize parser by removing repeated hash merges
  • Loading branch information
bkeepers authored Dec 12, 2024
2 parents 6993e8b + 1877fa0 commit b396779
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 7 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ gemspec name: "dotenv"
gemspec name: "dotenv-rails"

gem "railties", "~> #{ENV["RAILS_VERSION"] || "7.1"}"
gem "benchmark-ips"
gem "stackprof"

group :guard do
gem "guard-rspec"
Expand Down
15 changes: 15 additions & 0 deletions benchmark/parse_ips.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require "bundler/setup"
require "dotenv"
require "benchmark/ips"
require "tempfile"

f = Tempfile.create("benchmark_ips.env")
1000.times.map { |i| f.puts "VAR_#{i}=#{i}" }
f.close

Benchmark.ips do |x|
x.report("parse, overwrite:false") { Dotenv.parse(f.path, overwrite: false) }
x.report("parse, overwrite:true") { Dotenv.parse(f.path, overwrite: true) }
end

File.unlink(f.path)
23 changes: 23 additions & 0 deletions benchmark/parse_profile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require "bundler/setup"
require "dotenv"
require "stackprof"
require "benchmark/ips"
require "tempfile"

f = Tempfile.create("benchmark_ips.env")
1000.times.map { |i| f.puts "VAR_#{i}=#{i}" }
f.close

profile = StackProf.run(mode: :wall, interval: 1_000) do
10_000.times do
Dotenv.parse(f.path, overwrite: false)
end
end

result = StackProf::Report.new(profile)
puts
result.print_text
puts "\n\n\n"
result.print_method(/Dotenv.parse/)

File.unlink(f.path)
6 changes: 4 additions & 2 deletions lib/dotenv/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ def call(...)
def initialize(string, overwrite: false)
@string = string
@hash = {}
@overwrite = overwrite
@variables_to_ignore = overwrite ? nil : ENV.except("DOTENV_LINEBREAK_MODE")
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)

@hash[key] = parse_value(value || "")
end
# Process non-matches
Expand Down Expand Up @@ -104,7 +106,7 @@ def unescape_value(value, maybe_quote)
def perform_substitutions(value, maybe_quote)
if maybe_quote != "'"
self.class.substitutions.each do |proc|
value = proc.call(value, @hash, overwrite: @overwrite)
value = proc.call(value, @hash)
end
end
value
Expand Down
2 changes: 1 addition & 1 deletion lib/dotenv/substitutions/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class << self
)
/x

def call(value, _env, overwrite: false)
def call(value, _env)
# Process interpolated shell commands
value.gsub(INTERPOLATED_SHELL_COMMAND) do |*|
# Eliminate opening and closing parentheses
Expand Down
7 changes: 3 additions & 4 deletions lib/dotenv/substitutions/variable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ class << self
\}? # closing brace
/xi

def call(value, env, overwrite: false)
combined_env = overwrite ? ENV.to_h.merge(env) : env.merge(ENV)
def call(value, env)
value.gsub(VARIABLE) do |variable|
match = $LAST_MATCH_INFO
substitute(match, variable, combined_env)
substitute(match, variable, env)
end
end

Expand All @@ -32,7 +31,7 @@ def substitute(match, variable, env)
if match[1] == "\\"
variable[1..]
elsif match[3]
env.fetch(match[3], "")
env[match[3]] || ENV[match[3]] || ""
else
variable
end
Expand Down

0 comments on commit b396779

Please sign in to comment.