dotenv 2.7.6 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/dotenv.rb CHANGED
@@ -1,75 +1,133 @@
1
+ require "dotenv/version"
1
2
  require "dotenv/parser"
2
3
  require "dotenv/environment"
3
4
  require "dotenv/missing_keys"
5
+ require "dotenv/diff"
4
6
 
5
- # The top level Dotenv module. The entrypoint for the application logic.
7
+ # Shim to load environment variables from `.env files into `ENV`.
6
8
  module Dotenv
7
- class << self
8
- attr_accessor :instrumenter
9
- end
9
+ extend self
10
+
11
+ # An internal monitor to synchronize access to ENV in multi-threaded environments.
12
+ SEMAPHORE = Monitor.new
13
+ private_constant :SEMAPHORE
10
14
 
11
- module_function
15
+ attr_accessor :instrumenter
12
16
 
13
- def load(*filenames)
14
- with(*filenames) do |f|
15
- ignoring_nonexistent_files do
16
- env = Environment.new(f, true)
17
- instrument("dotenv.load", env: env) { env.apply }
17
+ # Loads environment variables from one or more `.env` files. See `#parse` for more details.
18
+ def load(*filenames, overwrite: false, ignore: true)
19
+ parse(*filenames, overwrite: overwrite, ignore: ignore) do |env|
20
+ instrument(:load, env: env) do |payload|
21
+ update(env, overwrite: overwrite)
18
22
  end
19
23
  end
20
24
  end
21
25
 
22
- # same as `load`, but raises Errno::ENOENT if any files don't exist
26
+ # Same as `#load`, but raises Errno::ENOENT if any files don't exist
23
27
  def load!(*filenames)
24
- with(*filenames) do |f|
25
- env = Environment.new(f, true)
26
- instrument("dotenv.load", env: env) { env.apply }
27
- end
28
+ load(*filenames, ignore: false)
29
+ end
30
+
31
+ # same as `#load`, but will overwrite existing values in `ENV`
32
+ def overwrite(*filenames)
33
+ load(*filenames, overwrite: true)
34
+ end
35
+ alias_method :overload, :overwrite
36
+
37
+ # same as `#overwrite`, but raises Errno::ENOENT if any files don't exist
38
+ def overwrite!(*filenames)
39
+ load(*filenames, overwrite: true, ignore: false)
28
40
  end
41
+ alias_method :overload!, :overwrite!
42
+
43
+ # Parses the given files, yielding for each file if a block is given.
44
+ #
45
+ # @param filenames [String, Array<String>] Files to parse
46
+ # @param overwrite [Boolean] Overwrite existing `ENV` values
47
+ # @param ignore [Boolean] Ignore non-existent files
48
+ # @param block [Proc] Block to yield for each parsed `Dotenv::Environment`
49
+ # @return [Hash] parsed key/value pairs
50
+ def parse(*filenames, overwrite: false, ignore: true, &block)
51
+ filenames << ".env" if filenames.empty?
52
+ filenames = filenames.reverse if overwrite
29
53
 
30
- # same as `load`, but will override existing values in `ENV`
31
- def overload(*filenames)
32
- with(*filenames) do |f|
33
- ignoring_nonexistent_files do
34
- env = Environment.new(f, false)
35
- instrument("dotenv.overload", env: env) { env.apply! }
54
+ filenames.reduce({}) do |hash, filename|
55
+ begin
56
+ env = Environment.new(File.expand_path(filename), overwrite: overwrite)
57
+ env = block.call(env) if block
58
+ rescue Errno::ENOENT, Errno::EISDIR
59
+ raise unless ignore
36
60
  end
61
+
62
+ hash.merge! env || {}
37
63
  end
38
64
  end
39
65
 
40
- # same as `overload`, but raises Errno::ENOENT if any files don't exist
41
- def overload!(*filenames)
42
- with(*filenames) do |f|
43
- env = Environment.new(f, false)
44
- instrument("dotenv.overload", env: env) { env.apply! }
66
+ # Save the current `ENV` to be restored later
67
+ def save
68
+ instrument(:save) do |payload|
69
+ @diff = payload[:diff] = Dotenv::Diff.new
45
70
  end
46
71
  end
47
72
 
48
- # returns a hash of parsed key/value pairs but does not modify ENV
49
- def parse(*filenames)
50
- with(*filenames) do |f|
51
- ignoring_nonexistent_files do
52
- Environment.new(f, false)
53
- end
73
+ # Restore `ENV` to a given state
74
+ #
75
+ # @param env [Hash] Hash of keys and values to restore, defaults to the last saved state
76
+ # @param safe [Boolean] Is it safe to modify `ENV`? Defaults to `true` in the main thread, otherwise raises an error.
77
+ def restore(env = @diff&.a, safe: Thread.current == Thread.main)
78
+ # No previously saved or provided state to restore
79
+ return unless env
80
+
81
+ diff = Dotenv::Diff.new(b: env)
82
+ return unless diff.any?
83
+
84
+ unless safe
85
+ raise ThreadError, <<~EOE.tr("\n", " ")
86
+ Dotenv.restore is not thread safe. Use `Dotenv.modify { }` to update ENV for the duration
87
+ of the block in a thread safe manner, or call `Dotenv.restore(safe: true)` to ignore
88
+ this error.
89
+ EOE
54
90
  end
91
+ instrument(:restore, diff: diff) { ENV.replace(env) }
55
92
  end
56
93
 
