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,43 @@
1
+ require 'accord/extendor'
2
+
3
+ module Accord
4
+ class ExtendorContainer
5
+ def get(interface)
6
+ hash[interface] || Extendor.new
7
+ end
8
+
9
+ def add(provided)
10
+ provided.iro.each do |interface|
11
+ extendor = get(interface)
12
+ extendor.add(provided)
13
+ set(interface, extendor)
14
+ end
15
+ end
16
+
17
+ def delete(provided)
18
+ provided.iro.each do |interface|
19
+ extendor = get(interface)
20
+ extendor.delete(provided)
21
+ set(interface, extendor)
22
+ end
23
+ end
24
+
25
+ def has?(interface)
26
+ hash.has_key?(interface)
27
+ end
28
+
29
+ private
30
+
31
+ def set(interface, extendor)
32
+ if extendor.empty?
33
+ hash.delete(interface)
34
+ else
35
+ hash[interface] = extendor
36
+ end
37
+ end
38
+
39
+ def hash
40
+ @hash ||= {}
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,189 @@
1
+ require 'set'
2
+ require 'accord/exceptions'
3
+ require 'accord/specification'
4
+ require 'accord/declarations'
5
+ require 'accord/tags'
6
+ require 'accord/signature_info'
7
+ require 'accord/interface_body'
8
+ require 'accord/interface_members'
9
+
10
+ module Accord
11
+ class << self
12
+ def install_adapter_hook(hook)
13
+ adapter_hooks << hook
14
+ end
15
+
16
+ def clear_adapter_hooks
17
+ adapter_hooks.clear
18
+ end
19
+
20
+ def adapter_hooks
21
+ @adapter_hooks ||= []
22
+ end
23
+
24
+ def Interface(*args, &block)
25
+ first_arg = args.shift
26
+ if first_arg.is_a?(String) || first_arg.is_a?(Symbol)
27
+ const_name = nil
28
+ top_level = nil
29
+ name = first_arg.to_s
30
+ else
31
+ const_name = args.shift.to_s
32
+ top_level = first_arg
33
+ name = top_level.name + '::' + const_name
34
+ end
35
+
36
+ interface = InterfaceClass.new(name, [Interface])
37
+
38
+ InterfaceBody.run(interface, &block)
39
+
40
+ top_level.const_set(const_name, interface) if const_name
41
+ interface
42
+ end
43
+ end
44
+
45
+ class InterfaceInvariants
46
+ def initialize(interface)
47
+ @interface = interface
48
+ @invariants = {}
49
+ end
50
+
51
+ def add(invariant_name, &block)
52
+ @invariants[invariant_name.to_sym] = block
53
+ end
54
+
55
+ def run(object, errors)
56
+ @invariants.each do |invariant_name, block|
57
+ block.call(object, errors)
58
+ end
59
+ end
60
+ end
61
+
62
+ class InterfaceClass < Accord::Specification
63
+ attr_accessor :doc
64
+
65
+ def initialize(name, bases=[])
66
+ super(bases)
67
+ @name = name.to_s
68
+ end
69
+
70
+ def name
71
+ @name
72
+ end
73
+
74
+ def each_interface
75
+ yield(self)
76
+ end
77
+
78
+ def iro
79
+ @iro ||= ancestors.select { |spec| spec.is_a?(InterfaceClass) }
80
+ end
81
+
82
+ def inspect
83
+ "<Interface #{name}>"
84
+ end
85
+
86
+ def adapt(*objects)
87
+ Accord.adapter_hooks.each do |hook|
88
+ result = hook.call(self, *objects)
89
+ return result if result
90
+ end
91
+ nil
92
+ end
93
+
94
+ def adapt!(*objects)
95
+ adapt(*objects).tap do |result|
96
+ raise TypeError, "could not adapt: #{objects.inspect}" if result.nil?
97
+ end
98
+ end
99
+
100
+ def provided_by?(object)
101
+ Accord::Declarations.provided_by(object).extends?(self)
102
+ end
103
+
104
+ def implemented_by?(cls_or_mod)
105
+ Accord::Declarations.implemented_by(cls_or_mod).extends?(self)
106
+ end
107
+
108
+ def members
109
+ @members ||= InterfaceMembers.new(self)
110
+ end
111
+
112
+ def invariants
113
+ @invariants ||= InterfaceInvariants.new(self)
114
+ end
115
+
116
+ def member_names
117
+ iro.reverse.each_with_object([]) do |i, names|
118
+ i.members.names.each do |name|
119
+ names << name unless names.include?(name)
120
+ end
121
+ end
122
+ end
123
+
124
+ def own_member_names
125
+ members.names
126
+ end
127
+
128
+ def [](name)
129
+ owner = iro.detect { |i| i.owns?(name) }
130
+ owner.members[name] if owner
131
+ end
132
+
133
+ def defined?(name)
134
+ iro.any? { |i| i.owns?(name) }
135
+ end
136
+
137
+ def owns?(name)
138
+ members.added?(name)
139
+ end
140
+
141
+ def each
142
+ member_names.each do |name|
143
+ yield(name, self[name])
144
+ end
145
+ end
146
+
147
+ def assert_invariants?(object, errors=nil)
148
+ errors ||= []
149
+ (iro - [self]).each { |i| i.assert_invariants?(object, errors) }
150
+ invariants.run(object, errors)
151
+ errors.empty?
152
+ end
153
+
154
+ def assert_invariants(object, errors=nil)
155
+ raise Invalid unless assert_invariants?(object, errors)
156
+ end
157
+
158
+ def tags
159
+ @tags ||= Tags.new
160
+ end
161
+
162
+ def verify_object(object)
163
+ raise DoesNotImplement.new(self) unless provided_by?(object)
164
+
165
+ each do |name, member|
166
+ raise BrokenImplementation.new(self, name) unless \
167
+ member.compatible_with_object?(object)
168
+ end
169
+ end
170
+
171
+ def verify_module(mod)
172
+ raise DoesNotImplement.new(self) unless implemented_by?(mod)
173
+
174
+ each do |name, member|
175
+ raise BrokenImplementation.new(self, name) unless \
176
+ member.compatible_with_module?(mod)
177
+ end
178
+ end
179
+
180
+ protected
181
+
182
+ def changed(originally_changed)
183
+ @iro = nil
184
+ super
185
+ end
186
+ end
187
+
188
+ Interface = InterfaceClass.new(:'Accord::Interface')
189
+ end
@@ -0,0 +1,73 @@
1
+ require 'accord/interface_method'
2
+
3
+ module Accord
4
+ class InterfaceBody
5
+ attr_reader :interface
6
+
7
+ def initialize(interface, bases, members, invariants)
8
+ @interface = interface
9
+ @bases = bases
10
+ @members = members
11
+ @invariants = invariants
12
+ end
13
+
14
+ def extends(*new_bases)
15
+ new_bases.each do |base|
16
+ @bases.unshift(base) unless @bases.include?(base)
17
+ end
18
+ end
19
+
20
+ def responds_to(name, options={}, &block)
21
+ method = self.class.make_method(interface, name, options, block)
22
+ @members.add(name, method)
23
+ end
24
+
25
+ def invariant(invariant_name, &block)
26
+ @invariants.add(invariant_name, &block)
27
+ end
28
+
29
+ def tags
30
+ interface.tags
31
+ end
32
+
33
+ def self.run(interface, &block)
34
+ bases = []
35
+ members = interface.members
36
+ invariants = interface.invariants
37
+
38
+ body = new(interface, bases, members, invariants)
39
+ body.instance_exec(&block) if block
40
+
41
+ bases.unshift(Interface) if bases.empty?
42
+ interface.bases = bases
43
+
44
+ interface
45
+ end
46
+
47
+ def self.make_method(interface, name, options, block)
48
+ arguments = SignatureInfo.new
49
+ if args = options[:params]
50
+ raise ArgumentError,
51
+ "no block is expected when using option `:params`." if block
52
+ args = [args] unless args.is_a?(Array)
53
+ args.each do |arg|
54
+ if arg.is_a?(Symbol)
55
+ if arg.to_s.start_with?('&')
56
+ arguments.block(arg.to_s.gsub(/^&/, '').to_sym)
57
+ elsif arg.to_s.start_with?('*')
58
+ arguments.splat(arg.to_s.gsub(/^\*/, '').to_sym)
59
+ else
60
+ arguments.param(arg)
61
+ end
62
+ else
63
+ arguments.param(arg)
64
+ end
65
+ end
66
+ elsif block
67
+ arguments.instance_exec(&block)
68
+ end
69
+
70
+ InterfaceMethod.new(interface, name, arguments)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ module Accord
2
+ class InterfaceMembers
3
+ def initialize(interface)
4
+ @interface = interface
5
+ @hash = {}
6
+ @order = []
7
+ end
8
+
9
+ def add(member_name, member)
10
+ member_name = member_name.to_sym
11
+ @order << member_name unless @order.include?(member_name)
12
+ @hash[member_name] = member
13
+ end
14
+
15
+ def names
16
+ @order.dup
17
+ end
18
+
19
+ def [](name)
20
+ @hash[name.to_sym]
21
+ end
22
+
23
+ def added?(name)
24
+ names.include?(name)
25
+ end
26
+
27
+ def each
28
+ @order.each { |name| yield(name, @hash[name]) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require 'accord/tags'
2
+
3
+ module Accord
4
+ class InterfaceMethod
5
+ attr_reader :name, :interface, :signature_info
6
+
7
+ def initialize(interface, name, signature_info)
8
+ @interface = interface
9
+ @name = name.to_sym
10
+ @signature_info = signature_info
11
+ end
12
+
13
+ def tags
14
+ @tags ||= Tags.new
15
+ end
16
+
17
+ def compatible_with_object?(object)
18
+ return false unless object.respond_to?(name)
19
+ signature_info.match?(object.method(name).parameters)
20
+ end
21
+
22
+ def compatible_with_module?(mod)
23
+ return false unless mod.instance_methods.include?(name)
24
+ signature_info.match?(mod.instance_method(name).parameters)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,471 @@
1
+ require 'accord/interface'
2
+
3
+ module Accord
4
+ module Interfaces
5
+ # A hash-like container for tags.
6
+ Accord::Interface(self, :Tags) do
7
+ # Return the value associated with a tag.
8
+ # @param tag [String, Symbol] the tag.
9
+ # @return [Object] the value associated with the tag.
10
+ responds_to :[], params: :tag
11
+
12
+ # Associates a tag with a value.
13
+ # @param tag [String, Symbol] the tag.
14
+ # @param value [Object] any value.
15
+ # @return [void]
16
+ responds_to :[]=, params: [:tag, :value]
17
+
18
+ # Return the value associated with `tags`.
19
+ # @param tag [String, Symbol] the tag to look for.
20
+ # @param default [Object] any default value that should be returned when
21
+ # the tag is not found.
22
+ # @raise ArgumentError if the default value is not provided and the tag
23
+ # is not found.
24
+ responds_to :fetch do
25
+ param :tag
26
+ param default: Accord::UNSPECIFIED
27
+ end
28
+ end
29
+
30
+ Declarations.implements(Accord::Tags, Tags)
31
+
32
+ # Objects that have a name and tags.
33
+ Accord::Interface(self, :Element) do
34
+ # The object name.
35
+ # @return [String, Symbol] the name of the object.
36
+ responds_to :name
37
+
38
+ # Element tags.
39
+ # @return [Tags] tags associated with this element.
40
+ responds_to :tags
41
+ end
42
+
43
+ # The signature details of a method.
44
+ Accord::Interface(self, :SignatureInfo) do
45
+ # Arguments.
46
+ # @return [<Hash>] sequence of hashes with a `:name` key which is the
47
+ # proposed name of the argument, an optional `:default` key which is
48
+ # the default value of the argument, if any, a `:splat` key which is
49
+ # true if the argument is a splat (false/nil/unavailable otherwise).
50
+ # The arguments are listed in the order they are expected by
51
+ # implementations.
52
+ responds_to :arguments
53
+
54
+ # Block argument name.
55
+ # @return [Symbol] the name of the block argument or nil if none is
56
+ # expected.
57
+ responds_to :block
58
+ end
59
+
60
+ Declarations.implements(Accord::SignatureInfo, SignatureInfo)
61
+
62
+ # An interface member.
63
+ Accord::Interface(self, :Member) do
64
+ extends Element
65
+
66
+ # The member name.
67
+ # @return [Symbol] the name of the member.
68
+ responds_to :name
69
+
70
+ # The interface on which this member is defined.
71
+ # @return [Interface]
72
+ responds_to :interface
73
+
74
+ # Tests if the object is compatible with this member.
75
+ # @param object [Object] the object to be tested.
76
+ # @return [Boolean] true if the object is compatible, false otherwise.
77
+ responds_to :compatible_with_object?, params: :object
78
+
79
+ # Tests if the module is compatible with this member.
80
+ # @param mod [Module] the module to be tested.
81
+ # @return [Boolean] true if the module is compatible, false otherwise.
82
+ responds_to :compatible_with_module?, params: :mod
83
+ end
84
+
85
+ # A specialized interface member which represents a method.
86
+ Accord::Interface(self, :Method) do
87
+ extends Member
88
+
89
+ # Signature information.
90
+ # @return [SignatureInfo] signature info of the method.
91
+ responds_to :signature_info
92
+ end
93
+
94
+ Declarations.implements(Accord::InterfaceMethod, Method)
95
+
96
+ Accord::Interface(self, :Specification) do
97
+ # @return [<Specification>] the list of specifications from which this
98
+ # specification is directly derived.
99
+ responds_to :bases
100
+
101
+ # @param bases [<Specification>] a list of specifications from which this
102
+ # specification is directly derived.
103
+ # @return [void]
104
+ responds_to :bases=, params: :bases
105
+
106
+ # @return [<Specification>] the list of specifications from which this
107
+ # specification is derived, including self, from most specific to least
108
+ # specific (similar to ancestors of a Ruby module).
109
+ responds_to :ancestors
110
+
111
+ # Test whether this specification extends another.
112
+ #
113
+ # The specification extends other if it has other as a base or if one of
114
+ # its bases extends other.
115
+ #
116
+ # A specification always extends itself.
117
+ #
118
+ # @param other [Specification] the other specification.
119
+ # @return [Boolean] if this specification extends other.
120
+ responds_to :extends?, params: :other
121
+ end
122
+
123
+ Declarations.implements(Accord::Specification, Specification)
124
+
125
+ Accord::Interface(self, :Interface) do
126
+ extends Element, Specification
127
+
128
+ # Interface resolution order
129
+ # @return [<Interface>] the sequence of ancestors which are interfaces.
130
+ responds_to :iro
131
+
132
+ # All interface member names.
133
+ # @return [<Symbol>] the sequence of member names of the interface in the
134
+ # order they were first defined.
135
+ # @note All ancestors are verified.
136
+ responds_to :member_names
137
+
138
+ # Interface member names.
139
+ # @return [<Symbol>] the sequence of member names of the interface in the
140
+ # order they were defined.
141
+ # @note Return only members defined in the interface.
142
+ responds_to :own_member_names
143
+
144
+ # Get the member object of a name.
145
+ # @param name [String, Symbol] the name of the member.
146
+ # @return [Member] the member or nil if the member doesn't exists.
147
+ # @note All ancestors are verified.
148
+ responds_to :[], params: :name
149
+
150
+ # Test whether the name is defined in the interface.
151
+ # @param name [String, Symbol] the name of the member.
152
+ # @return [Boolean] true if the member is defined.
153
+ # @note All ancestors are verified.
154
+ responds_to :defined?, params: :name
155
+
156
+ # Test whether the name is defined in the interface.
157
+ # @param name [String, Symbol] the name of the member.
158
+ # @return [Boolean] true if the member is defined in the interface.
159
+ # @note Ancestors are ignored.
160
+ responds_to :owns?, params: :name
161
+
162
+ # Iterate over all members defined by this interface and its ancestors in
163
+ # the order they where defined.
164
+ # @yield [name, Member] the name of the member as a symbol and the member
165
+ # object.
166
+ # @return [void]
167
+ responds_to :each
168
+
169
+ # Validate invariants.
170
+ # @param object [Object] the object to validate.
171
+ # @param errors [<Object>] any container where the errors will be pushed
172
+ # to, defaults to nil (no container to push errors to).
173
+ # @return [Boolean] true if no invariant was violated, false otherwise.
174
+ # @note All invariants of all ancestors are checked against the object.
175
+ responds_to :assert_invariants?, params: [:object, { errors: nil }]
176
+
177
+ # Validate invariants.
178
+ # @param (see #assert_invariants?)
179
+ # @raise Exception if any invariant has been violated, but pushes all
180
+ # errors to the errors container before raising.
181
+ # @note All invariants of all ancestors are checked against the object.
182
+ responds_to :assert_invariants, params: [:object, { errors: nil }]
183
+
184
+ # Test if the object claims to provide this interface.
185
+ #
186
+ # This is done by checking if this interface or any of its extensions was
187
+ # previously declared as directly provided by the object or as
188
+ # implemented by its class. No further tests are done.
189
+ #
190
+ # @param object [Object] the object to test.
191
+ # @return [Boolean] true if the interface is provided by the object.
192
+ responds_to :provided_by?, params: :object
193
+
194
+ # Test if the interface is claimed to be implemented by the given
195
+ # factory.
196
+
197
+ # This is done by checking if this interface or any of its extensions was
198
+ # previously declared as implemented by the factory. No further tests
199
+ # are done.
200
+ #
201
+ # @param factory [Class, Module, Proc] the factory to test.
202
+ # @return [Boolean] true if the interface is implemented by the factory.
203
+ responds_to :implemented_by?, params: :factory
204
+
205
+ # Test if an object might provide this interface.
206
+ #
207
+ # This is done checking the object agains all members defined in this
208
+ # interface, including the correct signature for each method. The
209
+ # signature may not have exactly the same argument names, but must match
210
+ # arity and accept block accordingly.
211
+ #
212
+ # @return [true] if the object passes all tests.
213
+ # @raise BrokenImplementation if the object doesn't provide some member
214
+ # or provide it with the wrong signature.
215
+ # @raise DoesNotImplement if the object fails #provided_by?.
216
+ responds_to :verify_object, params: :object
217
+
218
+ # Test if a class or module might implement this interface.
219
+ #
220
+ # This is done checking the class or module methods agains all members
221
+ # defined in this interface, including the correct signature for each
222
+ # method. The signature may not have exactly the same argument names,
223
+ # but must match arity and accept block accordingly.
224
+ #
225
+ # @return [true] if the class/module passes all tests.
226
+ # @raise BrokenImplementation if the class/module doesn't implements some
227
+ # member or implements it with the wrong signature.
228
+ # @raise DoesNotImplement if the class/module fails #implemented_by?.
229
+ responds_to :verify_module, params: :mod
230
+ end
231
+
232
+ Declarations.implements(Accord::InterfaceClass, Interface)
233
+
234
+ Accord::Interface(self, :Declaration) do
235
+ extends Specification
236
+
237
+ # Tests whether the given interface is in the specification.
238
+ # @param interface [Interface] the interface.
239
+ # @return [Boolean] true if the given interface is one of the interfaces
240
+ # in the specification or false otherwise.
241
+ responds_to :extends?, params: :interface
242
+
243
+ # Create a new declaration with the interfaces from self and other.
244
+ # @param other [Specification] the other specification.
245
+ # @return [Declaration] a new declaration.
246
+ responds_to :+, params: :other
247
+
248
+ # Create a new declaration with interfaces that don't extend any of the
249
+ # other.
250
+ # @param other [Specification] the other specification.
251
+ # @return [Declaration] a new declaration.
252
+ responds_to :-, params: :other
253
+ end
254
+
255
+ Declarations.implements(Accord::Declarations::Declaration, Declaration)
256
+
257
+ Accord::Interface(self, :InterfaceDeclarations) do
258
+ # Get the provided interfaces of an object
259
+ # @param object [Object] the target object.
260
+ # @return [Declaration] the provided interfaces.
261
+ responds_to :provided_by, params: :object
262
+
263
+ # Get the directly provided interfaces of an object, that is, those that
264
+ # are provided by the object independently of its class.
265
+ # @param object [Object] the target object.
266
+ # @return [Declaration] the provided interfaces.
267
+ responds_to :directly_provided_by, params: :object
268
+
269
+ # Get the interfaces implemented by a factory.
270
+ # @param factory [Module, Proc] the target factory.
271
+ # @return [Declaration] the implemented interfaces.
272
+ responds_to :implemented_by, params: :factory
273
+
274
+ # Declare interfaces implemented by a factory.
275
+ # @param factory [Module, Proc] the target factory.
276
+ # @param *interfaces [Interface] the interfaces of the declaration.
277
+ # @return [void]
278
+ responds_to :implements, params: [:factory, :"*interfaces"]
279
+
280
+ # Declare the only interfaces implemented by a factory.
281
+ # @param factory [Module, Proc] the target factory.
282
+ # @param *interfaces [Interface] the interfaces of the declaration.
283
+ # @return [void]
284
+ responds_to :implements_only, params: [:factory, :"*interfaces"]
285
+
286
+ # Declare the only interfaces directly provided by an object.
287
+ # @param object [Object] the target object.
288
+ # @param *interfaces [Interface] the interfaces of the declaration.
289
+ # @return [void]
290
+ responds_to :directly_provides, params: [:object, :"*interfaces"]
291
+
292
+ # Declare additional interfaces provided by an object.
293
+ # @param object [Object] the target object.
294
+ # @param *interfaces [Interface] the interfaces of the declaration.
295
+ # @return [void]
296
+ responds_to :also_provides, params: [:object, :"*interfaces"]
297
+
298
+ # Remove an interface provided by an object.
299
+ # @param object [Object] the target object.
300
+ # @param interface [Interface] the interface to be removed.
301
+ # @return [void]
302
+ responds_to :no_longer_provides, params: [:object, :interface]
303
+ end
304
+
305
+ Declarations.also_provides(Accord::Declarations, InterfaceDeclarations)
306
+
307
+ Accord::Interface(self, :AdapterRegistry) do
308
+ responds_to :register do
309
+ param :required
310
+ param :provided
311
+ param name: ''
312
+ block :value
313
+ end
314
+
315
+ responds_to :unregister do
316
+ param :required
317
+ param :provided
318
+ param :name
319
+ param value: nil
320
+ end
321
+
322
+ responds_to :first, params: { options: {} }
323
+ responds_to :all, params: { options: {} }
324
+
325
+ responds_to :lookup, params: [:required, :provided, :"*args"]
326
+ responds_to :lookup_all, params: [:required, :provided]
327
+
328
+ responds_to :get, params: [:required, :provided, :"*args"]
329
+ end
330
+
331
+ Declarations.implements(Accord::AdapterRegistry, AdapterRegistry)
332
+
333
+ Accord::Interface(self, :SubscriptionRegistry) do
334
+ responds_to :subscribe do
335
+ param :required
336
+ param :provided
337
+ block :value
338
+ end
339
+
340
+ responds_to :unsubscribe do
341
+ param :required
342
+ param :provided
343
+ param value: nil
344
+ end
345
+
346
+ responds_to :all, params: { options: {} }
347
+
348
+ responds_to :lookup, params: [:required, :provided]
349
+ responds_to :get, params: [:required, :provided]
350
+ responds_to :call, params: [:required, :provided]
351
+ end
352
+
353
+ Declarations.implements(Accord::SubscriptionRegistry, SubscriptionRegistry)
354
+
355
+ Accord::Interface(self, :InterfaceBody) do
356
+ # Declares that an object providing the interface should respond to a
357
+ # method.
358
+ #
359
+ # @param message_name [Symbol] the name of the method.
360
+ # @param options [Hash] options hash, defaults to an empty hash.
361
+ # @yield
362
+ # @raise ArgumentError if a `:params` option is passed and also a block.
363
+ #
364
+ # == Detailed description
365
+ #
366
+ # This method can be used in three ways: without options or block, with
367
+ # the `:params` option or with a block which takes no parameters.
368
+ #
369
+ # Usage without any option and without block is straightforward:
370
+ #
371
+ # responds_to :method
372
+ #
373
+ # This declares that objects providing this interface should respond to
374
+ # `.method`.
375
+ #
376
+ # Examples for the `:params` option:
377
+ #
378
+ # responds_to :method, params: :single_argument
379
+ # responds_to :method, params: [:single_argument]
380
+ # responds_to :method, params: { single_with_default: default }
381
+ # responds_to :method, params: [{ single_with_default: default }]
382
+ # responds_to :method, params: [:a1, { a2: default }, :"*args", :"&blk"]
383
+ #
384
+ # Which would be fulfilled in a real implementation with:
385
+ #
386
+ # def method(single_argument); end
387
+ # def method(single_argument=default); end
388
+ # def method(a1, a2=default, *args, &blk); end
389
+ #
390
+ # Using a block the same examples would be declared as the following:
391
+ #
392
+ # responds_to :method do
393
+ # param :single_argument
394
+ # end
395
+ #
396
+ # responds_to :method do
397
+ # param single_argument: default
398
+ # end
399
+ #
400
+ # responds_to :method do
401
+ # param :a1
402
+ # param a2: default
403
+ # splat :args
404
+ # block :blk
405
+ # end
406
+ #
407
+ # In all cases, the implementation should respect the order the arguments
408
+ # where declared, but doesn't need to respect the names of the arguments.
409
+ # The names are described here for clarification, documentation and
410
+ # design intent purposes.
411
+ #
412
+ # @note When a default value exists but is not specified (or is unknown,
413
+ # or is irrelevant), use `Accord::UNSPECIFIED`. This also can be used
414
+ # when the argument can be optionally given or not. Implementations
415
+ # may not rely on this value, though.
416
+ responds_to :responds_to do
417
+ param :message_name
418
+ param options: {}
419
+ block :block
420
+ end
421
+
422
+ # Declare an invariant.
423
+ #
424
+ # @param name [Symbol] the name of the invariant.
425
+ # @yields [object, error] the object on which the invariant must hold and
426
+ # an errors container (which responds to `<<` and `push`).
427
+ #
428
+ # == Example
429
+ #
430
+ # Let's say that a event should not start after end:
431
+ #
432
+ # Account = Accord::Interface(:Event) do
433
+ # respond_to :start_date
434
+ # respond_to :end_date
435
+ # invariant :event_consistency do |object, errors|
436
+ # next unless start = object.start_date
437
+ # next unless end = object.end_date
438
+ # errors.push("cannot start after end") unless start <= end
439
+ # end
440
+ # end
441
+ #
442
+ # Errors don't need to be a string. Actually, they can be any object.
443
+ #
444
+ # Account = Accord::Interface(:Event) do
445
+ # respond_to :start_date
446
+ # respond_to :end_date
447
+ # invariant :event_consistency do |object, errors|
448
+ # next unless start = object.start_date
449
+ # next unless end = object.end_date
450
+ # errors << CausalityViolation.new(start, end) unless start <= end
451
+ # end
452
+ # end
453
+ #
454
+ # In the examples above, every time an object providing `Event` which
455
+ # starts after its end is checked, the invariant is considered violated,
456
+ # since an error is pushed into the `errors` container. If no error is
457
+ # pushed, the invariant succeeds.
458
+ responds_to :invariant, params: :name
459
+
460
+ # Declare that this interface extends another or various.
461
+ # @param interfaces [Interface] one or more interfaces to extend. They
462
+ # are added in the reversed order they are given, so the last one listed
463
+ # is the most specific (that is, will appear first in the bases array and
464
+ # its ancestors will also appear first in the ancestors array). Bases
465
+ # already extended are ignored.
466
+ responds_to(:extends) { splat :interfaces }
467
+ end
468
+
469
+ Declarations.implements(Accord::InterfaceBody, InterfaceBody)
470
+ end
471
+ end