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