accord 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.
Files changed (49) hide show
  1. data/.gitignore +7 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +7 -0
  4. data/Gemfile.lock +43 -0
  5. data/accord.gemspec +20 -0
  6. data/lib/accord.rb +4 -0
  7. data/lib/accord/adapter_registry.rb +106 -0
  8. data/lib/accord/base_registry.rb +73 -0
  9. data/lib/accord/declarations.rb +131 -0
  10. data/lib/accord/exceptions.rb +22 -0
  11. data/lib/accord/extendor.rb +36 -0
  12. data/lib/accord/extendor_container.rb +43 -0
  13. data/lib/accord/interface.rb +189 -0
  14. data/lib/accord/interface_body.rb +73 -0
  15. data/lib/accord/interface_members.rb +31 -0
  16. data/lib/accord/interface_method.rb +27 -0
  17. data/lib/accord/interfaces.rb +471 -0
  18. data/lib/accord/nested_key_hash.rb +66 -0
  19. data/lib/accord/ro.rb +65 -0
  20. data/lib/accord/signature_info.rb +76 -0
  21. data/lib/accord/specification.rb +83 -0
  22. data/lib/accord/subscription_registry.rb +76 -0
  23. data/lib/accord/tags.rb +25 -0
  24. data/lib/accord/version.rb +3 -0
  25. data/spec/adapter_registry_spec.rb +296 -0
  26. data/spec/declarations_spec.rb +144 -0
  27. data/spec/extendor_container_spec.rb +101 -0
  28. data/spec/extendor_spec.rb +203 -0
  29. data/spec/integration/adaptation_spec.rb +86 -0
  30. data/spec/integration/adapter_for_class_declaration_spec.rb +22 -0
  31. data/spec/integration/adapter_hook_spec.rb +41 -0
  32. data/spec/integration/default_adapters_spec.rb +81 -0
  33. data/spec/integration/hash_adapters_spec.rb +20 -0
  34. data/spec/integration/interface_declaration_spec.rb +258 -0
  35. data/spec/integration/multi_adapters_spec.rb +83 -0
  36. data/spec/integration/named_adapters_spec.rb +54 -0
  37. data/spec/integration/single_adapters_spec.rb +93 -0
  38. data/spec/integration/subscriptions_spec.rb +245 -0
  39. data/spec/integration/verification_spec.rb +215 -0
  40. data/spec/interface_body_spec.rb +157 -0
  41. data/spec/interface_members_spec.rb +57 -0
  42. data/spec/interface_spec.rb +147 -0
  43. data/spec/nested_key_hash_spec.rb +140 -0
  44. data/spec/signature_info_spec.rb +65 -0
  45. data/spec/spec_helper.rb +2 -0
  46. data/spec/specification_spec.rb +246 -0
  47. data/spec/subscription_registry_spec.rb +206 -0
  48. data/spec/tags_spec.rb +38 -0
  49. metadata +134 -0
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'accord/signature_info'
3
+
4
+ module Accord
5
+ describe SignatureInfo do
6
+ subject { SignatureInfo.new }
7
+
8
+ describe "#param" do
9
+ it "adds new arguments to #arguments" do
10
+ subject.param(:foo)
11
+ expect(subject.arguments).to eq [ { name: :foo } ]
12
+ end
13
+
14
+ it "converts names to symbols" do
15
+ subject.param('foo')
16
+ expect(subject.arguments).to eq [ { name: :foo } ]
17
+ end
18
+
19
+ it "accepts a unitary hash which holds a default value" do
20
+ subject.param(foo: :bar)
21
+ expect(subject.arguments).to eq [ { name: :foo, default: :bar } ]
22
+ end
23
+
24
+ it "rejects anything else" do
25
+ expect { subject.param(1) }.to raise_error(ArgumentError)
26
+ end
27
+ end
28
+
29
+ describe "#splat" do
30
+ context "with a symbol" do
31
+ it "adds a splat argument" do
32
+ subject.splat(:foo)
33
+ expect(subject.arguments).to eq [ { name: :foo, splat: true } ]
34
+ end
35
+ end
36
+
37
+ context "with a string" do
38
+ it "sets a splat argument, as a symbol" do
39
+ subject.splat('foo')
40
+ expect(subject.arguments).to eq [ { name: :foo, splat: true } ]
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "#block" do
46
+ it "returns nil if no splat argument has been set" do
47
+ expect(subject.block).to be_nil
48
+ end
49
+
50
+ context "with a symbol" do
51
+ it "sets a block argument" do
52
+ subject.block(:foo)
53
+ expect(subject.block).to eq :foo
54
+ end
55
+ end
56
+
57
+ context "with a string" do
58
+ it "sets a block argument, as a symbol" do
59
+ subject.block('foo')
60
+ expect(subject.block).to eq :foo
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,2 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
@@ -0,0 +1,246 @@
1
+ require 'spec_helper'
2
+ require 'set'
3
+ require 'accord/specification'
4
+
5
+ module Accord
6
+ describe Specification do
7
+ describe "empty specification" do
8
+ let(:spec) { Specification.new }
9
+ it "has only itself as its ancestor" do
10
+ expect(spec.ancestors).to eq [spec]
11
+ end
12
+
13
+ it "extends itself" do
14
+ expect(spec).to be_extends(spec)
15
+ end
16
+ end
17
+
18
+ describe "specification based on another" do
19
+ let(:base_spec) { Specification.new }
20
+ let(:spec) { Specification.new([base_spec]) }
21
+
22
+ it "has itself as and its base specification as ancestors" do
23
+ expect(Set.new(spec.ancestors)).to eq Set.new([spec, base_spec])
24
+ end
25
+
26
+ it "extends itself" do
27
+ expect(spec).to be_extends(spec)
28
+ end
29
+
30
+ it "extends the base specification" do
31
+ expect(spec).to be_extends(base_spec)
32
+ end
33
+ end
34
+
35
+ describe "specification ancestry ordering" do
36
+ # Note that spec ancestors are not sorted like Ruby's module ancestors.
37
+ #
38
+ # The expected results should match the C3 algorithm. For more
39
+ # information, refer to the MRO used in Python (introduced in Python
40
+ # 2.3): http://www.python.org/download/releases/2.3/mro/
41
+
42
+ let(:root) { Specification.new(:root) }
43
+
44
+ describe "impossible" do
45
+ let(:a) { Specification.new([root]) }
46
+ let(:b) { Specification.new([a, root]) }
47
+ let(:c) { Specification.new([root]) }
48
+ let(:d) { Specification.new([b, c]) }
49
+ # let(:s5) { Specification.new([c, b, d]) } # This should break
50
+
51
+ it "rejects such hierarchy" do
52
+ # This hierarchy is rejected because local order prececedence cannot
53
+ # be satisfied without violating monotonicity.
54
+ expect { Specification.new([c, b, d]) }.to raise_error(TypeError)
55
+ end
56
+ end
57
+
58
+ describe "possible" do
59
+ let(:a) { Specification.new(:a, [root]) }
60
+ let(:b) { Specification.new(:b, [root]) }
61
+ let(:c) { Specification.new(:c, [root]) }
62
+ let(:d) { Specification.new(:d, [c, a]) }
63
+ let(:e) { Specification.new(:e, [c, b]) }
64
+ let(:f) { Specification.new(:f, [e, d]) }
65
+
66
+ let(:expected_resolution_order) { [f, e, d, c, b, a, root] }
67
+
68
+ subject { f }
69
+ its(:ancestors) { should eq expected_resolution_order }
70
+ end
71
+
72
+ describe "possible" do
73
+ let(:a) { Specification.new(:a, [root]) }
74
+ let(:b) { Specification.new(:b, [root]) }
75
+ let(:c) { Specification.new(:c, [root]) }
76
+ let(:d) { Specification.new(:d, [c, a]) }
77
+ let(:e) { Specification.new(:e, [b, c]) }
78
+ let(:f) { Specification.new(:f, [e, d]) }
79
+
80
+ let(:expected_resolution_order) { [f, e, b, d, c, a, root] }
81
+
82
+ subject { f }
83
+ its(:ancestors) { should eq expected_resolution_order }
84
+ end
85
+
86
+ describe "possible" do
87
+ let(:a) { Specification.new(:a, [root]) }
88
+ let(:b) { Specification.new(:b, [root]) }
89
+ let(:c) { Specification.new(:c, [root]) }
90
+ let(:d) { Specification.new(:d, [root]) }
91
+ let(:e) { Specification.new(:e, [root]) }
92
+ let(:k1) { Specification.new(:k1, [a, b, c]) }
93
+ let(:k2) { Specification.new(:k2, [d, b, e]) }
94
+ let(:k3) { Specification.new(:k3, [d, a]) }
95
+ let(:z) { Specification.new(:z, [k1, k2, k3]) }
96
+
97
+ let(:expected_resolution_order) do
98
+ [z, k1, k2, k3, d, a, b, c, e, root]
99
+ end
100
+
101
+ subject { z }
102
+ its(:ancestors) { should eq expected_resolution_order }
103
+ end
104
+ end
105
+
106
+ describe "bad bases" do
107
+ context "on construction" do
108
+ it "complains if something other than Specification is set as a base" do
109
+ expect { Specification.new(['bad']) }.to raise_error(TypeError)
110
+ end
111
+ end
112
+
113
+ context "on #bases=" do
114
+ let(:spec) { Specification.new }
115
+
116
+ it "complains if set to not an array of Specification's" do
117
+ expect { spec.bases = ['bad'] }.to raise_error(TypeError)
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "bases change" do
123
+ let(:spec) { Specification.new }
124
+ let(:dependent_spec) { Specification.new([spec]) }
125
+
126
+ it "updates ancestry in dependent specifications" do
127
+ base = Specification.new
128
+ spec.bases = [base]
129
+
130
+ expect(dependent_spec.ancestors).to eq [dependent_spec, spec, base]
131
+ end
132
+
133
+ it "updates ancestry in dependent specs if base change in ancestor" do
134
+ base1 = Specification.new
135
+ base2 = Specification.new([base1])
136
+ spec.bases = [base2]
137
+
138
+ base2.bases = []
139
+
140
+ expect(spec.ancestors).to eq [spec, base2]
141
+ end
142
+
143
+ it "makes old bases to not be extended by old dependent" do
144
+ old_base = Specification.new
145
+ new_base = Specification.new
146
+ spec.bases = [old_base]
147
+
148
+ spec.bases = [new_base]
149
+
150
+ expect(spec).to_not be_extends(old_base)
151
+ end
152
+
153
+ it "can leave no base" do
154
+ base = Specification.new
155
+ spec.bases = [base]
156
+
157
+ spec.bases = []
158
+
159
+ expect(spec.ancestors).to eq [spec]
160
+ end
161
+
162
+ it "can add bases" do
163
+ base1 = Specification.new
164
+ base2 = Specification.new
165
+ spec.bases = [base1]
166
+
167
+ spec.bases = [base1, base2]
168
+
169
+ expect(spec.ancestors).to eq [spec, base1, base2]
170
+ end
171
+
172
+ it "leaves other dependents untouched" do
173
+ base = Specification.new
174
+ spec.bases = [base]
175
+ other_spec = Specification.new([base])
176
+
177
+ spec.bases = []
178
+ expect(other_spec.ancestors).to include(base)
179
+ end
180
+
181
+ it "cannot modify ancestors through modifying returned bases directly" do
182
+ base = Specification.new
183
+ spec.bases << base
184
+
185
+ expect(spec.ancestors).to eq [spec]
186
+ end
187
+ end
188
+
189
+ describe "#interfaces" do
190
+ it "is empty by default" do
191
+ expect(Specification.new.interfaces).to be_empty
192
+ end
193
+
194
+ it "is empty when dealing only with pure specifications" do
195
+ base = Specification.new
196
+ spec = Specification.new([base])
197
+ expect(spec.interfaces).to be_empty
198
+ end
199
+
200
+ it "takes interfaces from bases through #each_interface" do
201
+ interface1 = stub(:i1)
202
+ interface2 = stub(:i2)
203
+
204
+ base1 = Specification.new
205
+ base1.stub(:each_interface).and_yield(interface1)
206
+
207
+ base2 = Specification.new
208
+ base2.stub(:each_interface).and_yield(interface2)
209
+
210
+ spec = Specification.new([base1, base2])
211
+
212
+ expect(spec.interfaces).to eq [interface1, interface2]
213
+ end
214
+
215
+ it "avoids duplications" do
216
+ interface = stub(:i)
217
+
218
+ base1 = Specification.new
219
+ base1.stub(:each_interface).and_yield(interface)
220
+
221
+ base2 = Specification.new
222
+ base2.stub(:each_interface).and_yield(interface)
223
+
224
+ spec = Specification.new([base1, base2])
225
+
226
+ expect(spec.interfaces).to eq [interface]
227
+ end
228
+
229
+ it "accumulates interfaces from bases' bases" do
230
+ interface1 = stub(:i1)
231
+ interface2 = stub(:i2)
232
+
233
+ base1 = Specification.new
234
+ base1.stub(:each_interface).and_yield(interface1)
235
+
236
+ base2 = Specification.new
237
+ base2.stub(:each_interface).and_yield(interface2)
238
+
239
+ base3 = Specification.new([base2])
240
+ spec = Specification.new([base1, base3])
241
+
242
+ expect(spec.interfaces).to eq [interface1, interface2]
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,206 @@
1
+ require 'spec_helper'
2
+ require 'accord/subscription_registry'
3
+
4
+ module Accord
5
+ describe SubscriptionRegistry do
6
+ subject(:registry) { SubscriptionRegistry.new }
7
+
8
+ let(:subscriber) { Proc.new {} }
9
+ let(:other_subscriber) { Proc.new {} }
10
+
11
+ let(:i1) { stub_interface }
12
+ let(:i2) { stub_interface }
13
+
14
+ def stub_interface(*bases)
15
+ stub.tap do |interface|
16
+ interface.stub(
17
+ :iro => [interface],
18
+ :ancestors => [interface]
19
+ )
20
+ interface.stub(:extends?).and_return(false)
21
+ interface.stub(:extends?).with(interface).and_return(true)
22
+ bases.each do |base|
23
+ interface.iro << base
24
+ interface.ancestors << base
25
+ interface.stub(:extends?).with(base).and_return(true)
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#all" do
31
+ it "returns no adapter when registry is empty" do
32
+ expect(subject.all).to be_empty
33
+ end
34
+
35
+ context "subscribed most simple single subscriber" do
36
+ it "returns that if no arguments are passed" do
37
+ subject.subscribe([Interface], Interface, &subscriber)
38
+ expect(subject.all).to eq [subscriber]
39
+ end
40
+ end
41
+
42
+ it "hits right subscriber if matches" do
43
+ subject.subscribe([i1], nil, &subscriber)
44
+ expect(
45
+ subject.all(required: [i1], provided: nil)
46
+ ).to eq [subscriber]
47
+ end
48
+
49
+ it "miss subscriber if none matches" do
50
+ subject.subscribe([i1], nil, &subscriber)
51
+ expect(
52
+ subject.all(required: [i2], provided: nil)
53
+ ).to be_empty
54
+ end
55
+ end
56
+
57
+ describe "#subscribe" do
58
+ it "complains if subscription happens without block" do
59
+ expect {
60
+ subject.subscribe([Interface], Interface, '')
61
+ }.to raise_error(ArgumentError)
62
+ end
63
+
64
+ it "allows subscribing a null subscriber" do
65
+ subject.subscribe([], i1, &subscriber)
66
+ expect(subject.all(required: [], provided: i1)).to eq [subscriber]
67
+ end
68
+
69
+ it "subscribes multiple subscribers" do
70
+ subject.subscribe([nil], nil, &subscriber)
71
+ subject.subscribe([nil], nil, &other_subscriber)
72
+ expect(
73
+ subject.all(required: [Interface], provided: Interface)
74
+ ).to eq [subscriber, other_subscriber]
75
+ end
76
+
77
+ context "when using [nil] and nil for required and provided" do
78
+ it "defaults to most simple single subscriber" do
79
+ subject.subscribe([nil], nil, &subscriber)
80
+ expect(
81
+ subject.all(required: [Interface], provided: Interface)
82
+ ).to eq [subscriber]
83
+ end
84
+ end
85
+
86
+ context "when using nil and nil for required and provided" do
87
+ it "defaults to most simple single subscriber" do
88
+ subject.subscribe(nil, nil, &subscriber)
89
+ expect(
90
+ subject.all(required: [Interface], provided: Interface)
91
+ ).to eq [subscriber]
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "#lookup" do
97
+ it "returns no subscriber when registry is empty" do
98
+ expect(subject.lookup([], nil)).to be_empty
99
+ end
100
+
101
+ context "registered most simple single subscriber" do
102
+ before do
103
+ subject.subscribe([Interface], Interface, &subscriber)
104
+ end
105
+
106
+ it "returns that if required is nil and provided is nil" do
107
+ expect(subject.lookup(nil, nil)).to eq [subscriber]
108
+ end
109
+
110
+ it "returns that if required is [nil] and provided is nil" do
111
+ expect(subject.lookup([nil], nil)).to eq [subscriber]
112
+ end
113
+ end
114
+
115
+ it "hits right subscriber if matches exactly" do
116
+ subject.subscribe([i1], nil, &subscriber)
117
+ expect(subject.lookup([i1], nil)).to eq [subscriber]
118
+ end
119
+
120
+ it "hits right subscriber if required extends some registered" do
121
+ i12 = stub_interface(i1)
122
+ subject.subscribe([i1], nil, &subscriber)
123
+ expect(subject.lookup([i12], nil)).to eq [subscriber]
124
+ end
125
+
126
+ it "miss subscriber if none matches" do
127
+ subject.subscribe([i1], nil, &subscriber)
128
+ expect(subject.lookup([i2], nil)).to be_empty
129
+ end
130
+ end
131
+
132
+ describe "#unsubscribe" do
133
+ it "doesn't complain when unsubscribing empty registry" do
134
+ expect { subject.unsubscribe([nil], nil) }.to_not raise_error
135
+ end
136
+
137
+ it "keeps subscribed if required doesn't match on unsubscription" do
138
+ subject.subscribe([i1], nil, &subscriber)
139
+ subject.unsubscribe([i2], nil)
140
+ expect(
141
+ subject.all(required: [i1], provided: nil)
142
+ ).to eq [subscriber]
143
+ end
144
+
145
+ it "keeps subscribed if provided doesn't match on unsubscription" do
146
+ subject.subscribe([nil], i1, &subscriber)
147
+ subject.unsubscribe([nil], i2)
148
+ expect(
149
+ subject.all(required: [nil], provided: i1)
150
+ ).to eq [subscriber]
151
+ end
152
+
153
+ it "keeps subscribed if value is passed but doesn't match on "\
154
+ "unsubscription" do
155
+ subject.subscribe([i1], nil, &subscriber)
156
+ subject.unsubscribe([i1], nil, stub)
157
+ expect(
158
+ subject.all(required: [i1], provided: nil)
159
+ ).to eq [subscriber]
160
+ end
161
+
162
+ it "unsubscribes only subscribers matching the value passed" do
163
+ subject.subscribe([i1], nil, &subscriber)
164
+ subject.subscribe([i1], nil, &other_subscriber)
165
+ subject.unsubscribe([i1], nil, subscriber)
166
+ expect(
167
+ subject.all(required: [i1], provided: nil)
168
+ ).to eq [other_subscriber]
169
+ end
170
+
171
+ it "unsubscribes if value not passed and everything else matches "\
172
+ "previous subscription" do
173
+ subject.subscribe([i1], nil, &subscriber)
174
+ subject.unsubscribe([i1], nil)
175
+ expect(
176
+ subject.all(required: [i1], provided: nil)
177
+ ).to be_empty
178
+ end
179
+
180
+ it "unsubscribes all if value not passed" do
181
+ subject.subscribe([i1], nil, &subscriber)
182
+ subject.subscribe([i1], nil, &other_subscriber)
183
+ subject.unsubscribe([i1], nil)
184
+ expect(
185
+ subject.all(required: [i1], provided: nil, name: '')
186
+ ).to be_empty
187
+ end
188
+
189
+ context "when using [nil] and nil for required and provided" do
190
+ it "defaults to most simple single adapter" do
191
+ subject.subscribe([Interface], Interface, &subscriber)
192
+ subject.unsubscribe([nil], nil)
193
+ expect(subject.all).to be_empty
194
+ end
195
+ end
196
+
197
+ context "when using nil and nil for required and provided" do
198
+ it "defaults to most simple single adapter" do
199
+ subject.subscribe([Interface], Interface, &subscriber)
200
+ subject.unsubscribe(nil, nil)
201
+ expect(subject.all).to be_empty
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end