collapsium 0.3.0 → 0.4.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 +4 -4
- data/Gemfile.lock +6 -6
- data/collapsium.gemspec +1 -1
- data/lib/collapsium.rb +6 -1
- data/lib/collapsium/environment_override.rb +167 -0
- data/lib/collapsium/pathed_access.rb +173 -81
- data/lib/collapsium/recursive_dup.rb +8 -3
- data/lib/collapsium/recursive_merge.rb +8 -4
- data/lib/collapsium/recursive_sort.rb +6 -33
- data/lib/collapsium/support/hash_methods.rb +48 -0
- data/lib/collapsium/support/methods.rb +88 -0
- data/lib/collapsium/uber_hash.rb +7 -1
- data/lib/collapsium/version.rb +1 -1
- data/lib/collapsium/viral_capabilities.rb +151 -0
- data/spec/environment_override_spec.rb +154 -0
- data/spec/pathed_access_spec.rb +83 -0
- data/spec/recursive_merge_spec.rb +17 -0
- data/spec/support_methods_spec.rb +231 -0
- data/spec/uber_hash_spec.rb +16 -0
- data/spec/viral_capabilities_spec.rb +248 -0
- metadata +14 -5
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../lib/collapsium/environment_override'
|
3
|
+
require_relative '../lib/collapsium/pathed_access'
|
4
|
+
|
5
|
+
class EnvironmentHash < Hash
|
6
|
+
# We need only one of these; this is for coverage mostly
|
7
|
+
prepend ::Collapsium::EnvironmentOverride
|
8
|
+
include ::Collapsium::EnvironmentOverride
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ::Collapsium::EnvironmentOverride do
|
12
|
+
before :each do
|
13
|
+
@tester = { "foo" => { "bar" => 42 } }
|
14
|
+
@tester.extend(::Collapsium::EnvironmentOverride)
|
15
|
+
ENV.delete("FOO")
|
16
|
+
ENV.delete("BAR")
|
17
|
+
end
|
18
|
+
|
19
|
+
context "environment variable name" do
|
20
|
+
it "upcases keys" do
|
21
|
+
expect(@tester.key_to_env("foo")).to eql "FOO"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "replaces non-alphanumeric characters with underscores" do
|
25
|
+
expect(@tester.key_to_env("foo!bar")).to eql "FOO_BAR"
|
26
|
+
expect(@tester.key_to_env("foo.bar")).to eql "FOO_BAR"
|
27
|
+
expect(@tester.key_to_env("foo@bar")).to eql "FOO_BAR"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "collapses multiple underscores into one" do
|
31
|
+
expect(@tester.key_to_env("foo!_@bar")).to eql "FOO_BAR"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "strips leading and trailing underscores" do
|
35
|
+
expect(@tester.key_to_env(".foo@bar")).to eql "FOO_BAR"
|
36
|
+
expect(@tester.key_to_env("foo@bar_")).to eql "FOO_BAR"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "without pathed access" do
|
41
|
+
it "overrides first-order keys" do
|
42
|
+
expect(@tester["foo"].is_a?(Hash)).to be_truthy
|
43
|
+
ENV["FOO"] = "test"
|
44
|
+
expect(@tester["foo"].is_a?(Hash)).to be_falsy
|
45
|
+
expect(@tester["foo"]).to eql "test"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "inherits environment override" do
|
49
|
+
expect(@tester["foo"]["bar"].is_a?(Fixnum)).to be_truthy
|
50
|
+
ENV["BAR"] = "test"
|
51
|
+
expect(@tester["foo"]["bar"].is_a?(Fixnum)).to be_falsy
|
52
|
+
expect(@tester["foo"]["bar"]).to eql "test"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "write still works" do
|
56
|
+
@tester.store("foo", 42)
|
57
|
+
expect(@tester["foo"]).to eql 42
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with pathed access" do
|
62
|
+
before :each do
|
63
|
+
@tester = { "foo" => { "bar" => 42 } }
|
64
|
+
@tester.extend(::Collapsium::EnvironmentOverride)
|
65
|
+
@tester.extend(::Collapsium::PathedAccess)
|
66
|
+
ENV["FOO"] = nil
|
67
|
+
ENV["BAR"] = nil
|
68
|
+
ENV["FOO_BAR"] = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "overrides first-order keys" do
|
72
|
+
expect(@tester["foo"].is_a?(Hash)).to be_truthy
|
73
|
+
ENV["FOO"] = "test"
|
74
|
+
expect(@tester["foo"].is_a?(Hash)).to be_falsy
|
75
|
+
expect(@tester["foo"]).to eql "test"
|
76
|
+
end
|
77
|
+
|
78
|
+
it "inherits environment override" do
|
79
|
+
expect(@tester["foo"]["bar"].is_a?(Fixnum)).to be_truthy
|
80
|
+
ENV["BAR"] = "test"
|
81
|
+
expect(@tester["foo"]["bar"].is_a?(Fixnum)).to be_falsy
|
82
|
+
expect(@tester["foo"]["bar"]).to eql "test"
|
83
|
+
end
|
84
|
+
|
85
|
+
it "write still works" do
|
86
|
+
@tester.store("foo", 42)
|
87
|
+
expect(@tester["foo"]).to eql 42
|
88
|
+
end
|
89
|
+
|
90
|
+
it "overrides from pathed key" do
|
91
|
+
expect(@tester["foo.bar"].is_a?(Fixnum)).to be_truthy
|
92
|
+
ENV["FOO_BAR"] = "test"
|
93
|
+
expect(@tester["foo.bar"].is_a?(Fixnum)).to be_falsy
|
94
|
+
expect(@tester["foo.bar"]).to eql "test"
|
95
|
+
end
|
96
|
+
|
97
|
+
it "prefers pathed key over non-pathed key" do
|
98
|
+
expect(@tester["foo.bar"].is_a?(Fixnum)).to be_truthy
|
99
|
+
ENV["FOO_BAR"] = "pathed"
|
100
|
+
ENV["BAR"] = "simple"
|
101
|
+
expect(@tester["foo.bar"].is_a?(Fixnum)).to be_falsy
|
102
|
+
expect(@tester["foo.bar"]).to eql "pathed"
|
103
|
+
end
|
104
|
+
|
105
|
+
it "prefers pathed key over non-pathed key when using nested values" do
|
106
|
+
expect(@tester["foo"]["bar"].is_a?(Fixnum)).to be_truthy
|
107
|
+
ENV["FOO_BAR"] = "pathed"
|
108
|
+
ENV["BAR"] = "simple"
|
109
|
+
expect(@tester["foo"]["bar"].is_a?(Fixnum)).to be_falsy
|
110
|
+
expect(@tester["foo"]["bar"]).to eql "pathed"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "respects the behaviour of wrapped methods" do
|
115
|
+
it "works with :[]" do
|
116
|
+
ENV["FOO"] = "test"
|
117
|
+
expect(@tester["foo"]).to eql "test"
|
118
|
+
end
|
119
|
+
|
120
|
+
it "works with :fetch" do
|
121
|
+
ENV["FOO"] = "test"
|
122
|
+
expect(@tester.fetch("foo", 1234)).to eql "test"
|
123
|
+
end
|
124
|
+
|
125
|
+
it "works with :key?" do
|
126
|
+
ENV["FOO"] = "test"
|
127
|
+
expect(@tester.key?("foo")).to eql true # not be_truthy
|
128
|
+
expect(@tester.key?("bar")).to eql false # not be_falsey
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context EnvironmentHash do
|
133
|
+
let(:test_hash) { EnvironmentHash.new }
|
134
|
+
|
135
|
+
it "works when prepended" do
|
136
|
+
ENV["FOO"] = "test"
|
137
|
+
expect(test_hash["foo"]).to eql "test"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "JSON" do
|
142
|
+
it "interprets JSON content in environment variables" do
|
143
|
+
ENV["FOO"] = '{ "json_key": "json_value" }'
|
144
|
+
expect(@tester["foo"].is_a?(Hash)).to be_truthy
|
145
|
+
expect(@tester["foo"]["json_key"]).to eql "json_value"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "coverage" do
|
150
|
+
it "raises when not passing arguments" do
|
151
|
+
expect { @tester.fetch }.to raise_error
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/spec/pathed_access_spec.rb
CHANGED
@@ -1,12 +1,46 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require_relative '../lib/collapsium/pathed_access'
|
3
3
|
|
4
|
+
class PathedHash < Hash
|
5
|
+
prepend ::Collapsium::PathedAccess
|
6
|
+
end
|
7
|
+
|
4
8
|
describe ::Collapsium::PathedAccess do
|
5
9
|
before :each do
|
6
10
|
@tester = {}
|
7
11
|
@tester.extend(::Collapsium::PathedAccess)
|
8
12
|
end
|
9
13
|
|
14
|
+
describe "Path components" do
|
15
|
+
it "splits a path into components" do
|
16
|
+
expect(@tester.path_components("foo.bar")).to eql %w(foo bar)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "strips empty components at the beginning" do
|
20
|
+
expect(@tester.path_components("..foo.bar")).to eql %w(foo bar)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "strips empty components at the end" do
|
24
|
+
expect(@tester.path_components("foo.bar..")).to eql %w(foo bar)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "strips empty components in the middle" do
|
28
|
+
expect(@tester.path_components("foo...bar")).to eql %w(foo bar)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "joins path components" do
|
32
|
+
expect(@tester.join_path(%w(foo bar))).to eql "foo.bar"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "joins empty components to an empty string" do
|
36
|
+
expect(@tester.join_path([])).to eql ""
|
37
|
+
end
|
38
|
+
|
39
|
+
it "normalizes a path" do
|
40
|
+
expect(@tester.normalize_path("foo..bar..baz.")).to eql ".foo.bar.baz"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
10
44
|
describe "Hash-like" do
|
11
45
|
it "responds to Hash functions" do
|
12
46
|
[:invert, :delete, :fetch].each do |meth|
|
@@ -27,6 +61,26 @@ describe ::Collapsium::PathedAccess do
|
|
27
61
|
end
|
28
62
|
|
29
63
|
describe "pathed access" do
|
64
|
+
context ":path_prefix" do
|
65
|
+
it "can be read" do
|
66
|
+
expect { @tester.path_prefix }.not_to raise_error
|
67
|
+
end
|
68
|
+
|
69
|
+
it "defaults to empty String" do
|
70
|
+
expect(@tester.path_prefix).to be_empty
|
71
|
+
expect(@tester.path_prefix.class).to eql String
|
72
|
+
end
|
73
|
+
|
74
|
+
it "can be set" do
|
75
|
+
expect { @tester.path_prefix = "foo.bar" }.not_to raise_error
|
76
|
+
end
|
77
|
+
|
78
|
+
it "normalizes when set" do
|
79
|
+
@tester.path_prefix = "foo..bar..baz.."
|
80
|
+
expect(@tester.path_prefix).to eql ".foo.bar.baz"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
30
84
|
it "can recursively read entries via a path" do
|
31
85
|
@tester["foo"] = 42
|
32
86
|
@tester["bar"] = {
|
@@ -84,6 +138,26 @@ describe ::Collapsium::PathedAccess do
|
|
84
138
|
end
|
85
139
|
end
|
86
140
|
|
141
|
+
describe "nested inherit capabilities" do
|
142
|
+
before do
|
143
|
+
@tester['foo'] = {
|
144
|
+
'bar' => {
|
145
|
+
'baz' => 42,
|
146
|
+
},
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
it "can still perform pathed access" do
|
151
|
+
foo = @tester['foo']
|
152
|
+
expect(foo['bar.baz']).to eql 42
|
153
|
+
end
|
154
|
+
|
155
|
+
it "knows its path prefix" do
|
156
|
+
bar = @tester['foo.bar']
|
157
|
+
expect(bar.path_prefix).to eql '.foo.bar'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
87
161
|
describe "with indifferent access" do
|
88
162
|
before do
|
89
163
|
require_relative '../lib/collapsium/indifferent_access'
|
@@ -104,4 +178,13 @@ describe ::Collapsium::PathedAccess do
|
|
104
178
|
expect(@tester['foo.baz']).to eql 'quux'
|
105
179
|
end
|
106
180
|
end
|
181
|
+
|
182
|
+
context PathedHash do
|
183
|
+
let(:test_hash) { PathedHash.new }
|
184
|
+
|
185
|
+
it "can write recursively" do
|
186
|
+
test_hash["foo.bar"] = 42
|
187
|
+
expect(test_hash["foo.bar"]).to eql 42
|
188
|
+
end
|
189
|
+
end
|
107
190
|
end
|
@@ -67,4 +67,21 @@ describe ::Collapsium::RecursiveMerge do
|
|
67
67
|
|
68
68
|
expect(x[:bar].length).to eql 2
|
69
69
|
end
|
70
|
+
|
71
|
+
it "makes nested hashes able to merge recursively" do
|
72
|
+
@tester[:foo] = {
|
73
|
+
bar: true,
|
74
|
+
}
|
75
|
+
expect(@tester[:foo].respond_to?(:recursive_merge)).to be_truthy
|
76
|
+
|
77
|
+
to_merge = {
|
78
|
+
foo: {
|
79
|
+
bar: false,
|
80
|
+
},
|
81
|
+
}
|
82
|
+
x = @tester.recursive_merge(to_merge)
|
83
|
+
|
84
|
+
expect(@tester[:foo].respond_to?(:recursive_merge)).to be_truthy
|
85
|
+
expect(x[:foo].respond_to?(:recursive_merge)).to be_truthy
|
86
|
+
end
|
70
87
|
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../lib/collapsium/support/methods'
|
3
|
+
|
4
|
+
module First
|
5
|
+
class << self
|
6
|
+
include ::Collapsium::Support::Methods
|
7
|
+
|
8
|
+
def prepended(base)
|
9
|
+
wrap_method(base, :calling_test) do |super_method, *args, &block|
|
10
|
+
result = super_method.call(*args, &block)
|
11
|
+
next "first: #{result}"
|
12
|
+
end
|
13
|
+
|
14
|
+
wrap_method(base, :test) do
|
15
|
+
next "first"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end # class << self
|
19
|
+
end # module First
|
20
|
+
|
21
|
+
module Second
|
22
|
+
class << self
|
23
|
+
include ::Collapsium::Support::Methods
|
24
|
+
|
25
|
+
def prepended(base)
|
26
|
+
wrap_method(base, :calling_test) do |super_method, *args, &block|
|
27
|
+
result = super_method.call(*args, &block)
|
28
|
+
next "second: #{result}"
|
29
|
+
end
|
30
|
+
|
31
|
+
wrap_method(base, :test) do
|
32
|
+
next "second"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end # class << self
|
36
|
+
end # module First
|
37
|
+
|
38
|
+
class FirstThenSecond
|
39
|
+
def calling_test
|
40
|
+
return "first_then_second"
|
41
|
+
end
|
42
|
+
|
43
|
+
def test
|
44
|
+
return "first_then_second"
|
45
|
+
end
|
46
|
+
|
47
|
+
prepend First
|
48
|
+
prepend Second
|
49
|
+
end
|
50
|
+
|
51
|
+
class SecondThenFirst
|
52
|
+
def calling_test
|
53
|
+
return "second_then_first"
|
54
|
+
end
|
55
|
+
|
56
|
+
def test
|
57
|
+
return "second_then_first"
|
58
|
+
end
|
59
|
+
|
60
|
+
prepend Second
|
61
|
+
prepend First
|
62
|
+
end
|
63
|
+
|
64
|
+
module IncludeModule
|
65
|
+
class << self
|
66
|
+
include ::Collapsium::Support::Methods
|
67
|
+
|
68
|
+
def included(base)
|
69
|
+
wrap_method(base, :calling_test) do |super_method, *args, &block|
|
70
|
+
result = super_method.call(*args, &block)
|
71
|
+
next "include_module: #{result}"
|
72
|
+
end
|
73
|
+
|
74
|
+
wrap_method(base, :test) do
|
75
|
+
next "include_module"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Included
|
82
|
+
def calling_test
|
83
|
+
return "included"
|
84
|
+
end
|
85
|
+
|
86
|
+
def test
|
87
|
+
return "included"
|
88
|
+
end
|
89
|
+
|
90
|
+
include IncludeModule
|
91
|
+
end
|
92
|
+
|
93
|
+
module NestModule
|
94
|
+
def calling_test
|
95
|
+
return "nest_module"
|
96
|
+
end
|
97
|
+
|
98
|
+
def test
|
99
|
+
return "nest_module"
|
100
|
+
end
|
101
|
+
|
102
|
+
include IncludeModule
|
103
|
+
end
|
104
|
+
|
105
|
+
class PrependNested
|
106
|
+
prepend NestModule
|
107
|
+
end
|
108
|
+
|
109
|
+
class IncludeNested
|
110
|
+
include NestModule
|
111
|
+
end
|
112
|
+
|
113
|
+
class IncludeNestedWithOwn
|
114
|
+
def calling_test
|
115
|
+
return "include_nested_with_own"
|
116
|
+
end
|
117
|
+
|
118
|
+
def test
|
119
|
+
return "include_nested_with_own"
|
120
|
+
end
|
121
|
+
|
122
|
+
include NestModule
|
123
|
+
end
|
124
|
+
|
125
|
+
class PrependNestedWithOwn
|
126
|
+
def calling_test
|
127
|
+
return "prepend_nested_with_own"
|
128
|
+
end
|
129
|
+
|
130
|
+
def test
|
131
|
+
return "prepend_nested_with_own"
|
132
|
+
end
|
133
|
+
|
134
|
+
prepend NestModule
|
135
|
+
end
|
136
|
+
|
137
|
+
describe ::Collapsium::Support::Methods do
|
138
|
+
context "#wrap_method" do
|
139
|
+
it "fails if there is no method to wrap" do
|
140
|
+
expect do
|
141
|
+
class NoMethodToWrap
|
142
|
+
prepend First
|
143
|
+
end
|
144
|
+
end.to raise_error(NameError)
|
145
|
+
end
|
146
|
+
|
147
|
+
context FirstThenSecond do
|
148
|
+
let(:tester) { FirstThenSecond.new }
|
149
|
+
|
150
|
+
it "uses only the second module's return value for non-calling methods" do
|
151
|
+
expect(tester.test).to eql 'second'
|
152
|
+
end
|
153
|
+
|
154
|
+
it "wraps results appropriately for calling methods" do
|
155
|
+
expect(tester.calling_test).to eql 'second: first: first_then_second'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context SecondThenFirst do
|
160
|
+
let(:tester) { SecondThenFirst.new }
|
161
|
+
|
162
|
+
it "uses only the second module's return value for non-calling methods" do
|
163
|
+
expect(tester.test).to eql 'first'
|
164
|
+
end
|
165
|
+
|
166
|
+
it "wraps results appropriately for calling methods" do
|
167
|
+
expect(tester.calling_test).to eql 'first: second: second_then_first'
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context Included do
|
172
|
+
let(:tester) { Included.new }
|
173
|
+
|
174
|
+
it "uses only the included module's return value for non-calling methods" do
|
175
|
+
expect(tester.test).to eql 'include_module'
|
176
|
+
end
|
177
|
+
|
178
|
+
it "wraps results appropriately for calling methods" do
|
179
|
+
expect(tester.calling_test).to eql 'include_module: included'
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context PrependNested do
|
184
|
+
let(:tester) { PrependNested.new }
|
185
|
+
|
186
|
+
it "uses only the included module's return value for non-calling methods" do
|
187
|
+
expect(tester.test).to eql 'include_module'
|
188
|
+
end
|
189
|
+
|
190
|
+
it "wraps results appropriately for calling methods" do
|
191
|
+
expect(tester.calling_test).to eql 'include_module: nest_module'
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context IncludeNested do
|
196
|
+
let(:tester) { IncludeNested.new }
|
197
|
+
|
198
|
+
it "uses only the included module's return value for non-calling methods" do
|
199
|
+
expect(tester.test).to eql 'include_module'
|
200
|
+
end
|
201
|
+
|
202
|
+
it "wraps results appropriately for calling methods" do
|
203
|
+
expect(tester.calling_test).to eql 'include_module: nest_module'
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context IncludeNestedWithOwn do
|
208
|
+
let(:tester) { IncludeNestedWithOwn.new }
|
209
|
+
|
210
|
+
it "uses only the own class result for non-calling methods" do
|
211
|
+
expect(tester.test).to eql 'include_nested_with_own'
|
212
|
+
end
|
213
|
+
|
214
|
+
it "uses only the own class result for calling methods" do
|
215
|
+
expect(tester.calling_test).to eql 'include_nested_with_own'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context PrependNestedWithOwn do
|
220
|
+
let(:tester) { PrependNestedWithOwn.new }
|
221
|
+
|
222
|
+
it "ignores class methods for non-calling methods" do
|
223
|
+
expect(tester.test).to eql 'include_module'
|
224
|
+
end
|
225
|
+
|
226
|
+
it "ignores class methods for calling methods" do
|
227
|
+
expect(tester.calling_test).to eql 'include_module: nest_module'
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|