climate_control 1.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aac4a1d4fe272e5369855d9d80c8bfd764b752f27d1bdfad2b19a607d5f0a44e
4
- data.tar.gz: 8945986d5956f681810ef8a5f164323a3cd2a48e82368618c292f3c6fc7a6caa
3
+ metadata.gz: '00584062039c6b9161ff0e4a1b61ca51e4d8742225fc611180c4effcc43959c5'
4
+ data.tar.gz: 99135f45c57f58d87e17480896045c00eecebd632ed64eaf18bd1964ef7d454d
5
5
  SHA512:
6
- metadata.gz: c36a6b4ead29b23f93c95457e56f6bc3fd6c5cd596f66fe8243e736edcc5a11cfe9fd5fe62b1cf5fc8cd0c7f4fe72c051c0560338a6496e30c56b3cf095c92ec
7
- data.tar.gz: cbdf93ff0a149ef67129db0ac0f25838f02c7d2faf6dea87f8ce0acaf54848fb129f4d1b8cbf0ad40c50358cd2186e5a726b2fed5d82fbc9de2c765f4a9bf94d
6
+ metadata.gz: 01ba4ad3241cea796d408f0adb02f8c23b85771d1ce8338eedf973b95228a7cac9978218f08921915918c90a0f8f292dbb38f9401ca29de8d7a6ef3dc76d99b5
7
+ data.tar.gz: 9b20ce4affbcf22b6204016d31b7b54465ad5ba2a143fd2e6578d3dc5d4c80ac167c02c9ccc47d91c2a62441c5ce3037ebb13c445407876c163edaba73eb484d
@@ -6,7 +6,7 @@ jobs:
6
6
  runs-on: ubuntu-latest
7
7
  strategy:
8
8
  matrix:
9
- ruby-version: ['3.0', 2.7, 2.6, 2.5]
9
+ ruby-version: [3.1, '3.0', 2.7, 2.6, 2.5]
10
10
  steps:
11
11
  - uses: actions/checkout@v2
12
12
  - name: Set up Ruby ${{ matrix.ruby-version }}
data/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## 1.2.0 / 2022-07-15
9
+
10
+ - Added: `ClimateControl.unsafe_modify` for a thread-unsafe version of
11
+ `ClimateControl.modify` (useful for minitest-around for instance)
12
+ - Deprecates `ClimateControl.env`, `ENV` should be used instead
13
+
14
+ ## 1.1.1 / 2022-05-28
15
+
16
+ - Fixed: ENV was not restored if an error was thrown when assigning ENV
17
+
18
+ ## 1.1.0 / 2022-05-26
19
+
20
+ - Refactor to use `Monitor` instead of `Mutex`
21
+ - Add documentation about thread-safety
22
+ - Allow ClimateControl.modify to be called without environment variables
23
+ - Add test for concurrent access needed to be inside block
24
+ - Relax development dependencies
25
+
26
+ ## 1.0.1 / 2021-05-26
27
+
28
+ - Require minimum Ruby version of 2.5.0
29
+
30
+ # 1.0.0 / 2021-03-06
31
+
32
+ - Commit to supporting latest patch versions of Ruby 2.5+
33
+ - Improve documentation
34
+ - Format code with StandardRB
35
+ - Bump gem dependencies
36
+
37
+ # 0.2.0 / 2017-05-12
38
+
39
+ - Allow nested environment changes in the same thread
40
+
41
+ # 0.1.0 / 2017-01-07
42
+
43
+ - Remove ActiveSupport dependency
44
+
45
+ # 0.0.4 / 2017-01-06
46
+
47
+ - Improved thread safety
48
+ - Handle TypeErrors during assignment
49
+ - Improve documentation
50
+
51
+ # 0.0.1 / 2012-11-28
52
+
53
+ - Initial release
data/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @dorianmariefr
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Climate Control
2
2
 
