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,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