octiron 0.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 +7 -0
- data/.codeclimate.yml +31 -0
- data/.gitignore +39 -0
- data/.rspec +2 -0
- data/.rubocop.yml +78 -0
- data/.travis.yml +11 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +70 -0
- data/LICENSE +30 -0
- data/README.md +159 -0
- data/Rakefile +25 -0
- data/lib/octiron.rb +11 -0
- data/lib/octiron/events/bus.rb +161 -0
- data/lib/octiron/support/camel_case.rb +27 -0
- data/lib/octiron/support/constantize.rb +43 -0
- data/lib/octiron/support/identifiers.rb +45 -0
- data/lib/octiron/transmogrifiers/registry.rb +201 -0
- data/lib/octiron/version.rb +12 -0
- data/lib/octiron/world.rb +94 -0
- data/octiron.gemspec +50 -0
- data/spec/events_bus_spec.rb +322 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support_camel_case_spec.rb +24 -0
- data/spec/support_constantize_spec.rb +64 -0
- data/spec/support_identifiers_spec.rb +70 -0
- data/spec/transmogrifiers_registry_spec.rb +311 -0
- data/spec/world_spec.rb +58 -0
- metadata +191 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../lib/octiron/support/camel_case'
|
3
|
+
|
4
|
+
class Tester
|
5
|
+
include ::Octiron::Support::CamelCase
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ::Octiron::Support::CamelCase do
|
9
|
+
before do
|
10
|
+
@tester = Tester.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "capitalizes a single word" do
|
14
|
+
expect(@tester.camel_case('foo')).to eql 'Foo'
|
15
|
+
end
|
16
|
+
|
17
|
+
it "capitalizes letters in underscore separated words" do
|
18
|
+
expect(@tester.camel_case('foo_bar')).to eql 'FooBar'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "handles symbols" do
|
22
|
+
expect(@tester.camel_case(:foo_bar)).to eql 'FooBar'
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../lib/octiron/support/constantize'
|
3
|
+
|
4
|
+
class Tester
|
5
|
+
include ::Octiron::Support::Constantize
|
6
|
+
end
|
7
|
+
|
8
|
+
class Test
|
9
|
+
end
|
10
|
+
|
11
|
+
module TestModule
|
12
|
+
class InnerTest
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class A
|
17
|
+
class A
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class B < A
|
22
|
+
class B
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe ::Octiron::Support::Constantize do
|
27
|
+
before do
|
28
|
+
@tester = Tester.new
|
29
|
+
end
|
30
|
+
|
31
|
+
it "throws with an empty path (i.e. '::')" do
|
32
|
+
expect do
|
33
|
+
@tester.constantize('::')
|
34
|
+
end.to raise_error(NameError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "resolves a constant in the global namespace" do
|
38
|
+
expect(@tester.constantize('Test')).to eql Test
|
39
|
+
end
|
40
|
+
|
41
|
+
it "accepts absolute paths (i.e. starting with '::')" do
|
42
|
+
expect(@tester.constantize('::Test')).to eql Test
|
43
|
+
end
|
44
|
+
|
45
|
+
it "accepts nested names" do
|
46
|
+
expect(@tester.constantize('TestModule::InnerTest')).to \
|
47
|
+
eql TestModule::InnerTest
|
48
|
+
end
|
49
|
+
|
50
|
+
it "performs lookup in ancestors" do
|
51
|
+
expect(@tester.constantize('A::A')).to eql A::A
|
52
|
+
|
53
|
+
expect do
|
54
|
+
@tester.constantize('A::does_not_exist')
|
55
|
+
end.to raise_error(NameError)
|
56
|
+
|
57
|
+
expect(@tester.constantize('B::B')).to eql B::B
|
58
|
+
expect(@tester.constantize('B::A')).to eql A::A
|
59
|
+
|
60
|
+
expect do
|
61
|
+
@tester.constantize('B::does_not_exist')
|
62
|
+
end.to raise_error(NameError)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../lib/octiron/support/identifiers'
|
3
|
+
|
4
|
+
class Tester
|
5
|
+
include ::Octiron::Support::Identifiers
|
6
|
+
|
7
|
+
def initialize(*_)
|
8
|
+
@default_namespace = 'Octiron::Support'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Test
|
13
|
+
end
|
14
|
+
|
15
|
+
module TestModule
|
16
|
+
class InnerTest
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class A
|
21
|
+
class A
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class B < A
|
26
|
+
class B
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ::Octiron::Support::Identifiers do
|
31
|
+
before do
|
32
|
+
@tester = Tester.new
|
33
|
+
end
|
34
|
+
|
35
|
+
it "throws with an empty path (i.e. '::')" do
|
36
|
+
expect do
|
37
|
+
@tester.identify('::')
|
38
|
+
end.to raise_error(NameError)
|
39
|
+
|
40
|
+
expect do
|
41
|
+
@tester.identify('')
|
42
|
+
end.to raise_error(NameError)
|
43
|
+
|
44
|
+
expect do
|
45
|
+
@tester.identify(nil)
|
46
|
+
end.to raise_error(NameError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns the name of a Class" do
|
50
|
+
expect(@tester.identify(Test)).to eql 'Test'
|
51
|
+
expect(@tester.identify(A)).to eql 'A'
|
52
|
+
expect(@tester.identify(B)).to eql 'B'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns Hashes untouched" do
|
56
|
+
test_hash = {}
|
57
|
+
expect(@tester.identify(test_hash)).to eql test_hash
|
58
|
+
expect(@tester.identify(test_hash).object_id).to eql test_hash.object_id
|
59
|
+
end
|
60
|
+
|
61
|
+
it "resolves strings to constant names" do
|
62
|
+
expect(@tester.identify('Octiron::Support::Identifiers')).to eql \
|
63
|
+
'Octiron::Support::Identifiers'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "attempts to resolve anything else as constants in the default namespace" do
|
67
|
+
expect(@tester.identify(:identifiers)).to eql \
|
68
|
+
'Octiron::Support::Identifiers'
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../lib/octiron/transmogrifiers/registry'
|
3
|
+
|
4
|
+
class Test1
|
5
|
+
end
|
6
|
+
|
7
|
+
class Test2
|
8
|
+
end
|
9
|
+
|
10
|
+
class Test3
|
11
|
+
end
|
12
|
+
|
13
|
+
class Transmogrifier
|
14
|
+
attr_reader :invoked
|
15
|
+
def initialize
|
16
|
+
@invoked = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(_)
|
20
|
+
@invoked += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class HashTransmogrifier < Transmogrifier
|
25
|
+
def initialize(bad = false)
|
26
|
+
super()
|
27
|
+
@bad = bad
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(from)
|
31
|
+
super
|
32
|
+
|
33
|
+
if @bad
|
34
|
+
return {}
|
35
|
+
end
|
36
|
+
|
37
|
+
return {
|
38
|
+
a: from[:a],
|
39
|
+
b_c: from[:b][:c],
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Test1To2Transmogrifier < Transmogrifier
|
45
|
+
def call(from)
|
46
|
+
super
|
47
|
+
return Test2.new
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Test2To3Transmogrifier < Transmogrifier
|
52
|
+
def call(from)
|
53
|
+
super
|
54
|
+
return Test3.new
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Test1To3Transmogrifier < Transmogrifier
|
59
|
+
def call(from)
|
60
|
+
super
|
61
|
+
return Test3.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe Octiron::Transmogrifiers::Registry do
|
66
|
+
describe "construction" do
|
67
|
+
it "can be constructed without a default namespace" do
|
68
|
+
reg = nil
|
69
|
+
expect do
|
70
|
+
reg = ::Octiron::Transmogrifiers::Registry.new
|
71
|
+
end.not_to raise_error
|
72
|
+
|
73
|
+
expect(reg.default_namespace).to eql 'Octiron::Transmogrifiers'
|
74
|
+
end
|
75
|
+
|
76
|
+
it "can be constructed with a namespace" do
|
77
|
+
reg = nil
|
78
|
+
expect do
|
79
|
+
reg = ::Octiron::Transmogrifiers::Registry.new(::Octiron::Transmogrifiers)
|
80
|
+
end.not_to raise_error
|
81
|
+
|
82
|
+
expect(reg.default_namespace).to eql 'Octiron::Transmogrifiers'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "registration" do
|
87
|
+
before :each do
|
88
|
+
@reg = ::Octiron::Transmogrifiers::Registry.new
|
89
|
+
end
|
90
|
+
|
91
|
+
it "can register a transmogrifier object" do
|
92
|
+
expect do
|
93
|
+
@reg.register(Test1, Test2, false, Transmogrifier.new)
|
94
|
+
end.not_to raise_error
|
95
|
+
end
|
96
|
+
|
97
|
+
it "throws when registering a transmogrifier for the same pair twice" do
|
98
|
+
expect do
|
99
|
+
@reg.register(Test1, Test2, false, Transmogrifier.new)
|
100
|
+
end.not_to raise_error
|
101
|
+
|
102
|
+
expect do
|
103
|
+
@reg.register(Test1, Test2, false, Transmogrifier.new)
|
104
|
+
end.to raise_error(ArgumentError)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "can overwrite transmogrifiers" do
|
108
|
+
expect do
|
109
|
+
@reg.register(Test1, Test2, false, Transmogrifier.new)
|
110
|
+
end.not_to raise_error
|
111
|
+
|
112
|
+
expect do
|
113
|
+
@reg.register(Test1, Test2, true, Transmogrifier.new)
|
114
|
+
end.not_to raise_error
|
115
|
+
end
|
116
|
+
|
117
|
+
it "can register transmogrifier procs" do
|
118
|
+
expect do
|
119
|
+
@reg.register(Test1, Test2) do |_|
|
120
|
+
end
|
121
|
+
end.not_to raise_error
|
122
|
+
end
|
123
|
+
|
124
|
+
it "raises if no transmogrifier is given" do
|
125
|
+
expect do
|
126
|
+
@reg.register(Test1, Test2)
|
127
|
+
end.to raise_error(ArgumentError)
|
128
|
+
|
129
|
+
expect do
|
130
|
+
@reg.register(Test1, Test2, false)
|
131
|
+
end.to raise_error(ArgumentError)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "can deregister a transmogrifier" do
|
135
|
+
expect do
|
136
|
+
@reg.register(Test1, Test2) do |_|
|
137
|
+
end
|
138
|
+
end.not_to raise_error
|
139
|
+
|
140
|
+
# First time, there is a transmogrifier
|
141
|
+
expect do
|
142
|
+
@reg.deregister(Test1, Test2)
|
143
|
+
end.not_to raise_error
|
144
|
+
|
145
|
+
# Second time, there is none - it still shouldn't fail
|
146
|
+
expect do
|
147
|
+
@reg.deregister(Test1, Test2)
|
148
|
+
end.not_to raise_error
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "register argument validation" do
|
153
|
+
before :each do
|
154
|
+
@reg = ::Octiron::Transmogrifiers::Registry.new
|
155
|
+
end
|
156
|
+
|
157
|
+
it "accepts string names" do
|
158
|
+
expect do
|
159
|
+
@reg.register('Test1', 'Test2', false, Transmogrifier.new)
|
160
|
+
end.not_to raise_error
|
161
|
+
end
|
162
|
+
|
163
|
+
it "accepts symbol names" do
|
164
|
+
expect do
|
165
|
+
@reg.register(:test1, :test2, false, Transmogrifier.new)
|
166
|
+
end.not_to raise_error
|
167
|
+
end
|
168
|
+
|
169
|
+
it "accepts hash prototypes" do
|
170
|
+
proto1 = {
|
171
|
+
a: nil,
|
172
|
+
b: {
|
173
|
+
c: 42,
|
174
|
+
},
|
175
|
+
}
|
176
|
+
proto2 = {
|
177
|
+
a: nil,
|
178
|
+
b_c: nil,
|
179
|
+
}
|
180
|
+
|
181
|
+
expect do
|
182
|
+
@reg.register(proto1, proto2, false, Transmogrifier.new)
|
183
|
+
end.not_to raise_error
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "transmogrification" do
|
188
|
+
before :each do
|
189
|
+
@reg = ::Octiron::Transmogrifiers::Registry.new
|
190
|
+
@trans1to2 = Test1To2Transmogrifier.new
|
191
|
+
@trans2to3 = Test2To3Transmogrifier.new
|
192
|
+
@reg.register(Test1, Test2, false, @trans1to2)
|
193
|
+
@reg.register(Test2, Test3, false, @trans2to3)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "can transmogrify with a directly registered transmogrifier" do
|
197
|
+
result = nil
|
198
|
+
|
199
|
+
expect do
|
200
|
+
result = @reg.transmogrify(Test1.new, Test2)
|
201
|
+
end.not_to raise_error
|
202
|
+
|
203
|
+
expect(result.class).to eql Test2
|
204
|
+
|
205
|
+
expect(@trans1to2.invoked).to eql 1
|
206
|
+
expect(@trans2to3.invoked).to eql 0
|
207
|
+
end
|
208
|
+
|
209
|
+
it "can transmogrify with indirectly registered transmogrifiers" do
|
210
|
+
result = nil
|
211
|
+
|
212
|
+
expect do
|
213
|
+
result = @reg.transmogrify(Test1.new, Test3)
|
214
|
+
end.not_to raise_error
|
215
|
+
|
216
|
+
expect(result.class).to eql Test3
|
217
|
+
|
218
|
+
expect(@trans1to2.invoked).to eql 1
|
219
|
+
expect(@trans2to3.invoked).to eql 1
|
220
|
+
end
|
221
|
+
|
222
|
+
it "chooses the shortest transmogrification path" do
|
223
|
+
result = nil
|
224
|
+
|
225
|
+
direct = Test1To3Transmogrifier.new
|
226
|
+
@reg.register(Test1, Test3, false, direct)
|
227
|
+
|
228
|
+
expect do
|
229
|
+
result = @reg.transmogrify(Test1.new, Test3)
|
230
|
+
end.not_to raise_error
|
231
|
+
|
232
|
+
expect(result.class).to eql Test3
|
233
|
+
|
234
|
+
expect(@trans1to2.invoked).to eql 0
|
235
|
+
expect(@trans2to3.invoked).to eql 0
|
236
|
+
expect(direct.invoked).to eql 1
|
237
|
+
end
|
238
|
+
|
239
|
+
it "fails if no transmogrifier is found" do
|
240
|
+
expect do
|
241
|
+
@reg.transmogrify(Test1.new, Hash)
|
242
|
+
end.to raise_error(ArgumentError)
|
243
|
+
end
|
244
|
+
|
245
|
+
it "fails if a transmogrifier misbehaves" do
|
246
|
+
# Register the wrong 'direct' transmogrifier that will then be chosen
|
247
|
+
direct = Test1To2Transmogrifier.new
|
248
|
+
@reg.register(Test1, Test3, false, direct)
|
249
|
+
|
250
|
+
expect do
|
251
|
+
@reg.transmogrify(Test1.new, Test3)
|
252
|
+
end.to raise_error(RuntimeError)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "transmogrifies hashes" do
|
256
|
+
proto1 = {
|
257
|
+
a: nil,
|
258
|
+
b: {
|
259
|
+
c: 42,
|
260
|
+
},
|
261
|
+
}
|
262
|
+
proto2 = {
|
263
|
+
a: nil,
|
264
|
+
b_c: nil,
|
265
|
+
}
|
266
|
+
|
267
|
+
@reg.register(proto1, proto2, false, HashTransmogrifier.new)
|
268
|
+
|
269
|
+
from = {
|
270
|
+
a: 'foo',
|
271
|
+
b: {
|
272
|
+
c: 42,
|
273
|
+
},
|
274
|
+
}
|
275
|
+
|
276
|
+
result = nil
|
277
|
+
expect do
|
278
|
+
result = @reg.transmogrify(from, proto2)
|
279
|
+
end.not_to raise_error
|
280
|
+
|
281
|
+
expect(result[:a]).to eql 'foo'
|
282
|
+
expect(result[:b_c]).to eql 42
|
283
|
+
end
|
284
|
+
|
285
|
+
it "throws if a hash transmogrifier produces bad results" do
|
286
|
+
proto1 = {
|
287
|
+
a: nil,
|
288
|
+
b: {
|
289
|
+
c: 42,
|
290
|
+
},
|
291
|
+
}
|
292
|
+
proto2 = {
|
293
|
+
a: nil,
|
294
|
+
b_c: nil,
|
295
|
+
}
|
296
|
+
|
297
|
+
@reg.register(proto1, proto2, false, HashTransmogrifier.new(true))
|
298
|
+
|
299
|
+
from = {
|
300
|
+
a: 'foo',
|
301
|
+
b: {
|
302
|
+
c: 42,
|
303
|
+
},
|
304
|
+
}
|
305
|
+
|
306
|
+
expect do
|
307
|
+
@reg.transmogrify(from, proto2)
|
308
|
+
end.to raise_error(RuntimeError)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|