climate_control 0.2.0 → 1.1.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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +23 -0
- data/CHANGELOG.md +45 -0
- data/CODEOWNERS +1 -0
- data/CODE_OF_CONDUCT.md +6 -0
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +54 -3
- data/climate_control.gemspec +16 -14
- data/lib/climate_control/version.rb +1 -1
- data/lib/climate_control.rb +35 -7
- data/spec/acceptance/climate_control_spec.rb +44 -10
- data/spec/spec_helper.rb +1 -1
- metadata +40 -25
- data/.travis.yml +0 -11
- data/NEWS +0 -13
- data/lib/climate_control/environment.rb +0 -36
- data/lib/climate_control/modifier.rb +0 -96
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 919bb6d645214941bca1f5de4d30732891348bb76a29725e9db8ba00b6274dbc
|
4
|
+
data.tar.gz: 332e9222519c1c6a91d866783d4c3993c4372ffe29242f60759cf9948636d3ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c5c9c97dcef22aa4923d284f196addeb1ddeb2f87c80785fd5d8a38e3f23ea7e66e11acbe265e225bf3663d36dbe500edc981d5baaf53cc872a19670dfa1878
|
7
|
+
data.tar.gz: c56fb5c40969ed979a485503765b33f7f4adc2d34830309502a2207dad363f4c9a0b04c5dda5cb4c56c001e80c317954a8d19b0b21fce53aa0fd84962d33c091
|
@@ -0,0 +1,23 @@
|
|
1
|
+
name: CI
|
2
|
+
on: [pull_request]
|
3
|
+
jobs:
|
4
|
+
tests:
|
5
|
+
name: Tests
|
6
|
+
runs-on: ubuntu-latest
|
7
|
+
strategy:
|
8
|
+
matrix:
|
9
|
+
ruby-version: [3.1, '3.0', 2.7, 2.6, 2.5]
|
10
|
+
steps:
|
11
|
+
- uses: actions/checkout@v2
|
12
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
13
|
+
uses: ruby/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
ruby-version: ${{ matrix.ruby-version }}
|
16
|
+
- name: Install gems
|
17
|
+
run: |
|
18
|
+
bundle config path vendor/bundle
|
19
|
+
bundle install --jobs 4 --retry 3
|
20
|
+
- name: Run tests
|
21
|
+
run: bundle exec rake
|
22
|
+
- name: Run StandardRB
|
23
|
+
run: bundle exec standardrb
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,45 @@
|
|
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
|
+
## Unreleased
|
9
|
+
|
10
|
+
## 1.1.0 / 2022-05-26
|
11
|
+
|
12
|
+
- Refactor to use `Monitor` instead of `Mutex`
|
13
|
+
- Add documentation about thread-safety
|
14
|
+
- Allow ClimateControl.modify to be called without environment variables
|
15
|
+
- Add test for concurrent access needed to be inside block
|
16
|
+
- Relax development dependencies
|
17
|
+
|
18
|
+
## 1.0.1 / 2021-05-26
|
19
|
+
|
20
|
+
- Require minimum Ruby version of 2.5.0
|
21
|
+
|
22
|
+
# 1.0.0 / 2021-03-06
|
23
|
+
|
24
|
+
- Commit to supporting latest patch versions of Ruby 2.5+
|
25
|
+
- Improve documentation
|
26
|
+
- Format code with StandardRB
|
27
|
+
- Bump gem dependencies
|
28
|
+
|
29
|
+
# 0.2.0 / 2017-05-12
|
30
|
+
|
31
|
+
- Allow nested environment changes in the same thread
|
32
|
+
|
33
|
+
# 0.1.0 / 2017-01-07
|
34
|
+
|
35
|
+
- Remove ActiveSupport dependency
|
36
|
+
|
37
|
+
# 0.0.4 / 2017-01-06
|
38
|
+
|
39
|
+
- Improved thread safety
|
40
|
+
- Handle TypeErrors during assignment
|
41
|
+
- Improve documentation
|
42
|
+
|
43
|
+
# 0.0.1 / 2012-11-28
|
44
|
+
|
45
|
+
- Initial release
|
data/CODEOWNERS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* @dorianmariefr
|
data/CODE_OF_CONDUCT.md
ADDED
data/{LICENSE.txt → LICENSE}
RENAMED
data/README.md
CHANGED
@@ -24,15 +24,31 @@ within a block:
|
|
24
24
|
```ruby
|
25
25
|
ClimateControl.modify CONFIRMATION_INSTRUCTIONS_BCC: 'confirmation_bcc@example.com' do
|
26
26
|
sign_up_as 'john@example.com'
|
27
|
+
|
27
28
|
confirm_account_for_email 'john@example.com'
|
28
|
-
|
29
|
+
|
30
|
+
expect(current_email).to bcc_to('confirmation_bcc@example.com')
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
To modify multiple environment variables:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
ClimateControl.modify CONFIRMATION_INSTRUCTIONS_BCC: 'confirmation_bcc@example.com',
|
38
|
+
MAIL_FROM: 'us@example.com' do
|
39
|
+
sign_up_as 'john@example.com'
|
40
|
+
|
41
|
+
confirm_account_for_email 'john@example.com'
|
42
|
+
|
43
|
+
expect(current_email).to bcc_to('confirmation_bcc@example.com')
|
44
|
+
expect(current_email).to be_from('us@example.com')
|
29
45
|
end
|
30
46
|
```
|
31
47
|
|
32
48
|
To use with RSpec, you could define this in your spec:
|
33
49
|
|
34
50
|
```ruby
|
35
|
-
def with_modified_env(options, &block)
|
51
|
+
def with_modified_env(options = {}, &block)
|
36
52
|
ClimateControl.modify(options, &block)
|
37
53
|
end
|
38
54
|
```
|
@@ -89,6 +105,39 @@ manner becomes more difficult:
|
|
89
105
|
Climate Control modifies environment variables only within the context of the
|
90
106
|
block, ensuring values are managed properly and consistently.
|
91
107
|
|
108
|
+
## Thread-safety
|
109
|
+
|
110
|
+
When using threads, for instance when running tests concurrently in the same
|
111
|
+
process, you may need to wrap your code inside `ClimateControl.modify` blocks,
|
112
|
+
e.g.:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
first_thread = Thread.new do
|
116
|
+
ClimateControl.modify(SECRET: "1") do
|
117
|
+
p ENV["SECRET"] # => "1"
|
118
|
+
sleep 2
|
119
|
+
p ENV["SECRET"] # => "1"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
second_thread = Thread.new do
|
124
|
+
ClimateControl.modify({}) do
|
125
|
+
sleep 1
|
126
|
+
p ENV["SECRET"] # => nil
|
127
|
+
sleep 1
|
128
|
+
p ENV["SECRET"] # => nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
first_thread.join
|
133
|
+
second_thread.join
|
134
|
+
```
|
135
|
+
|
136
|
+
> 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).
|
137
|
+
>
|
138
|
+
> <cite><a href="https://github.com/thoughtbot/climate_control/issues/32#issuecomment-800713686">Josh Clayton</a></cite>
|
139
|
+
|
140
|
+
|
92
141
|
## Contributing
|
93
142
|
|
94
143
|
1. Fork it
|
@@ -97,6 +146,8 @@ block, ensuring values are managed properly and consistently.
|
|
97
146
|
4. Push to the branch (`git push origin my-new-feature`)
|
98
147
|
5. Create new Pull Request
|
99
148
|
|
149
|
+
This project uses [StandardRB](https://github.com/testdouble/standard) to ensure formatting.
|
150
|
+
|
100
151
|
## License
|
101
152
|
|
102
|
-
climate_control is copyright 2012-
|
153
|
+
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.
|
data/climate_control.gemspec
CHANGED
@@ -1,23 +1,25 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
lib = File.expand_path("../lib", __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require "climate_control/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name
|
8
|
-
gem.version
|
9
|
-
gem.authors
|
10
|
-
gem.email
|
11
|
-
gem.description
|
12
|
-
gem.summary
|
13
|
-
gem.homepage
|
14
|
-
gem.license
|
6
|
+
gem.name = "climate_control"
|
7
|
+
gem.version = ClimateControl::VERSION
|
8
|
+
gem.authors = ["Joshua Clayton", "Dorian Marié"]
|
9
|
+
gem.email = ["joshua.clayton@gmail.com", "dorian@dorianmarie.fr"]
|
10
|
+
gem.description = "Modify your ENV"
|
11
|
+
gem.summary = "Modify your ENV easily with ClimateControl"
|
12
|
+
gem.homepage = "https://github.com/thoughtbot/climate_control"
|
13
|
+
gem.license = "MIT"
|
15
14
|
|
16
|
-
gem.files
|
17
|
-
gem.test_files
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
17
|
gem.require_paths = ["lib"]
|
19
18
|
|
20
|
-
gem.
|
21
|
-
|
22
|
-
gem.add_development_dependency "
|
19
|
+
gem.required_ruby_version = ">= 2.5.0"
|
20
|
+
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
gem.add_development_dependency "rake"
|
23
|
+
gem.add_development_dependency "simplecov"
|
24
|
+
gem.add_development_dependency "standard"
|
23
25
|
end
|
data/lib/climate_control.rb
CHANGED
@@ -1,16 +1,44 @@
|
|
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
|
-
|
6
|
+
extend self
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
SEMAPHORE = Monitor.new
|
9
|
+
private_constant :SEMAPHORE
|
10
|
+
|
11
|
+
def modify(environment_overrides = {}, &block)
|
12
|
+
environment_overrides = environment_overrides.transform_keys(&:to_s)
|
13
|
+
|
14
|
+
SEMAPHORE.synchronize do
|
15
|
+
previous = ENV.to_hash
|
16
|
+
middle = {}
|
17
|
+
copy environment_overrides
|
18
|
+
middle = ENV.to_hash
|
19
|
+
block.call
|
20
|
+
ensure
|
21
|
+
after = ENV
|
22
|
+
(previous.keys | middle.keys | after.keys).each do |key|
|
23
|
+
if previous[key] != after[key] && middle[key] == after[key]
|
24
|
+
ENV[key] = previous[key]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
11
28
|
end
|
12
29
|
|
13
|
-
def
|
14
|
-
|
30
|
+
def env
|
31
|
+
ENV
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def copy(overrides)
|
37
|
+
overrides.each do |key, value|
|
38
|
+
ENV[key] = value
|
39
|
+
rescue TypeError => e
|
40
|
+
raise UnassignableValueError,
|
41
|
+
"attempted to assign #{value} to #{key} but failed (#{e.message})"
|
42
|
+
end
|
15
43
|
end
|
16
44
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
+
Thing = Class.new
|
4
|
+
|
3
5
|
describe "Climate control" do
|
4
6
|
it "allows modification of the environment" do
|
5
7
|
block_run = false
|
@@ -87,22 +89,22 @@ describe "Climate control" do
|
|
87
89
|
# 0.25s passes
|
88
90
|
# [other_thread] thread resolves, FOO is removed, BAZ is copied back to ENV
|
89
91
|
|
90
|
-
thread_removing_env = Thread.new
|
92
|
+
thread_removing_env = Thread.new {
|
91
93
|
with_modified_env BAZ: "buzz" do
|
92
94
|
sleep 0.5
|
93
95
|
end
|
94
96
|
|
95
97
|
expect(ENV["BAZ"]).to be_nil
|
96
|
-
|
98
|
+
}
|
97
99
|
|
98
|
-
other_thread = Thread.new
|
100
|
+
other_thread = Thread.new {
|
99
101
|
sleep 0.25
|
100
102
|
with_modified_env FOO: "bar" do
|
101
103
|
sleep 0.5
|
102
104
|
end
|
103
105
|
|
104
106
|
expect(ENV["FOO"]).to be_nil
|
105
|
-
|
107
|
+
}
|
106
108
|
|
107
109
|
thread_removing_env.join
|
108
110
|
other_thread.join
|
@@ -111,12 +113,45 @@ describe "Climate control" do
|
|
111
113
|
expect(ENV["BAZ"]).to be_nil
|
112
114
|
end
|
113
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
|
+
|
114
149
|
it "is re-entrant" do
|
115
|
-
ret = with_modified_env(FOO: "foo")
|
150
|
+
ret = with_modified_env(FOO: "foo") {
|
116
151
|
with_modified_env(BAR: "bar") do
|
117
152
|
"bar"
|
118
153
|
end
|
119
|
-
|
154
|
+
}
|
120
155
|
|
121
156
|
expect(ret).to eq("bar")
|
122
157
|
|
@@ -125,15 +160,14 @@ describe "Climate control" do
|
|
125
160
|
end
|
126
161
|
|
127
162
|
it "raises when the value cannot be assigned properly" do
|
128
|
-
Thing = Class.new
|
129
163
|
message = generate_type_error_for_object(Thing.new)
|
130
164
|
|
131
|
-
expect
|
165
|
+
expect {
|
132
166
|
with_modified_env(FOO: Thing.new)
|
133
|
-
|
167
|
+
}.to raise_error ClimateControl::UnassignableValueError, /attempted to assign .*Thing.* to FOO but failed \(#{message}\)$/
|
134
168
|
end
|
135
169
|
|
136
|
-
def with_modified_env(options, &block)
|
170
|
+
def with_modified_env(options = {}, &block)
|
137
171
|
ClimateControl.modify(options, &block)
|
138
172
|
end
|
139
173
|
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,76 +1,92 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: climate_control
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Clayton
|
8
|
-
|
8
|
+
- Dorian Marié
|
9
|
+
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2022-05-26 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:
|
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:
|
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:
|
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:
|
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
|
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
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: standard
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
55
70
|
description: Modify your ENV
|
56
71
|
email:
|
57
72
|
- joshua.clayton@gmail.com
|
73
|
+
- dorian@dorianmarie.fr
|
58
74
|
executables: []
|
59
75
|
extensions: []
|
60
76
|
extra_rdoc_files: []
|
61
77
|
files:
|
78
|
+
- ".github/workflows/ci.yml"
|
62
79
|
- ".gitignore"
|
63
|
-
-
|
80
|
+
- CHANGELOG.md
|
81
|
+
- CODEOWNERS
|
82
|
+
- CODE_OF_CONDUCT.md
|
64
83
|
- Gemfile
|
65
|
-
- LICENSE
|
66
|
-
- NEWS
|
84
|
+
- LICENSE
|
67
85
|
- README.md
|
68
86
|
- Rakefile
|
69
87
|
- climate_control.gemspec
|
70
88
|
- lib/climate_control.rb
|
71
|
-
- lib/climate_control/environment.rb
|
72
89
|
- lib/climate_control/errors.rb
|
73
|
-
- lib/climate_control/modifier.rb
|
74
90
|
- lib/climate_control/version.rb
|
75
91
|
- spec/acceptance/climate_control_spec.rb
|
76
92
|
- spec/spec_helper.rb
|
@@ -78,7 +94,7 @@ homepage: https://github.com/thoughtbot/climate_control
|
|
78
94
|
licenses:
|
79
95
|
- MIT
|
80
96
|
metadata: {}
|
81
|
-
post_install_message:
|
97
|
+
post_install_message:
|
82
98
|
rdoc_options: []
|
83
99
|
require_paths:
|
84
100
|
- lib
|
@@ -86,16 +102,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
102
|
requirements:
|
87
103
|
- - ">="
|
88
104
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
105
|
+
version: 2.5.0
|
90
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
107
|
requirements:
|
92
108
|
- - ">="
|
93
109
|
- !ruby/object:Gem::Version
|
94
110
|
version: '0'
|
95
111
|
requirements: []
|
96
|
-
|
97
|
-
|
98
|
-
signing_key:
|
112
|
+
rubygems_version: 3.3.13
|
113
|
+
signing_key:
|
99
114
|
specification_version: 4
|
100
115
|
summary: Modify your ENV easily with ClimateControl
|
101
116
|
test_files:
|
data/.travis.yml
DELETED
data/NEWS
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
0.2.0 (May 12, 2017)
|
2
|
-
Allow nested environment changes in the same thread
|
3
|
-
|
4
|
-
0.1.0 (January 7, 2017)
|
5
|
-
Remove ActiveSupport dependency
|
6
|
-
|
7
|
-
0.0.4 (January 6, 2017)
|
8
|
-
Improved thread safety
|
9
|
-
Handle TypeErrors during assignment
|
10
|
-
Improve documentation
|
11
|
-
|
12
|
-
0.0.1 (November 28, 2012)
|
13
|
-
Initial release
|
@@ -1,36 +0,0 @@
|
|
1
|
-
require "thread"
|
2
|
-
require "forwardable"
|
3
|
-
|
4
|
-
module ClimateControl
|
5
|
-
class Environment
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@semaphore = Mutex.new
|
10
|
-
@owner = nil
|
11
|
-
end
|
12
|
-
|
13
|
-
def_delegators :env, :[]=, :to_hash, :[], :delete
|
14
|
-
|
15
|
-
def synchronize
|
16
|
-
if @owner == Thread.current
|
17
|
-
return yield if block_given?
|
18
|
-
end
|
19
|
-
|
20
|
-
@semaphore.synchronize do
|
21
|
-
begin
|
22
|
-
@owner = Thread.current
|
23
|
-
yield if block_given?
|
24
|
-
ensure
|
25
|
-
@owner = nil
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def env
|
33
|
-
ENV
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,96 +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
|
-
begin
|
12
|
-
prepare_environment_for_block
|
13
|
-
run_block
|
14
|
-
ensure
|
15
|
-
cache_environment_after_block
|
16
|
-
delete_keys_that_do_not_belong
|
17
|
-
revert_changed_keys
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def prepare_environment_for_block
|
25
|
-
@original_env = clone_environment
|
26
|
-
copy_overrides_to_environment
|
27
|
-
@env_with_overrides_before_block = clone_environment
|
28
|
-
end
|
29
|
-
|
30
|
-
def run_block
|
31
|
-
@block.call
|
32
|
-
end
|
33
|
-
|
34
|
-
def copy_overrides_to_environment
|
35
|
-
@environment_overrides.each do |key, value|
|
36
|
-
begin
|
37
|
-
@env[key] = value
|
38
|
-
rescue TypeError => e
|
39
|
-
raise UnassignableValueError,
|
40
|
-
"attempted to assign #{value} to #{key} but failed (#{e.message})"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def keys_to_remove
|
46
|
-
@environment_overrides.keys
|
47
|
-
end
|
48
|
-
|
49
|
-
def keys_changed_by_block
|
50
|
-
@keys_changed_by_block ||= OverlappingKeysWithChangedValues.new(@env_with_overrides_before_block, @env_after_block).keys
|
51
|
-
end
|
52
|
-
|
53
|
-
def cache_environment_after_block
|
54
|
-
@env_after_block = clone_environment
|
55
|
-
end
|
56
|
-
|
57
|
-
def delete_keys_that_do_not_belong
|
58
|
-
(keys_to_remove - keys_changed_by_block).each {|key| @env.delete(key) }
|
59
|
-
end
|
60
|
-
|
61
|
-
def revert_changed_keys
|
62
|
-
(@original_env.keys - keys_changed_by_block).each do |key|
|
63
|
-
@env[key] = @original_env[key]
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def clone_environment
|
68
|
-
@env.to_hash
|
69
|
-
end
|
70
|
-
|
71
|
-
def stringify_keys(env)
|
72
|
-
env.each_with_object({}) do |(key, value), hash|
|
73
|
-
hash[key.to_s] = value
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
class OverlappingKeysWithChangedValues
|
78
|
-
def initialize(hash_1, hash_2)
|
79
|
-
@hash_1 = hash_1 || {}
|
80
|
-
@hash_2 = hash_2
|
81
|
-
end
|
82
|
-
|
83
|
-
def keys
|
84
|
-
overlapping_keys.select do |overlapping_key|
|
85
|
-
@hash_1[overlapping_key] != @hash_2[overlapping_key]
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
|
-
def overlapping_keys
|
92
|
-
@hash_2.keys & @hash_1.keys
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|