57
- # Internal: Helper to expand list of filenames.
94
+ # Update `ENV` with the given hash of keys and values
58
95
  #
59
- # Returns a hash of all the loaded environment variables.
60
- def with(*filenames)
61
- filenames << ".env" if filenames.empty?
62
-
63
- filenames.reduce({}) do |hash, filename|
64
- hash.merge!(yield(File.expand_path(filename)) || {})
96
+ # @param env [Hash] Hash of keys and values to set in `ENV`
97
+ # @param overwrite [Boolean|:warn] Overwrite existing `ENV` values
98
+ def update(env = {}, overwrite: false)
99
+ instrument(:update) do |payload|
100
+ diff = payload[:diff] = Dotenv::Diff.new do
101
+ ENV.update(env.transform_keys(&:to_s)) do |key, old_value, new_value|
102
+ # This block is called when a key exists. Return the new value if overwrite is true.
103
+ case overwrite
104
+ when :warn
105
+ # not printing the value since that could be a secret
106
+ warn "Warning: dotenv not overwriting ENV[#{key.inspect}]"
107
+ old_value
108
+ when true then new_value
109
+ when false then old_value
110
+ else raise ArgumentError, "Invalid value for overwrite: #{overwrite.inspect}"
111
+ end
112
+ end
113
+ end
114
+ diff.env
65
115
  end
66
116
  end
67
117
 
68
- def instrument(name, payload = {}, &block)
69
- if instrumenter
70
- instrumenter.instrument(name, payload, &block)
71
- else
72
- yield
118
+ # Modify `ENV` for the block and restore it to its previous state afterwards.
119
+ #
120
+ # Note that the block is synchronized to prevent concurrent modifications to `ENV`,
121
+ # so multiple threads will be executed serially.
122
+ #
123
+ # @param env [Hash] Hash of keys and values to set in `ENV`
124
+ def modify(env = {}, &block)
125
+ SEMAPHORE.synchronize do
126
+ diff = Dotenv::Diff.new
127
+ update(env, overwrite: true)
128
+ block.call
129
+ ensure
130
+ restore(diff.a, safe: true)
73
131
  end
74
132
  end
75
133
 
@@ -79,8 +137,15 @@ module Dotenv
79
137
  raise MissingKeys, missing_keys
80
138
  end
81
139
 
82
- def ignoring_nonexistent_files
83
- yield
84
- rescue Errno::ENOENT
140
+ private
141
+
142
+ def instrument(name, payload = {}, &block)
143
+ if instrumenter
144
+ instrumenter.instrument("#{name}.dotenv", payload, &block)
145
+ else
146
+ block&.call payload
147
+ end
85
148
  end
86
149
  end
150
+
151
+ require "dotenv/rails" if defined?(Rails::Railtie)
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dotenv
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.6
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Keepers
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2020-07-11 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake
@@ -39,19 +38,19 @@ dependencies:
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
40
  - !ruby/object:Gem::Dependency
42
- name: rubocop
41
+ name: standard
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
- - - "~>"
44
+ - - ">="
46
45
  - !ruby/object:Gem::Version
47
- version: 0.40.0
46
+ version: '0'
48
47
  type: :development
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
- - - "~>"
51
+ - - ">="
53
52
  - !ruby/object:Gem::Version
54
- version: 0.40.0
53
+ version: '0'
55
54
  description: Loads environment variables from `.env`.
56
55
  email:
57
56
  - brandon@opensoul.org
@@ -64,11 +63,17 @@ files:
64
63
  - README.md
65
64
  - bin/dotenv
66
65
  - lib/dotenv.rb
66
+ - lib/dotenv/autorestore.rb
67
67
  - lib/dotenv/cli.rb
68
+ - lib/dotenv/diff.rb
68
69
  - lib/dotenv/environment.rb
69
70
  - lib/dotenv/load.rb
71
+ - lib/dotenv/log_subscriber.rb
70
72
  - lib/dotenv/missing_keys.rb
71
73
  - lib/dotenv/parser.rb
74
+ - lib/dotenv/rails-now.rb
75
+ - lib/dotenv/rails.rb
76
+ - lib/dotenv/replay_logger.rb
72
77
  - lib/dotenv/substitutions/command.rb
73
78
  - lib/dotenv/substitutions/variable.rb
74
79
  - lib/dotenv/tasks.rb
@@ -77,8 +82,9 @@ files:
77
82
  homepage: https://github.com/bkeepers/dotenv
78
83
  licenses:
79
84
  - MIT
80
- metadata: {}
81
- post_install_message:
85
+ metadata:
86
+ changelog_uri: https://github.com/bkeepers/dotenv/releases
87
+ funding_uri: https://github.com/sponsors/bkeepers
82
88
  rdoc_options: []
83
89
  require_paths:
84
90
  - lib
@@ -86,15 +92,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
92
  requirements:
87
93
  - - ">="
88
94
  - !ruby/object:Gem::Version
89
- version: '0'
95
+ version: '3.0'
90
96
  required_rubygems_version: !ruby/object:Gem::Requirement
91
97
  requirements:
92
98
  - - ">="
93
99
  - !ruby/object:Gem::Version
94
100
  version: '0'
95
101
  requirements: []
96
- rubygems_version: 3.0.3
97
- signing_key:
102
+ rubygems_version: 3.6.9
98
103
  specification_version: 4
99
104
  summary: Loads environment variables from `.env`.
100
105
  test_files: []