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,157 @@
1
+ require 'spec_helper'
2
+ require 'accord/interface_body'
3
+
4
+ module Accord
5
+ describe InterfaceBody do
6
+ let(:interface) { stub }
7
+ let(:bases) { [] }
8
+ let(:members) { stub }
9
+ let(:invariants) { stub }
10
+ let(:sig_info) { stub(:param => nil, :block => nil, :splat => nil) }
11
+
12
+ subject { InterfaceBody.new(interface, bases, members, invariants) }
13
+
14
+ before do
15
+ stub_const("Accord::SignatureInfo", stub)
16
+ stub_const("Accord::InterfaceMethod", stub)
17
+ Accord::SignatureInfo.stub(:new).and_return(sig_info)
18
+ end
19
+
20
+ describe "#extends" do
21
+ let(:base1) { stub }
22
+ let(:base2) { stub }
23
+
24
+ it "adds up interfaces" do
25
+ subject.extends(base1)
26
+ expect(bases).to include(base1)
27
+ end
28
+
29
+ it "doesn't add a already added base" do
30
+ subject.extends(base1)
31
+ subject.extends(base1)
32
+ expect(bases.size).to eq 1
33
+ end
34
+
35
+ it "prepends the added bases" do
36
+ subject.extends(base1)
37
+ subject.extends(base2)
38
+
39
+ expect(bases.shift).to be base2
40
+ expect(bases.shift).to be base1
41
+ end
42
+ end
43
+
44
+ describe "#responds_to" do
45
+ let(:method) { stub }
46
+
47
+ before do
48
+ members.stub(:add)
49
+ InterfaceMethod.stub(:new).with(interface, :method, sig_info).
50
+ and_return(method)
51
+ end
52
+
53
+ it "adds up methods" do
54
+ members.should_receive(:add).with(:method, method)
55
+ subject.responds_to(:method)
56
+ end
57
+
58
+ context "using :params option" do
59
+ context "to add a regular argument" do
60
+ it "creates a param in the signature info" do
61
+ sig_info.should_receive(:param).with(:foo)
62
+ subject.responds_to(:method, params: :foo)
63
+ end
64
+ end
65
+
66
+ context "to add a splat argument" do
67
+ it "creates a splat in the signature info" do
68
+ sig_info.should_receive(:splat).with(:foo)
69
+ subject.responds_to(:method, params: :"*foo")
70
+ end
71
+ end
72
+
73
+ context "to add a block argument" do
74
+ it "creates a block argument in the signature info" do
75
+ sig_info.should_receive(:block).with(:foo)
76
+ subject.responds_to(:method, params: :"&foo")
77
+ end
78
+ end
79
+ end
80
+
81
+ context "using block" do
82
+ context "to add a regular argument" do
83
+ it "creates a param in the signature info" do
84
+ sig_info.should_receive(:param).with(:foo)
85
+ subject.responds_to(:method) { param :foo }
86
+ end
87
+ end
88
+
89
+ context "to add a splat argument" do
90
+ it "creates a splat in the signature info" do
91
+ sig_info.should_receive(:splat).with(:foo)
92
+ subject.responds_to(:method) { splat :foo }
93
+ end
94
+ end
95
+
96
+ context "to add a block argument" do
97
+ it "creates a block argument in the signature info" do
98
+ sig_info.should_receive(:block).with(:foo)
99
+ subject.responds_to(:method) { block :foo }
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "#invariant" do
106
+ it "adds up invariants" do
107
+ block = Proc.new { }
108
+ invariants.should_receive(:add).with(:invariant, &block)
109
+ subject.invariant(:invariant, &block)
110
+ end
111
+ end
112
+
113
+ describe "#interface" do
114
+ it "exposes the interface object" do
115
+ expect(subject.interface).to be interface
116
+ end
117
+ end
118
+
119
+ describe "#tags" do
120
+ let(:tags) { stub }
121
+
122
+ it "expose interface tags" do
123
+ interface.stub(:tags).and_return(tags)
124
+ expect(subject.tags).to be tags
125
+ end
126
+ end
127
+
128
+ describe ".run" do
129
+ subject { InterfaceBody }
130
+ let(:root_interface) { stub }
131
+
132
+ before do
133
+ stub_const('Accord::Interface', root_interface)
134
+ interface.stub(:bases=)
135
+ interface.stub(:members)
136
+ interface.stub(:invariants)
137
+ end
138
+
139
+ it "adds the root interface if bases is left empty" do
140
+ interface.should_receive(:bases=).with([root_interface])
141
+ subject.run(interface) { }
142
+ end
143
+
144
+ it "doesn't add root interface if bases has at least one interface" do
145
+ stub_const('Accord::TestInterface', stub)
146
+ interface.should_receive(:bases=).with([Accord::TestInterface])
147
+ subject.run(interface) { extends Accord::TestInterface }
148
+ end
149
+
150
+ it "runs the block in an instance" do
151
+ instance = nil
152
+ subject.run(interface) { instance = self }
153
+ expect(instance).to be_a InterfaceBody
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'accord/interface_members'
3
+
4
+ module Accord
5
+ describe InterfaceMembers do
6
+ let(:interface) { stub }
7
+ let(:member) { stub }
8
+
9
+ subject { InterfaceMembers.new(interface) }
10
+
11
+ it "doesn't have any member added by default" do
12
+ expect(subject[:m]).to be_nil
13
+ end
14
+
15
+ describe "#add" do
16
+ it "adds the member" do
17
+ subject.add(:m, member)
18
+ expect(subject[:m]).to be member
19
+ end
20
+ end
21
+
22
+ describe "#names" do
23
+ it "return added names as symbols" do
24
+ subject.add(:m, member)
25
+ expect(subject.names).to eq [:m]
26
+ end
27
+ end
28
+
29
+ describe "#added?" do
30
+ it "returns true if the method was added" do
31
+ subject.add(:m, member)
32
+ expect(subject).to be_added(:m)
33
+ end
34
+
35
+ it "returns false if the method was not added" do
36
+ expect(subject).to_not be_added(:m)
37
+ end
38
+ end
39
+
40
+ describe "#each" do
41
+ it "iterates over all members" do
42
+ names = []
43
+ members = []
44
+
45
+ m, n = stub, stub
46
+
47
+ subject.add(:m, m)
48
+ subject.add(:n, n)
49
+
50
+ subject.each { |name, member| names << name; members << member }
51
+
52
+ expect(names).to eq [:m, :n]
53
+ expect(members).to eq [m, n]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+ require 'accord/interface'
3
+
4
+ module Accord
5
+ describe InterfaceClass do
6
+ before { Accord.clear_adapter_hooks }
7
+
8
+ specify "interfaces are just a kind of specification" do
9
+ expect(InterfaceClass.new(:I)).to be_a(Specification)
10
+ end
11
+
12
+ it "returns self as its single interface" do
13
+ interface = InterfaceClass.new(:I)
14
+ expect(interface.interfaces).to eq [interface]
15
+ end
16
+
17
+ it "returns ancestor specs in #ancestors" do
18
+ spec = Specification.new
19
+ interface = InterfaceClass.new(:I, [spec])
20
+
21
+ expect(interface.ancestors).to eq [interface, spec]
22
+ end
23
+
24
+ it "returns only ancestor interfaces in #iro" do
25
+ spec = Specification.new
26
+ interface = InterfaceClass.new(:I, [spec])
27
+
28
+ expect(interface.iro).to eq [interface]
29
+ end
30
+
31
+ describe "#provided_by?" do
32
+ let(:object) { stub }
33
+ let(:interface) { InterfaceClass.new(:I) }
34
+
35
+ let(:provided_by_declaration) { stub(:extends? => false) }
36
+ let(:declarations_module) { stub }
37
+
38
+ before do
39
+ stub_const('Accord::Declarations', declarations_module)
40
+ declarations_module.stub(:provided_by).with(object).
41
+ and_return(provided_by_declaration)
42
+ end
43
+
44
+ it "returns false if the object is not told to provide the interface" do
45
+ expect(interface).to_not be_provided_by(object)
46
+ end
47
+
48
+ it "returns true if the object is told to provide the interface" do
49
+ provided_by_declaration.stub(:extends?).with(interface).
50
+ and_return(true)
51
+ expect(interface).to be_provided_by(object)
52
+ end
53
+ end
54
+
55
+ describe "#implemented_by?" do
56
+ let(:factory) { stub }
57
+ let(:interface) { InterfaceClass.new(:I) }
58
+
59
+ let(:implemented_by_declaration) { stub(:extends? => false) }
60
+ let(:declarations_module) { stub }
61
+
62
+ before do
63
+ stub_const('Accord::Declarations', declarations_module)
64
+ declarations_module.stub(:implemented_by).with(factory).
65
+ and_return(implemented_by_declaration)
66
+ end
67
+
68
+ it "returns false if the factory is not told to implement the interface" do
69
+ expect(interface).to_not be_implemented_by(factory)
70
+ end
71
+
72
+ it "returns true if the factory is told to implement the interface" do
73
+ implemented_by_declaration.stub(:extends?).with(interface).
74
+ and_return(true)
75
+ expect(interface).to be_implemented_by(factory)
76
+ end
77
+ end
78
+
79
+ describe "#adapt" do
80
+ let(:interface) { InterfaceClass.new(:I) }
81
+ let(:object) { stub }
82
+ let(:adapter) { stub }
83
+ let(:hook_miss) { Proc.new { |iface, *obs| nil } }
84
+ let(:hook_hit) { Proc.new { |iface, *obs| adapter } }
85
+
86
+ it "returns nil if no hook is installed" do
87
+ expect(interface.adapt(object)).to be_nil
88
+ end
89
+
90
+ it "returns nil if all hooks fail" do
91
+ Accord.install_adapter_hook(hook_miss)
92
+ Accord.install_adapter_hook(hook_miss)
93
+ Accord.install_adapter_hook(hook_miss)
94
+
95
+ expect(interface.adapt(object)).to be_nil
96
+ end
97
+
98
+ it "returns the result of the first hook which succeeds" do
99
+ hook_hit2 = Proc.new { |iface, *obs| 'other adapter' }
100
+
101
+ Accord.install_adapter_hook(hook_miss)
102
+ Accord.install_adapter_hook(hook_hit)
103
+ Accord.install_adapter_hook(hook_miss)
104
+ Accord.install_adapter_hook(hook_hit2)
105
+ Accord.install_adapter_hook(hook_miss)
106
+
107
+ expect(interface.adapt(object)).to be adapter
108
+ end
109
+
110
+ context "when the hook actually cares about what is being adapted" do
111
+ let(:hook_hit) { stub }
112
+
113
+ before do
114
+ hook_hit.stub(:call).with(interface, object, object).
115
+ and_return(adapter)
116
+ Accord.install_adapter_hook(hook_hit)
117
+ end
118
+
119
+ subject { interface.adapt(object, object) }
120
+ it { should be adapter }
121
+ end
122
+ end
123
+
124
+ describe "#adapt!" do
125
+ let(:interface) { InterfaceClass.new(:I) }
126
+ let(:object) { stub }
127
+ let(:adapter) { stub }
128
+ let(:hook_miss) { Proc.new { |iface, *obs| nil } }
129
+ let(:hook_hit) { Proc.new { |iface, *obs| adapter } }
130
+
131
+ it "raises TypeError if there's no hook installed" do
132
+ expect { interface.adapt!(object) }.to raise_error(TypeError)
133
+ end
134
+
135
+ it "raises TypeError if no hook provided a suitable adapter" do
136
+ Accord.install_adapter_hook(Proc.new { |iface, *obs| nil })
137
+
138
+ expect { interface.adapt!(object) }.to raise_error(TypeError)
139
+ end
140
+
141
+ it "returns the adapter if one hook succeeded" do
142
+ Accord.install_adapter_hook(Proc.new { |iface, *obs| adapter })
143
+ expect(interface.adapt!(object)).to be adapter
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+ require 'accord/nested_key_hash'
3
+
4
+ module Accord
5
+ describe NestedKeyHash do
6
+ subject { NestedKeyHash.new }
7
+
8
+ let(:k1) { 'key 1' }
9
+ let(:k2) { 'key 2' }
10
+ let(:k3) { 'key 3' }
11
+
12
+ let(:key) { [k1, k2, k3] }
13
+
14
+ specify "empty hash returns nil" do
15
+ expect(subject[key]).to be_nil
16
+ end
17
+
18
+ it "adds a value using a composed key" do
19
+ value = stub
20
+ subject[key] = value
21
+ expect(subject[key]).to be value
22
+ end
23
+
24
+ specify "different keys store different values" do
25
+ value1 = stub
26
+ value2 = stub
27
+ subject[[k1, k2, k3]] = value1
28
+ subject[[k3, k2, k1]] = value2
29
+
30
+ expect(subject[[k1, k2, k3]]).to be value1
31
+ expect(subject[[k3, k2, k1]]).to be value2
32
+ end
33
+
34
+ it "overwrites a value when using the same key" do
35
+ old_value = stub
36
+ new_value = stub
37
+ subject[key] = old_value
38
+ subject[key] = new_value
39
+ expect(subject[key]).to be new_value
40
+ end
41
+
42
+ specify "keys not prefixing other already in use returns nil" do
43
+ subject[key] = 'a value'
44
+ expect(subject[[k2, k1, k3]]).to be_nil
45
+ end
46
+
47
+ specify "prefix keys returns hash containing the remaining keys nested" do
48
+ value = stub
49
+ subject[key] = value
50
+ expect(subject[[k1]]).to eq({ k2 => { k3 => value } })
51
+ end
52
+
53
+ it "deletes a key" do
54
+ subject[key] = 'a value'
55
+ subject.delete(key)
56
+ expect(subject[key]).to be_nil
57
+ end
58
+
59
+ specify "deleting returns the deleted value" do
60
+ value = stub
61
+ subject[key] = value
62
+ result = subject.delete(key)
63
+ expect(result).to be value
64
+ end
65
+
66
+ specify "deleting a key removes intermediate empty hashes" do
67
+ subject[key] = 'a value'
68
+ subject.delete(key)
69
+ expect(subject[[k1]]).to be_nil
70
+ expect(subject[[k1, k2]]).to be_nil
71
+ end
72
+
73
+ specify "deleting a key keeps intermediate non-empty hashes" do
74
+ value, another_value, k4, k5 = stub, stub, 'key 4', 'key 5'
75
+ subject[[k1, k2, k3, k4]] = value
76
+ subject[[k1, k2, k4, k5]] = another_value
77
+ subject.delete([k1, k2, k3, k4])
78
+
79
+ expect(subject[[k1, k2, k4, k5]]).to be another_value
80
+ end
81
+
82
+ describe "key lookup with expansion" do
83
+
84
+ let(:k11) { stub }
85
+ let(:k12) { stub }
86
+ let(:k13) { stub }
87
+ let(:k21) { stub }
88
+ let(:k22) { stub }
89
+ let(:k23) { stub }
90
+
91
+ let(:expansions) do
92
+ {
93
+ k1 => [k11, k12, k13],
94
+ k2 => [k21, k22, k23]
95
+ }
96
+ end
97
+
98
+ describe "#detect_expansion" do
99
+ it "hits the value with valid expansion" do
100
+ subject[[k11, k22]] = 'value'
101
+ result = subject.detect_expansion([k1, k2]) do |k|
102
+ expansions[k]
103
+ end
104
+ expect(result).to eq 'value'
105
+ end
106
+
107
+ it "misses the value with invalid expansion" do
108
+ subject[[k11, k12]] = 'value'
109
+ result = subject.detect_expansion([k1, k2]) do |k|
110
+ expansions[k]
111
+ end
112
+ expect(result).to be_nil
113
+ end
114
+ end
115
+
116
+ describe "#select_expansions" do
117
+ it "hits the values matching some valid expansion" do
118
+ subject[[k11, k22]] = 'value'
119
+ subject[[k12, k21]] = 'other value'
120
+
121
+ result = subject.select_expansions([k1, k2]) do |k|
122
+ expansions[k]
123
+ end
124
+ expect(result).to eq ['value', 'other value']
125
+ end
126
+
127
+ it "misses if no valid expansion is found" do
128
+ subject[[k11, k12]] = 'value'
129
+ subject[[k11, k13]] = 'other value'
130
+
131
+ result = subject.select_expansions([k1, k2]) do |k|
132
+ expansions[k]
133
+ end
134
+
135
+ expect(result).to be_empty
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end