3
+ ![GitHub Actions CI](https://github.com/thoughtbot/climate_control/actions/workflows/ci.yml/badge.svg)
4
+
3
5
  Easily manage your environment.
4
6
 
5
7
  ## Installation
@@ -48,7 +50,7 @@ end
48
50
  To use with RSpec, you could define this in your spec:
49
51
 
50
52
  ```ruby
51
- def with_modified_env(options, &block)
53
+ def with_modified_env(options = {}, &block)
52
54
  ClimateControl.modify(options, &block)
53
55
  end
54
56
  ```
@@ -105,6 +107,39 @@ manner becomes more difficult:
105
107
  Climate Control modifies environment variables only within the context of the
106
108
  block, ensuring values are managed properly and consistently.
107
109
 
110
+ ## Thread-safety
111
+
112
+ When using threads, for instance when running tests concurrently in the same
113
+ process, you may need to wrap your code inside `ClimateControl.modify` blocks,
114
+ e.g.:
115
+
116
+ ```ruby
117
+ first_thread = Thread.new do
118
+ ClimateControl.modify(SECRET: "1") do
119
+ p ENV["SECRET"] # => "1"
120
+ sleep 2
121
+ p ENV["SECRET"] # => "1"
122
+ end
123
+ end
124
+
125
+ second_thread = Thread.new do
126
+ ClimateControl.modify({}) do
127
+ sleep 1
128
+ p ENV["SECRET"] # => nil
129
+ sleep 1
130
+ p ENV["SECRET"] # => nil
131
+ end
132
+ end
133
+
134
+ first_thread.join
135
+ second_thread.join
136
+ ```
137
+
138
+ > The modification wraps ENV in a mutex. If there's contention (the env being used - including potentially mutating values), it blocks until the value is freed (we shift out of the Ruby block).
139
+ >
140
+ > <cite><a href="https://github.com/thoughtbot/climate_control/issues/32#issuecomment-800713686">Josh Clayton</a></cite>
141
+
142
+
108
143
  ## Contributing
109
144
 
110
145
  1. Fork it
@@ -118,3 +153,18 @@ This project uses [StandardRB](https://github.com/testdouble/standard) to ensure
118
153
  ## License
119
154
 
120
155
  climate_control is copyright 2012-2021 Joshua Clayton and thoughtbot, inc. It is free software and may be redistributed under the terms specified in the [LICENSE](https://github.com/thoughtbot/climate_control/blob/main/LICENSE) file.
156
+
157
+ About thoughtbot
158
+ ----------------
159
+
160
+ ![thoughtbot](https://thoughtbot.com/brand_assets/93:44.svg)
161
+
162
+ climate_control is maintained and funded by thoughtbot, inc.
163
+ The names and logos for thoughtbot are trademarks of thoughtbot, inc.
164
+
165
+ We love open source software!
166
+ See [our other projects][community] or
167
+ [hire us][hire] to design, develop, and grow your product.
168
+
169
+ [community]: https://thoughtbot.com/community?utm_source=github
170
+ [hire]: https://thoughtbot.com/hire-us?utm_source=github
@@ -5,8 +5,8 @@ require "climate_control/version"
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = "climate_control"
7
7
  gem.version = ClimateControl::VERSION
8
- gem.authors = ["Joshua Clayton"]
9
- gem.email = ["joshua.clayton@gmail.com"]
8
+ gem.authors = ["Joshua Clayton", "Dorian Marié"]
9
+ gem.email = ["joshua.clayton@gmail.com", "dorian@dorianmarie.fr"]
10
10
  gem.description = "Modify your ENV"
11
11
  gem.summary = "Modify your ENV easily with ClimateControl"
12
12
  gem.homepage = "https://github.com/thoughtbot/climate_control"
@@ -18,8 +18,8 @@ Gem::Specification.new do |gem|
18
18
 
19
19
  gem.required_ruby_version = ">= 2.5.0"
20
20
 
21
- gem.add_development_dependency "rspec", "~> 3.10.0"
22
- gem.add_development_dependency "rake", "~> 12.3.3"
23
- gem.add_development_dependency "simplecov", "~> 0.9.1"
24
- gem.add_development_dependency "standard", "~> 1.0.0"
21
+ gem.add_development_dependency "rspec"
22
+ gem.add_development_dependency "rake"
23
+ gem.add_development_dependency "simplecov"
24
+ gem.add_development_dependency "standard"
25
25
  end
@@ -1,3 +1,3 @@
1
1
  module ClimateControl
2
- VERSION = "1.0.1".freeze
2
+ VERSION = "1.2.0".freeze
3
3
  end
@@ -1,16 +1,72 @@
1
- require "climate_control/environment"
2
1
  require "climate_control/errors"
3
- require "climate_control/modifier"
4
2
  require "climate_control/version"
3
+ require "monitor"
5
4
 
6
5
  module ClimateControl
7
- @@env = ClimateControl::Environment.new
6
+ extend self
7
+ extend Gem::Deprecate
8
8
 
9
- def self.modify(environment_overrides, &block)
10
- Modifier.new(env, environment_overrides, &block).process
9
+ SEMAPHORE = Monitor.new
10
+ private_constant :SEMAPHORE
11
+
12
+ def modify(environment_overrides = {}, &block)
13
+ environment_overrides = environment_overrides.transform_keys(&:to_s)
14
+
15
+ SEMAPHORE.synchronize do
16
+ previous = ENV.to_hash
17
+
18
+ begin
19
+ copy environment_overrides
20
+ ensure
21
+ middle = ENV.to_hash
22
+ end
23
+
24
+ block.call
25
+ ensure
26
+ after = ENV
27
+ (previous.keys | middle.keys | after.keys).each do |key|
28
+ if previous[key] != after[key] && middle[key] == after[key]
29
+ ENV[key] = previous[key]
30
+ end
31
+ end
32
+ end
11
33
  end
12
34
 
13
- def self.env
14
- @@env
35
+ def unsafe_modify(environment_overrides = {}, &block)
36
+ environment_overrides = environment_overrides.transform_keys(&:to_s)
37
+
38
+ previous = ENV.to_hash
39
+
40
+ begin
41
+ copy environment_overrides
42
+ ensure
43
+ middle = ENV.to_hash
44
+ end
45
+
46
+ block.call
47
+ ensure
48
+ after = ENV
49
+ (previous.keys | middle.keys | after.keys).each do |key|
50
+ if previous[key] != after[key] && middle[key] == after[key]
51
+ ENV[key] = previous[key]
52
+ end
53
+ end
54
+ end
55
+
56
+ def env
57
+ ENV
58
+ end
59
+
60
+ deprecate :env, "ENV", 2022, 10
61
+
62
+ private
63
+
64
+ def copy(overrides)
65
+ overrides.each do |key, value|
66
+ ENV[key] = value
67
+ rescue TypeError => e
68
+ raise UnassignableValueError,
69
+ "attempted to assign #{value} to #{key} but failed (#{e.message})"
70
+ end
15
71
  end
16
72
  end
@@ -5,7 +5,7 @@ Thing = Class.new
5
5
  describe "Climate control" do
6
6
  it "allows modification of the environment" do
7
7
  block_run = false
8
- ClimateControl.modify FOO: "bar" do
8
+ with_modified_env FOO: "bar" do
9
9
  expect(ENV["FOO"]).to eq "bar"
10
10
  block_run = true
11
11
  end
@@ -113,6 +113,39 @@ describe "Climate control" do
113
113
  expect(ENV["BAZ"]).to be_nil
114
114
  end
115
115
 
116
+ it "handles threads accessing the same key wrapped in a block" do
117
+ first_thread = Thread.new {
118
+ with_modified_env do
119
+ with_modified_env CONFLICTING_KEY: "1" do
120
+ sleep 0.5
121
+ expect(ENV["CONFLICTING_KEY"]).to eq("1")
122
+ end
123
+
124
+ expect(ENV["CONFLICTING_KEY"]).to be_nil
125
+ end
126
+ }
127
+
128
+ second_thread = Thread.new {
129
+ with_modified_env do
130
+ sleep 0.25
131
+ expect(ENV["CONFLICTING_KEY"]).to be_nil
132
+
133
+ with_modified_env CONFLICTING_KEY: "2" do
134
+ expect(ENV["CONFLICTING_KEY"]).to eq("2")
135
+ sleep 0.5
136
+ expect(ENV["CONFLICTING_KEY"]).to eq("2")
137
+ end
138
+
139
+ expect(ENV["CONFLICTING_KEY"]).to be_nil
140
+ end
141
+ }
142
+
143
+ first_thread.join
144
+ second_thread.join
145
+
146
+ expect(ENV["CONFLICTING_KEY"]).to be_nil
147
+ end
148
+
116
149
  it "is re-entrant" do
117
150
  ret = with_modified_env(FOO: "foo") {
118
151
  with_modified_env(BAR: "bar") do
@@ -134,7 +167,32 @@ describe "Climate control" do
134
167
  }.to raise_error ClimateControl::UnassignableValueError, /attempted to assign .*Thing.* to FOO but failed \(#{message}\)$/
135
168
  end
136
169
 
137
- def with_modified_env(options, &block)
170
+ it "restores the ENV even when an error was raised when assigning values" do
171
+ ENV["KEY_TO_OVERRIDE"] = "initial_value_1"
172
+ ENV["KEY_THAT_WILL_ERROR_OUT"] = "initial_value_2"
173
+
174
+ expect {
175
+ with_modified_env(
176
+ KEY_TO_OVERRIDE: "overwriten_value_1",
177
+ KEY_THAT_WILL_ERROR_OUT: :value_that_will_error_out
178
+ ) {}
179
+ }.to raise_error ClimateControl::UnassignableValueError
180
+
181
+ expect(ENV["KEY_TO_OVERRIDE"]).to eq("initial_value_1")
182
+ expect(ENV["KEY_THAT_WILL_ERROR_OUT"]).to eq("initial_value_2")
183
+ end
184
+
185
+ it "doesn't block on nested modify calls" do
186
+ with_modified_env(SMS_DEFAULT_COUNTRY_CODE: nil) do
187
+ with_modified_env(SMS_DEFAULT_COUNTRY_CODE: "++56") do
188
+ expect(ENV.fetch("SMS_DEFAULT_COUNTRY_CODE", "++41")).to eq("++56")
189
+ end
190
+
191
+ expect(ENV.fetch("SMS_DEFAULT_COUNTRY_CODE", "++41")).to eq("++41")
192
+ end
193
+ end
194
+
195
+ def with_modified_env(options = {}, &block)
138
196
  ClimateControl.modify(options, &block)
139
197
  end
140
198
 
@@ -0,0 +1,186 @@
1
+ require "spec_helper"
2
+
3
+ describe "ClimateControl#unsafe_modify" do
4
+ it "allows modification of the environment" do
5
+ block_run = false
6
+ with_modified_env FOO: "bar" do
7
+ expect(ENV["FOO"]).to eq "bar"
8
+ block_run = true
9
+ end
10
+
11
+ expect(ENV["FOO"]).to be_nil
12
+ expect(block_run).to be true
13
+ end
14
+
15
+ it "modifies the environment" do
16
+ with_modified_env VARIABLE_1: "bar", VARIABLE_2: "qux" do
17
+ expect(ENV["VARIABLE_1"]).to eq "bar"
18
+ expect(ENV["VARIABLE_2"]).to eq "qux"
19
+ end
20
+
21
+ expect(ENV["VARIABLE_1"]).to be_nil
22
+ expect(ENV["VARIABLE_2"]).to be_nil
23
+ end
24
+
25
+ it "allows for environment variables to be assigned within the block" do
26
+ with_modified_env VARIABLE_1: "modified" do
27
+ ENV["ASSIGNED_IN_BLOCK"] = "assigned"
28
+ end
29
+
30
+ expect(ENV["ASSIGNED_IN_BLOCK"]).to eq "assigned"
31
+ end
32
+
33
+ it "reassigns previously set environment variables" do
34
+ ENV["VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV"] = "original"
35
+ expect(ENV["VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV"]).to eq "original"
36
+
37
+ with_modified_env VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV: "overridden" do
38
+ expect(ENV["VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV"]).to eq "overridden"
39
+ end
40
+
41
+ expect(ENV["VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV"]).to eq "original"
42
+ end
43
+
44
+ it "persists the change when overriding the variable in the block" do
45
+ with_modified_env VARIABLE_MODIFIED_AND_THEN_ASSIGNED: "modified" do
46
+ ENV["VARIABLE_MODIFIED_AND_THEN_ASSIGNED"] = "assigned value"
47
+ end
48
+
49
+ expect(ENV["VARIABLE_MODIFIED_AND_THEN_ASSIGNED"]).to eq "assigned value"
50
+ end
51
+
52
+ it "resets environment variables even if the block raises" do
53
+ expect {
54
+ with_modified_env FOO: "bar" do
55
+ raise "broken"
56
+ end
57
+ }.to raise_error("broken")
58
+
59
+ expect(ENV["FOO"]).to be_nil
60
+ end
61
+
62
+ it "preserves environment variables set within the block" do
63
+ ENV["CHANGED"] = "old value"
64
+
65
+ with_modified_env IRRELEVANT: "ignored value" do
66
+ ENV["CHANGED"] = "new value"
67
+ end
68
+
69
+ expect(ENV["CHANGED"]).to eq "new value"
70
+ end
71
+
72
+ it "returns the value of the block" do
73
+ value = with_modified_env VARIABLE_1: "bar" do
74
+ "value inside block"
75
+ end
76
+
77
+ expect(value).to eq "value inside block"
78
+ end
79
+
80
+ it "handles threads correctly" do
81
+ # failure path without mutex
82
+ # [thread_removing_env] BAZ is assigned
83
+ # 0.25s passes
84
+ # [other_thread] FOO is assigned and ENV is copied (which includes BAZ)
85
+ # 0.25s passes
86
+ # [thread_removing_env] thread resolves and BAZ is removed from env; other_thread still retains knowledge of BAZ
87
+ # 0.25s passes
88
+ # [other_thread] thread resolves, FOO is removed, BAZ is copied back to ENV
89
+
90
+ thread_removing_env = Thread.new {
91
+ with_modified_env BAZ: "buzz" do
92
+ sleep 0.5
93
+ end
94
+
95
+ expect(ENV["BAZ"]).to be_nil
96
+ }
97
+
98
+ other_thread = Thread.new {
99
+ sleep 0.25
100
+ with_modified_env FOO: "bar" do
101
+ sleep 0.5
102
+ end
103
+
104
+ expect(ENV["FOO"]).to be_nil
105
+ }
106
+
107
+ thread_removing_env.join
108
+ other_thread.join
109
+
110
+ expect(ENV["FOO"]).to be_nil
111
+ expect(ENV["BAZ"]).to be_nil
112
+ end
113
+
114
+ it "is re-entrant" do
115
+ ret = with_modified_env(FOO: "foo") {
116
+ with_modified_env(BAR: "bar") do
117
+ "bar"
118
+ end
119
+ }
120
+
121
+ expect(ret).to eq("bar")
122
+
123
+ expect(ENV["FOO"]).to be_nil
124
+ expect(ENV["BAR"]).to be_nil
125
+ end
126
+
127
+ it "raises when the value cannot be assigned properly" do
128
+ message = generate_type_error_for_object(Thing.new)
129
+
130
+ expect {
131
+ with_modified_env(FOO: Thing.new)
132
+ }.to raise_error ClimateControl::UnassignableValueError, /attempted to assign .*Thing.* to FOO but failed \(#{message}\)$/
133
+ end
134
+
135
+ it "restores the ENV even when an error was raised when assigning values" do
136
+ ENV["KEY_TO_OVERRIDE"] = "initial_value_1"
137
+ ENV["KEY_THAT_WILL_ERROR_OUT"] = "initial_value_2"
138
+
139
+ expect {
140
+ with_modified_env(
141
+ KEY_TO_OVERRIDE: "overwriten_value_1",
142
+ KEY_THAT_WILL_ERROR_OUT: :value_that_will_error_out
143
+ ) {}
144
+ }.to raise_error ClimateControl::UnassignableValueError
145
+
146
+ expect(ENV["KEY_TO_OVERRIDE"]).to eq("initial_value_1")
147
+ expect(ENV["KEY_THAT_WILL_ERROR_OUT"]).to eq("initial_value_2")
148
+ end
149
+
150
+ it "doesn't block on nested modify calls" do
151
+ with_modified_env(SMS_DEFAULT_COUNTRY_CODE: nil) do
152
+ with_modified_env(SMS_DEFAULT_COUNTRY_CODE: "++56") do
153
+ expect(ENV.fetch("SMS_DEFAULT_COUNTRY_CODE", "++41")).to eq("++56")
154
+ end
155
+
156
+ expect(ENV.fetch("SMS_DEFAULT_COUNTRY_CODE", "++41")).to eq("++41")
157
+ end
158
+ end
159
+
160
+ def with_modified_env(options = {}, &block)
161
+ ClimateControl.unsafe_modify(options, &block)
162
+ end
163
+
164
+ def generate_type_error_for_object(object)
165
+ message = nil
166
+
167
+ begin
168
+ "1" + object
169
+ rescue TypeError => e
170
+ message = e.message
171
+ end
172
+
173
+ message
174
+ end
175
+
176
+ around do |example|
177
+ old_env = ENV.to_hash
178
+
179
+ example.run
180
+
181
+ ENV.clear
182
+ old_env.each do |key, value|
183
+ ENV[key] = value
184
+ end
185
+ end
186
+ end
metadata CHANGED
@@ -1,93 +1,95 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: climate_control
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Clayton
8
+ - Dorian Marié
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2021-05-27 00:00:00.000000000 Z
12
+ date: 2022-07-15 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rspec
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - "~>"
18
+ - - ">="
18
19
  - !ruby/object:Gem::Version
19
- version: 3.10.0
20
+ version: '0'
20
21
  type: :development
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - "~>"
25
+ - - ">="
25
26
  - !ruby/object:Gem::Version
26
- version: 3.10.0
27
+ version: '0'
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: rake
29
30
  requirement: !ruby/object:Gem::Requirement
30
31
  requirements:
31
- - - "~>"
32
+ - - ">="
32
33
  - !ruby/object:Gem::Version
33
- version: 12.3.3
34
+ version: '0'
34
35
  type: :development
35
36
  prerelease: false
36
37
  version_requirements: !ruby/object:Gem::Requirement
37
38
  requirements:
38
- - - "~>"
39
+ - - ">="
39
40
  - !ruby/object:Gem::Version
40
- version: 12.3.3
41
+ version: '0'
41
42
  - !ruby/object:Gem::Dependency
42
43
  name: simplecov
43
44
  requirement: !ruby/object:Gem::Requirement
44
45
  requirements:
45
- - - "~>"
46
+ - - ">="
46
47
  - !ruby/object:Gem::Version
47
- version: 0.9.1
48
+ version: '0'
48
49
  type: :development
49
50
  prerelease: false
50
51
  version_requirements: !ruby/object:Gem::Requirement
51
52
  requirements:
52
- - - "~>"
53
+ - - ">="
53
54
  - !ruby/object:Gem::Version
54
- version: 0.9.1
55
+ version: '0'
55
56
  - !ruby/object:Gem::Dependency
56
57
  name: standard
57
58
  requirement: !ruby/object:Gem::Requirement
58
59
  requirements:
59
- - - "~>"
60
+ - - ">="
60
61
  - !ruby/object:Gem::Version
61
- version: 1.0.0
62
+ version: '0'
62
63
  type: :development
63
64
  prerelease: false
64
65
  version_requirements: !ruby/object:Gem::Requirement
65
66
  requirements:
66
- - - "~>"
67
+ - - ">="
67
68
  - !ruby/object:Gem::Version
68
- version: 1.0.0
69
+ version: '0'
69
70
  description: Modify your ENV
70
71
  email:
71
72
  - joshua.clayton@gmail.com
73
+ - dorian@dorianmarie.fr
72
74
  executables: []
73
75
  extensions: []
74
76
  extra_rdoc_files: []
75
77
  files:
76
78
  - ".github/workflows/ci.yml"
77
79
  - ".gitignore"
80
+ - CHANGELOG.md
81
+ - CODEOWNERS
78
82
  - CODE_OF_CONDUCT.md
79
83
  - Gemfile
80
84
  - LICENSE
81
- - NEWS
82
85
  - README.md
83
86
  - Rakefile
84
87
  - climate_control.gemspec
85
88
  - lib/climate_control.rb
86
- - lib/climate_control/environment.rb
87
89
  - lib/climate_control/errors.rb
88
- - lib/climate_control/modifier.rb
89
90
  - lib/climate_control/version.rb
90
91
  - spec/acceptance/climate_control_spec.rb
92
+ - spec/acceptance/unsafe_modify_spec.rb
91
93
  - spec/spec_helper.rb
92
94
  homepage: https://github.com/thoughtbot/climate_control
93
95
  licenses:
@@ -108,10 +110,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
110
  - !ruby/object:Gem::Version
109
111
  version: '0'
110
112
  requirements: []
111
- rubygems_version: 3.2.3
113
+ rubygems_version: 3.3.13
112
114
  signing_key:
113
115
  specification_version: 4
114
116
  summary: Modify your ENV easily with ClimateControl
115
117
  test_files:
116
118
  - spec/acceptance/climate_control_spec.rb
119
+ - spec/acceptance/unsafe_modify_spec.rb
117
120
  - spec/spec_helper.rb
data/NEWS DELETED
@@ -1,22 +0,0 @@
1
- 1.0.1 (May 26, 2021)
2
- Require minimum Ruby version of 2.5.0
3
-
4
- 1.0.0 (March 6, 2021)
5
- Commit to supporting latest patch versions of Ruby 2.5+
6
- Improve documentation
7
- Format code with StandardRB
8
- Bump gem dependencies
9
-
10
- 0.2.0 (May 12, 2017)
11
- Allow nested environment changes in the same thread
12
-
13
- 0.1.0 (January 7, 2017)
14
- Remove ActiveSupport dependency
15
-
16
- 0.0.4 (January 6, 2017)
17
- Improved thread safety
18
- Handle TypeErrors during assignment
19
- Improve documentation
20
-
21
- 0.0.1 (November 28, 2012)
22
- Initial release
@@ -1,33 +0,0 @@
1
- require "forwardable"
2
-
3
- module ClimateControl
4
- class Environment
5
- extend Forwardable
6
-
7
- def initialize
8
- @semaphore = Mutex.new
9
- @owner = nil
10
- end
11
-
12
- def_delegators :env, :[]=, :to_hash, :[], :delete
13
-
14
- def synchronize
15
- if @owner == Thread.current
16
- return yield if block_given?
17
- end
18
-
19
- @semaphore.synchronize do
20
- @owner = Thread.current
21
- yield if block_given?
22
- ensure
23
- @owner = nil
24
- end
25
- end
26
-
27
- private
28
-
29
- def env
30
- ENV
31
- end
32
- end
33
- end
@@ -1,92 +0,0 @@
1
- module ClimateControl
2
- class Modifier
3
- def initialize(env, environment_overrides = {}, &block)
4
- @environment_overrides = stringify_keys(environment_overrides)
5
- @block = block
6
- @env = env
7
- end
8
-
9
- def process
10
- @env.synchronize do
11
- prepare_environment_for_block
12
- run_block
13
- ensure
14
- cache_environment_after_block
15
- delete_keys_that_do_not_belong
16
- revert_changed_keys
17
- end
18
- end
19
-
20
- private
21
-
22
- def prepare_environment_for_block
23
- @original_env = clone_environment
24
- copy_overrides_to_environment
25
- @env_with_overrides_before_block = clone_environment
26
- end
27
-
28
- def run_block
29
- @block.call
30
- end
31
-
32
- def copy_overrides_to_environment
33
- @environment_overrides.each do |key, value|
34
- @env[key] = value
35
- rescue TypeError => e
36
- raise UnassignableValueError,
37
- "attempted to assign #{value} to #{key} but failed (#{e.message})"
38
- end
39
- end
40
-
41
- def keys_to_remove
42
- @environment_overrides.keys
43
- end
44
-
45
- def keys_changed_by_block
46
- @keys_changed_by_block ||= OverlappingKeysWithChangedValues.new(@env_with_overrides_before_block, @env_after_block).keys
47
- end
48
-
49
- def cache_environment_after_block
50
- @env_after_block = clone_environment
51
- end
52
-
53
- def delete_keys_that_do_not_belong
54
- (keys_to_remove - keys_changed_by_block).each { |key| @env.delete(key) }
55
- end
56
-
57
- def revert_changed_keys
58
- (@original_env.keys - keys_changed_by_block).each do |key|
59
- @env[key] = @original_env[key]
60
- end
61
- end
62
-
63
- def clone_environment
64
- @env.to_hash
65
- end
66
-
67
- def stringify_keys(env)
68
- env.each_with_object({}) do |(key, value), hash|
69
- hash[key.to_s] = value
70
- end
71
- end
72
-
73
- class OverlappingKeysWithChangedValues
74
- def initialize(hash_1, hash_2)
75
- @hash_1 = hash_1 || {}
76
- @hash_2 = hash_2
77
- end
78
-
79
- def keys
80
- overlapping_keys.select do |overlapping_key|
81
- @hash_1[overlapping_key] != @hash_2[overlapping_key]
82
- end
83
- end
84
-
85
- private
86
-
87
- def overlapping_keys
88
- @hash_2.keys & @hash_1.keys
89
- end
90
- end
91
- end
92
- end