You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/vendor/bundle/ruby/4.0.0/gems/dotenv-3.2.0/lib/dotenv.rb

152 lines
4.7 KiB

require "dotenv/version"
require "dotenv/parser"
require "dotenv/environment"
require "dotenv/missing_keys"
require "dotenv/diff"
# Shim to load environment variables from `.env files into `ENV`.
module Dotenv
extend self
# An internal monitor to synchronize access to ENV in multi-threaded environments.
SEMAPHORE = Monitor.new
private_constant :SEMAPHORE
attr_accessor :instrumenter
# Loads environment variables from one or more `.env` files. See `#parse` for more details.
def load(*filenames, overwrite: false, ignore: true)
parse(*filenames, overwrite: overwrite, ignore: ignore) do |env|
instrument(:load, env: env) do |payload|
update(env, overwrite: overwrite)
end
end
end
# Same as `#load`, but raises Errno::ENOENT if any files don't exist
def load!(*filenames)
load(*filenames, ignore: false)
end
# same as `#load`, but will overwrite existing values in `ENV`
def overwrite(*filenames)
load(*filenames, overwrite: true)
end
alias_method :overload, :overwrite
# same as `#overwrite`, but raises Errno::ENOENT if any files don't exist
def overwrite!(*filenames)
load(*filenames, overwrite: true, ignore: false)
end
alias_method :overload!, :overwrite!
# Parses the given files, yielding for each file if a block is given.
#
# @param filenames [String, Array<String>] Files to parse
# @param overwrite [Boolean] Overwrite existing `ENV` values
# @param ignore [Boolean] Ignore non-existent files
# @param block [Proc] Block to yield for each parsed `Dotenv::Environment`
# @return [Hash] parsed key/value pairs
def parse(*filenames, overwrite: false, ignore: true, &block)
filenames << ".env" if filenames.empty?
filenames = filenames.reverse if overwrite
filenames.reduce({}) do |hash, filename|
begin
env = Environment.new(File.expand_path(filename), overwrite: overwrite)
env = block.call(env) if block
rescue Errno::ENOENT, Errno::EISDIR
raise unless ignore
end
hash.merge! env || {}
end
end
# Save the current `ENV` to be restored later
def save
instrument(:save) do |payload|
@diff = payload[:diff] = Dotenv::Diff.new
end
end
# Restore `ENV` to a given state
#
# @param env [Hash] Hash of keys and values to restore, defaults to the last saved state
# @param safe [Boolean] Is it safe to modify `ENV`? Defaults to `true` in the main thread, otherwise raises an error.
def restore(env = @diff&.a, safe: Thread.current == Thread.main)
# No previously saved or provided state to restore
return unless env
diff = Dotenv::Diff.new(b: env)
return unless diff.any?
unless safe
raise ThreadError, <<~EOE.tr("\n", " ")
Dotenv.restore is not thread safe. Use `Dotenv.modify { }` to update ENV for the duration
of the block in a thread safe manner, or call `Dotenv.restore(safe: true)` to ignore
this error.
EOE
end
instrument(:restore, diff: diff) { ENV.replace(env) }
end
# Update `ENV` with the given hash of keys and values
#
# @param env [Hash] Hash of keys and values to set in `ENV`
# @param overwrite [Boolean|:warn] Overwrite existing `ENV` values
def update(env = {}, overwrite: false)
instrument(:update) do |payload|
diff = payload[:diff] = Dotenv::Diff.new do
ENV.update(env.transform_keys(&:to_s)) do |key, old_value, new_value|
# This block is called when a key exists. Return the new value if overwrite is true.
case overwrite
when :warn
# not printing the value since that could be a secret
warn "Warning: dotenv not overwriting ENV[#{key.inspect}]"
old_value
when true then new_value
when false then old_value
else raise ArgumentError, "Invalid value for overwrite: #{overwrite.inspect}"
end
end
end
diff.env
end
end
# Modify `ENV` for the block and restore it to its previous state afterwards.
#
# Note that the block is synchronized to prevent concurrent modifications to `ENV`,
# so multiple threads will be executed serially.
#
# @param env [Hash] Hash of keys and values to set in `ENV`
def modify(env = {}, &block)
SEMAPHORE.synchronize do
diff = Dotenv::Diff.new
update(env, overwrite: true)
block.call
ensure
restore(diff.a, safe: true)
end
end
def require_keys(*keys)
missing_keys = keys.flatten - ::ENV.keys
return if missing_keys.empty?
raise MissingKeys, missing_keys
end
private
def instrument(name, payload = {}, &block)
if instrumenter
instrumenter.instrument("#{name}.dotenv", payload, &block)
else
block&.call payload
end
end
end
require "dotenv/rails" if defined?(Rails::Railtie)