accord 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|