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.
- data/.gitignore +7 -0
- data/.rvmrc +1 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +43 -0
- data/accord.gemspec +20 -0
- data/lib/accord.rb +4 -0
- data/lib/accord/adapter_registry.rb +106 -0
- data/lib/accord/base_registry.rb +73 -0
- data/lib/accord/declarations.rb +131 -0
- data/lib/accord/exceptions.rb +22 -0
- data/lib/accord/extendor.rb +36 -0
- data/lib/accord/extendor_container.rb +43 -0
- data/lib/accord/interface.rb +189 -0
- data/lib/accord/interface_body.rb +73 -0
- data/lib/accord/interface_members.rb +31 -0
- data/lib/accord/interface_method.rb +27 -0
- data/lib/accord/interfaces.rb +471 -0
- data/lib/accord/nested_key_hash.rb +66 -0
- data/lib/accord/ro.rb +65 -0
- data/lib/accord/signature_info.rb +76 -0
- data/lib/accord/specification.rb +83 -0
- data/lib/accord/subscription_registry.rb +76 -0
- data/lib/accord/tags.rb +25 -0
- data/lib/accord/version.rb +3 -0
- data/spec/adapter_registry_spec.rb +296 -0
- data/spec/declarations_spec.rb +144 -0
- data/spec/extendor_container_spec.rb +101 -0
- data/spec/extendor_spec.rb +203 -0
- data/spec/integration/adaptation_spec.rb +86 -0
- data/spec/integration/adapter_for_class_declaration_spec.rb +22 -0
- data/spec/integration/adapter_hook_spec.rb +41 -0
- data/spec/integration/default_adapters_spec.rb +81 -0
- data/spec/integration/hash_adapters_spec.rb +20 -0
- data/spec/integration/interface_declaration_spec.rb +258 -0
- data/spec/integration/multi_adapters_spec.rb +83 -0
- data/spec/integration/named_adapters_spec.rb +54 -0
- data/spec/integration/single_adapters_spec.rb +93 -0
- data/spec/integration/subscriptions_spec.rb +245 -0
- data/spec/integration/verification_spec.rb +215 -0
- data/spec/interface_body_spec.rb +157 -0
- data/spec/interface_members_spec.rb +57 -0
- data/spec/interface_spec.rb +147 -0
- data/spec/nested_key_hash_spec.rb +140 -0
- data/spec/signature_info_spec.rb +65 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/specification_spec.rb +246 -0
- data/spec/subscription_registry_spec.rb +206 -0
- data/spec/tags_spec.rb +38 -0
- 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
|