accord 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,144 @@
1
+ require 'spec_helper'
2
+ require 'accord/declarations'
3
+ require 'accord/interface'
4
+
5
+ module Accord
6
+ describe Declarations do
7
+ let(:i1) { InterfaceClass.new(:I1, [Interface]) }
8
+ let(:cls) { Class.new }
9
+
10
+ describe "class declarations" do
11
+ let(:mod) { Module.new }
12
+
13
+ specify "are empty by default" do
14
+ expect(Declarations.implemented_by(cls).interfaces).to be_empty
15
+ end
16
+
17
+ describe "implemented" do
18
+ it "can be modified to implement a specific interface" do
19
+ Declarations.implements(cls, i1)
20
+ expect(Declarations.implemented_by(cls)).to be_extend(i1)
21
+ end
22
+
23
+ it "can inherit interfaces from superclasses" do
24
+ Declarations.implements(cls, i1)
25
+ subclass = Class.new(cls)
26
+ expect(Declarations.implemented_by(subclass)).to be_extend(i1)
27
+ end
28
+
29
+ it "can inherit interfaces from included modules" do
30
+ Declarations.implements(mod, i1)
31
+ cls.send(:include, mod)
32
+ expect(Declarations.implemented_by(cls)).to be_extend(i1)
33
+ end
34
+
35
+ it "makes the class to implement the interface" do
36
+ Declarations.implements(cls, i1)
37
+ expect(i1).to be_implemented_by(cls)
38
+ end
39
+
40
+ it "makes instance to provide the interface" do
41
+ Declarations.implements(cls, i1)
42
+ expect(i1).to be_provided_by(cls.new)
43
+ end
44
+ end
45
+
46
+ describe "implemented only" do
47
+ let(:i2) { InterfaceClass.new(:i2, [Interface]) }
48
+
49
+ before do
50
+ Declarations.implements(cls, i1)
51
+ end
52
+
53
+ it "can be modified to implement a specific interface only" do
54
+ Declarations.implements_only(cls, i2)
55
+ expect(Declarations.implemented_by(cls)).to be_extend(i2)
56
+ expect(Declarations.implemented_by(cls)).to_not be_extend(i1)
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "proc declarations" do
62
+ let(:prc) { Proc.new {} }
63
+
64
+ specify "are empty by default" do
65
+ expect(Declarations.implemented_by(prc).interfaces).to be_empty
66
+ end
67
+
68
+ describe "implemented" do
69
+ it "can be modified to implement a specific interface" do
70
+ Declarations.implements(prc, i1)
71
+ expect(Declarations.implemented_by(prc)).to be_extend(i1)
72
+ end
73
+ end
74
+
75
+ describe "implemented only" do
76
+ let(:i2) { InterfaceClass.new(:i2, [Interface]) }
77
+
78
+ before do
79
+ Declarations.implements(prc, i1)
80
+ end
81
+
82
+ it "can be modified to implement a specific interface only" do
83
+ Declarations.implements_only(prc, i2)
84
+ expect(Declarations.implemented_by(prc)).to be_extend(i2)
85
+ expect(Declarations.implemented_by(prc)).to_not be_extend(i1)
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "object declarations" do
91
+ let(:i2) { InterfaceClass.new(:i2, [Interface]) }
92
+ let(:obj) { cls.new }
93
+
94
+ specify "are empty by default" do
95
+ expect(Declarations.provided_by(obj).interfaces).to be_empty
96
+ end
97
+
98
+ describe ".also_provides" do
99
+ it "makes the object to provide an interface" do
100
+ Declarations.also_provides(obj, i1)
101
+ expect(i1).to be_provided_by(obj)
102
+ end
103
+
104
+ it "adds interfaces to the object" do
105
+ Declarations.also_provides(obj, i1)
106
+ Declarations.also_provides(obj, i2)
107
+ expect(i2).to be_provided_by(obj)
108
+ end
109
+ end
110
+
111
+ describe ".directly_provides" do
112
+ it "makes an object to provide only the given interfaces" do
113
+ Declarations.also_provides(obj, i2)
114
+ Declarations.directly_provides(obj, i1)
115
+ expect(i1).to be_provided_by(obj)
116
+ expect(i2).to_not be_provided_by(obj)
117
+ end
118
+
119
+ it "doesn't affect interfaces provided from the object's class" do
120
+ Declarations.implements(cls, i1)
121
+ Declarations.directly_provides(obj, i2)
122
+ expect(i1).to be_provided_by(obj)
123
+ expect(i2).to be_provided_by(obj)
124
+ end
125
+ end
126
+
127
+ describe ".no_longer_provides" do
128
+ it "makes the object to not provide a specific interface" do
129
+ Declarations.also_provides(obj, i1)
130
+ Declarations.also_provides(obj, i2)
131
+ Declarations.no_longer_provides(obj, i1)
132
+ expect(i1).to_not be_provided_by(obj)
133
+ expect(i2).to be_provided_by(obj)
134
+ end
135
+
136
+ it "doesn't affect interfaces provided from the object's class" do
137
+ Declarations.implements(cls, i1)
138
+ Declarations.no_longer_provides(obj, i1)
139
+ expect(i1).to be_provided_by(obj)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+ require 'accord/extendor_container'
3
+
4
+ module Accord
5
+ describe ExtendorContainer do
6
+ let(:extendor) { stub }
7
+ let(:i1) { stub }
8
+ let(:i2) { stub }
9
+ let(:provided) { stub }
10
+
11
+ let(:extendor_class) do
12
+ Class.new do
13
+ attr_reader :current
14
+ def initialize
15
+ @current = []
16
+ end
17
+ def add(interface)
18
+ current << interface
19
+ end
20
+ def delete(interface)
21
+ current.delete(interface)
22
+ end
23
+ def empty?
24
+ current.empty?
25
+ end
26
+ end
27
+ end
28
+
29
+ subject { ExtendorContainer.new }
30
+
31
+ before do
32
+ provided.stub(:iro => [provided, i1, i2])
33
+ stub_const('Accord::Extendor', extendor_class)
34
+ end
35
+
36
+ it "starts clean" do
37
+ expect(subject.get(provided).current).to be_empty
38
+ end
39
+
40
+ it "reports when a given interface is not set" do
41
+ expect(subject.has?(provided)).to be_false
42
+ end
43
+
44
+ it "adds an interface for each extendor of its resolution order" do
45
+ subject.add(provided)
46
+ expect(subject.get(provided).current).to include(provided)
47
+ expect(subject.get(i1).current).to include(provided)
48
+ expect(subject.get(i2).current).to include(provided)
49
+ end
50
+
51
+ it "reports when a given interface is set" do
52
+ subject.add(provided)
53
+ expect(subject.has?(provided)).to be_true
54
+ expect(subject.has?(i1)).to be_true
55
+ expect(subject.has?(i2)).to be_true
56
+ end
57
+
58
+ it "accumulates interfaces while adding" do
59
+ other = stub
60
+ other.stub(:iro => [other, i1, i2])
61
+
62
+ subject.add(provided)
63
+ subject.add(other)
64
+
65
+ expect(subject.get(provided).current).to eq [provided]
66
+ expect(subject.get(other).current).to eq [other]
67
+
68
+ expect(subject.get(i1).current).to include(provided)
69
+ expect(subject.get(i1).current).to include(other)
70
+
71
+ expect(subject.get(i2).current).to include(provided)
72
+ expect(subject.get(i2).current).to include(other)
73
+ end
74
+
75
+ it "deletes interfaces" do
76
+ other = stub
77
+ other.stub(:iro => [other, i1, i2])
78
+
79
+ subject.add(provided)
80
+ subject.add(other)
81
+ subject.delete(provided)
82
+
83
+ expect(subject.get(provided).current).to eq []
84
+ expect(subject.get(other).current).to eq [other]
85
+
86
+ expect(subject.get(i1).current).to_not include(provided)
87
+ expect(subject.get(i1).current).to include(other)
88
+
89
+ expect(subject.get(i2).current).to_not include(provided)
90
+ expect(subject.get(i2).current).to include(other)
91
+ end
92
+
93
+ it "reports when an interface has been deleted" do
94
+ subject.add(provided)
95
+ subject.delete(provided)
96
+ expect(subject.has?(provided)).to be_false
97
+ expect(subject.has?(i1)).to be_false
98
+ expect(subject.has?(i2)).to be_false
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,203 @@
1
+ require 'spec_helper'
2
+ require 'accord/extendor'
3
+
4
+ module Accord
5
+ describe Extendor do
6
+ subject { Extendor.new }
7
+
8
+ it { should be_empty }
9
+ its(:current) { should be_empty }
10
+
11
+ specify "adding interface to extendor" do
12
+ interface = stub
13
+ subject.add(interface)
14
+ expect(subject.current).to eq [interface]
15
+ end
16
+
17
+ specify "adding interface makes it not empty" do
18
+ subject.add(stub)
19
+ expect(subject).to_not be_empty
20
+ expect(subject.current).to_not be_empty
21
+ end
22
+
23
+ specify "adds an interface only once" do
24
+ interface = stub
25
+ subject.add(interface)
26
+ subject.add(interface)
27
+ expect(subject.current).to eq [interface]
28
+ end
29
+
30
+ describe "adding in details" do
31
+ let(:interface1) { stub }
32
+ let(:interface2) { stub(:extends? => true) }
33
+ let(:extending) { stub }
34
+
35
+ before do
36
+ subject.add(interface1)
37
+ subject.add(interface2)
38
+ end
39
+
40
+ context "when added interface extends some" do
41
+ before do
42
+ extending.stub(:extends?).with(interface1).and_return(true)
43
+ extending.stub(:extends?).with(interface2).and_return(false)
44
+ subject.add(extending)
45
+ end
46
+
47
+ it "adds it after the ones it extends" do
48
+ expect(subject.current).to eq [interface1, extending, interface2]
49
+ end
50
+ end
51
+
52
+ context "when added interface extends none" do
53
+ before do
54
+ extending.stub(:extends?).with(interface1).and_return(false)
55
+ extending.stub(:extends?).with(interface2).and_return(false)
56
+ subject.add(extending)
57
+ end
58
+
59
+ it "adds it in the beginning" do
60
+ expect(subject.current).to eq [extending, interface1, interface2]
61
+ end
62
+ end
63
+
64
+ context "when added interface extends all" do
65
+ before do
66
+ extending.stub(:extends?).with(interface1).and_return(true)
67
+ extending.stub(:extends?).with(interface2).and_return(true)
68
+ subject.add(extending)
69
+ end
70
+
71
+ it "adds it in the end" do
72
+ expect(subject.current).to eq [interface1, interface2, extending]
73
+ end
74
+ end
75
+ end
76
+
77
+ specify "modifying current doesn't alter state of extendor" do
78
+ subject.current << stub
79
+ expect(subject.current).to be_empty
80
+
81
+ interface = stub
82
+ subject.add(interface)
83
+ subject.current << stub
84
+ expect(subject.current.size).to eq 1
85
+
86
+ subject.current.delete(interface)
87
+ expect(subject.current.size).to eq 1
88
+ end
89
+
90
+ specify "deleting is done using #delete" do
91
+ interface = stub
92
+ subject.add(interface)
93
+ subject.delete(interface)
94
+ expect(subject).to be_empty
95
+ end
96
+
97
+ specify "deleting interface on empty extendor" do
98
+ subject.delete(stub)
99
+ expect(subject).to be_empty
100
+ end
101
+
102
+ specify "deleting doesn't break if deleting already deleted interface" do
103
+ interface1 = stub
104
+ interface2 = stub(:extends? => false)
105
+ subject.add(interface1)
106
+ subject.add(interface2)
107
+
108
+ subject.delete(interface1)
109
+ subject.delete(interface1)
110
+ subject.delete(interface1)
111
+
112
+ expect(subject.current).to eq [interface2]
113
+ end
114
+
115
+ describe "#compact_map" do
116
+ it "maps the current interfaces to the given block" do
117
+ interface1 = stub
118
+ interface2 = stub(:extends? => false)
119
+ subject.add(interface1)
120
+ subject.add(interface2)
121
+
122
+ result = subject.compact_map { |i| i.object_id }
123
+
124
+ expect(result).to include(interface1.object_id)
125
+ expect(result).to include(interface2.object_id)
126
+ end
127
+
128
+ it "discards nil entries when mapping" do
129
+ interface1 = stub
130
+ interface2 = stub(:extends? => false)
131
+ subject.add(interface1)
132
+ subject.add(interface2)
133
+
134
+ result = subject.compact_map { |i| i.equal?(interface1) ? nil : i }
135
+
136
+ expect(result).to_not include(interface1)
137
+ expect(result).to include(interface2)
138
+ end
139
+
140
+ it "maps in the order they were left" do
141
+ interface1 = stub
142
+ interface2 = stub
143
+ interface3 = stub
144
+
145
+ interface2.stub(:extends?).with(interface1).and_return(false)
146
+ interface3.stub(:extends?).with(interface1).and_return(true)
147
+ interface3.stub(:extends?).with(interface2).and_return(false)
148
+
149
+ subject.add(interface1)
150
+ subject.add(interface2)
151
+ subject.add(interface3)
152
+
153
+ interfaces = subject.compact_map { |i| i }
154
+
155
+ expect(interfaces).to eq [interface1, interface3, interface2]
156
+ end
157
+ end
158
+
159
+ describe "#flat_map" do
160
+ it "maps the current interfaces to the given block" do
161
+ interface1 = stub
162
+ interface2 = stub(:extends? => false)
163
+ subject.add(interface1)
164
+ subject.add(interface2)
165
+
166
+ result = subject.flat_map { |i| i.object_id }
167
+
168
+ expect(result).to include(interface1.object_id)
169
+ expect(result).to include(interface2.object_id)
170
+ end
171
+
172
+ it "expands arrays returned by block" do
173
+ interface1 = stub
174
+ interface2 = stub(:extends? => false)
175
+ subject.add(interface1)
176
+ subject.add(interface2)
177
+
178
+ result = subject.flat_map { |i| [i] }
179
+
180
+ expect(result).to include(interface1)
181
+ expect(result).to include(interface2)
182
+ end
183
+
184
+ it "maps in the order they were left" do
185
+ interface1 = stub
186
+ interface2 = stub
187
+ interface3 = stub
188
+
189
+ interface2.stub(:extends?).with(interface1).and_return(false)
190
+ interface3.stub(:extends?).with(interface1).and_return(true)
191
+ interface3.stub(:extends?).with(interface2).and_return(false)
192
+
193
+ subject.add(interface1)
194
+ subject.add(interface2)
195
+ subject.add(interface3)
196
+
197
+ interfaces = subject.flat_map { |i| i }
198
+
199
+ expect(interfaces).to eq [interface1, interface3, interface2]
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require 'accord'
3
+
4
+ describe "Adaptation" do
5
+ let(:registry) { Accord::AdapterRegistry.new }
6
+ let(:ip1) { Accord::InterfaceClass.new(:ip1, [Accord::Interface]) }
7
+
8
+ let(:yclass) { Class.new do
9
+ Accord::Declarations.implements(self, IP1)
10
+ attr_reader :context
11
+ def initialize(context)
12
+ @context = context
13
+ end
14
+ end }
15
+
16
+ before do
17
+ stub_const('IP1', ip1)
18
+ end
19
+
20
+ describe "getting an adapter" do
21
+ let(:ir) { Accord::InterfaceClass.new(:ir, [Accord::Interface]) }
22
+ let(:xclass) { Class.new { Accord::Declarations.implements(self, IR) } }
23
+ let(:x) { xclass.new }
24
+
25
+ before do
26
+ stub_const('IR', ir)
27
+ end
28
+
29
+ context "unamed" do
30
+ subject { registry.get([x], IP1) }
31
+
32
+ before do
33
+ registry.register([IR], IP1, '') { |o| yclass.new(o) }
34
+ end
35
+
36
+ it { should be_a(yclass) }
37
+ its(:context) { should be x }
38
+ end
39
+
40
+ context "named" do
41
+ let(:y2class) { Class.new(yclass) }
42
+ subject { registry.get([x], IP1, 'bob') }
43
+
44
+ before do
45
+ registry.register([IR], IP1, 'bob') { |o| y2class.new(o) }
46
+ end
47
+
48
+ it { should be_a(y2class) }
49
+ its(:context) { should be x }
50
+ end
51
+
52
+ describe "factory with embedded condition" do
53
+ let(:factory) { Proc.new { |o| o.name == 'object' ? 'adapter' : nil } }
54
+ let(:oclass) { Class.new do
55
+ Accord::Declarations.implements(self, IR)
56
+ attr_writer :name
57
+ def name
58
+ @name ||= 'object'
59
+ end
60
+ end }
61
+ let(:obj) { oclass.new }
62
+
63
+ before do
64
+ registry.register([IR], IP1, 'conditional', &factory)
65
+ end
66
+
67
+ context "when the adapter satisfies the condition" do
68
+ subject { registry.get([obj], IP1, 'conditional') }
69
+ it { should eq 'adapter' }
70
+ end
71
+
72
+ context "when the adapter doesn't satisfy the condition" do
73
+ subject { registry.get([obj], IP1, 'conditional') }
74
+ before { obj.name = 'other' }
75
+ it { should be_nil }
76
+
77
+ context "when a default value is provided" do
78
+ subject do
79
+ registry.get([obj], IP1, 'conditional', default: 'default')
80
+ end
81
+ it { should eq 'default' }
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end