safer 0.3.1 → 0.4.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/History.txt +14 -0
- data/Manifest.txt +1 -1
- data/{README.txt → README.rdoc} +0 -0
- data/Rakefile +2 -1
- data/lib/safer/hashprotocol.rb +263 -265
- data/lib/safer/ivar.rb +4 -219
- data/lib/safer/protocol.rb +454 -457
- data/lib/safer.rb +1 -1
- data/test/test_safer_hashprotocol.rb +27 -27
- data/test/test_safer_ivar.rb +25 -41
- data/test/test_safer_ivar_run.rb +77 -0
- data/test/test_safer_ivarfactory.rb +36 -0
- data/test/test_safer_protocol.rb +8 -3
- metadata +11 -8
data/lib/safer/protocol.rb
CHANGED
@@ -1,502 +1,499 @@
|
|
1
1
|
require 'safer/ivar'
|
2
2
|
|
3
|
-
|
3
|
+
##
|
4
|
+
# Named set of class- and instance- methods, with associated numbers of
|
5
|
+
# arguments. Call +create_from_class+ or +create_from_instance+ to create a
|
6
|
+
# new +Protocol+ object. Call +class_conforms?+ or +instance_conforms?+
|
7
|
+
# to verify that a Class or instance of the class (respectively) conforms to
|
8
|
+
# a protocol.
|
9
|
+
#
|
10
|
+
# ==Usage
|
11
|
+
# In the example, we define a class with a single instance variable,
|
12
|
+
# initialized from an argument to #initialize. We must verify that the
|
13
|
+
# object corresponding to this argument implements a particular protocol.
|
14
|
+
#
|
15
|
+
# class YourClass
|
16
|
+
# # Define a class or module containing all the class- and instance-
|
17
|
+
# # methods that you require from objects passed into your methods. Here,
|
18
|
+
# # we expect the +Class+ object to implement a +foo+ method that takes
|
19
|
+
# # three arguments, and for instances to implement a +bar+ method that
|
20
|
+
# # takes one required argument, followed by any number of optional
|
21
|
+
# # arguments:
|
22
|
+
# class FooInterface
|
23
|
+
# # Note that we have a good place to document what these interfaces
|
24
|
+
# # mean, in standard RDoc fashion.
|
25
|
+
# def self.foo(arg1, arg2, arg3)
|
26
|
+
# end
|
27
|
+
# def bar(arg1, *rest)
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # Derive a Safer::Protocol object from this class:
|
32
|
+
# FOOPROTOCOL = Safer::Protocol.create_from_class(FooProtocol)
|
33
|
+
# # Note: we could also do this step as follows:
|
34
|
+
# # FOOPROTOCOL = Safer::Protocol.create_from_instance(FooProtocol.new)
|
35
|
+
#
|
36
|
+
# # Our #initialize method takes an object that should conform to
|
37
|
+
# # FOOPROTOCOL.
|
38
|
+
# def initialize(obj)
|
39
|
+
# # verify that 'obj' conforms to FOOPROTOCOL. If it does not, a
|
40
|
+
# # Safer::Protocol::Error::InstanceError exception will be raised.
|
41
|
+
# FOOPROTOCOL.instance_conforms?(obj)
|
42
|
+
# @obj = obj
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# # Now, functions can assume that @obj implements FOOPROTOCOL.
|
46
|
+
# def quux
|
47
|
+
# @obj.class.foo(self, 2, 3)
|
48
|
+
# @obj.bar(@obj)
|
49
|
+
# end
|
50
|
+
# # including client functions.
|
51
|
+
# attr_reader :obj
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# class Foo
|
55
|
+
# # implements YourClass::FOOPROTOCOL
|
56
|
+
# def self.foo(arg1, arg2, arg3)
|
57
|
+
# return arg1 + arg2 + arg3
|
58
|
+
# end
|
59
|
+
# def bar(arg1, *rest)
|
60
|
+
# "#{arg1.class.to_s}:#{rest.inspect}"
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# # should succeed.
|
64
|
+
# a = YourClass.new(Foo.new)
|
65
|
+
# # should raise a Safer::Protocol::Error::InstanceError.
|
66
|
+
# b = YourClass.new(15)
|
67
|
+
#
|
68
|
+
# ==Rationale
|
69
|
+
# It is often the case that we expect objects passed to our methods to
|
70
|
+
# follow some usage protocol. There are two ways this is usually accomplished
|
71
|
+
# in Ruby:
|
72
|
+
#
|
73
|
+
# 1. We can ensure that objects are of a particular type, by calling
|
74
|
+
# Object.kind_of? and related functions, disabling features or signaling
|
75
|
+
# error when the object is of the wrong type. If the object is of a
|
76
|
+
# particular type, then we can reasonably expect that the object will
|
77
|
+
# conform to that type's interface.
|
78
|
+
# 2. We can verify that objects respond to desired interfaces using
|
79
|
+
# Object#respond_to? and related functions, disabling features or
|
80
|
+
# signaling error when these objects do not support certain interfaces.
|
81
|
+
#
|
82
|
+
# The first method cannot always be used, as there exist circumstances in
|
83
|
+
# which the inheritance hierarchy cannot (or should not be used to) predict
|
84
|
+
# if an object can be used in a particular way. (For example, the object
|
85
|
+
# hierarchy does not determine if an object is serializable -- there is no
|
86
|
+
# base class for which all sub-classes are serializable, and only sub-classes
|
87
|
+
# are serializable.) So we must sometimes rely on directly verifying that an
|
88
|
+
# object supports a set of interfaces.
|
89
|
+
#
|
90
|
+
# Safer::Protocol is intended to improve the maintainability of code that
|
91
|
+
# must directly verify that objects implement desired interfaces. A Protocol
|
92
|
+
# object is derived from a class definition that includes the set of methods
|
93
|
+
# for which the Protocol should test, and provides a simple means of testing
|
94
|
+
# whether another class object (or instance objects) implement that set of
|
95
|
+
# methods. If the method set is not fully supported, a report object
|
96
|
+
# (Safer::Protocol::Error) is generated describing the differences between
|
97
|
+
# the object under test and the Protocol's requirements.
|
98
|
+
#
|
99
|
+
# Safer::Protocol is also intended to make it easier to document the
|
100
|
+
# interfaces required from a Protocol-implementing object. Protocol objects
|
101
|
+
# are derived from Class objects, so one can document the interfaces required
|
102
|
+
# by the protocol by documenting the Class definition.
|
103
|
+
class Safer::Protocol
|
4
104
|
##
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# # three arguments, and for instances to implement a +bar+ method that
|
21
|
-
# # takes one required argument, followed by any number of optional
|
22
|
-
# # arguments:
|
23
|
-
# class FooInterface
|
24
|
-
# # Note that we have a good place to document what these interfaces
|
25
|
-
# # mean, in standard RDoc fashion.
|
26
|
-
# def self.foo(arg1, arg2, arg3)
|
27
|
-
# end
|
28
|
-
# def bar(arg1, *rest)
|
29
|
-
# end
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# # Derive a Safer::Protocol object from this class:
|
33
|
-
# FOOPROTOCOL = Safer::Protocol.create_from_class(FooProtocol)
|
34
|
-
# # Note: we could also do this step as follows:
|
35
|
-
# # FOOPROTOCOL = Safer::Protocol.create_from_instance(FooProtocol.new)
|
36
|
-
#
|
37
|
-
# # Our #initialize method takes an object that should conform to
|
38
|
-
# # FOOPROTOCOL.
|
39
|
-
# def initialize(obj)
|
40
|
-
# # verify that 'obj' conforms to FOOPROTOCOL. If it does not, a
|
41
|
-
# # Safer::Protocol::Error::InstanceError exception will be raised.
|
42
|
-
# FOOPROTOCOL.instance_conforms?(obj)
|
43
|
-
# @obj = obj
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# # Now, functions can assume that @obj implements FOOPROTOCOL.
|
47
|
-
# def quux
|
48
|
-
# @obj.class.foo(self, 2, 3)
|
49
|
-
# @obj.bar(@obj)
|
50
|
-
# end
|
51
|
-
# # including client functions.
|
52
|
-
# attr_reader :obj
|
53
|
-
# end
|
54
|
-
#
|
55
|
-
# class Foo
|
56
|
-
# # implements YourClass::FOOPROTOCOL
|
57
|
-
# def self.foo(arg1, arg2, arg3)
|
58
|
-
# return arg1 + arg2 + arg3
|
59
|
-
# end
|
60
|
-
# def bar(arg1, *rest)
|
61
|
-
# "#{arg1.class.to_s}:#{rest.inspect}"
|
62
|
-
# end
|
63
|
-
# end
|
64
|
-
# # should succeed.
|
65
|
-
# a = YourClass.new(Foo.new)
|
66
|
-
# # should raise a Safer::Protocol::Error::InstanceError.
|
67
|
-
# b = YourClass.new(15)
|
68
|
-
#
|
69
|
-
# ==Rationale
|
70
|
-
# It is often the case that we expect objects passed to our methods to
|
71
|
-
# follow some usage protocol. There are two ways this is usually accomplished
|
72
|
-
# in Ruby:
|
73
|
-
#
|
74
|
-
# 1. We can ensure that objects are of a particular type, by calling
|
75
|
-
# Object.kind_of? and related functions, disabling features or signaling
|
76
|
-
# error when the object is of the wrong type. If the object is of a
|
77
|
-
# particular type, then we can reasonably expect that the object will
|
78
|
-
# conform to that type's interface.
|
79
|
-
# 2. We can verify that objects respond to desired interfaces using
|
80
|
-
# Object#respond_to? and related functions, disabling features or
|
81
|
-
# signaling error when these objects do not support certain interfaces.
|
82
|
-
#
|
83
|
-
# The first method cannot always be used, as there exist circumstances in
|
84
|
-
# which the inheritance hierarchy cannot (or should not be used to) predict
|
85
|
-
# if an object can be used in a particular way. (For example, the object
|
86
|
-
# hierarchy does not determine if an object is serializable -- there is no
|
87
|
-
# base class for which all sub-classes are serializable, and only sub-classes
|
88
|
-
# are serializable.) So we must sometimes rely on directly verifying that an
|
89
|
-
# object supports a set of interfaces.
|
90
|
-
#
|
91
|
-
# Safer::Protocol is intended to improve the maintainability of code that
|
92
|
-
# must directly verify that objects implement desired interfaces. A Protocol
|
93
|
-
# object is derived from a class definition that includes the set of methods
|
94
|
-
# for which the Protocol should test, and provides a simple means of testing
|
95
|
-
# whether another class object (or instance objects) implement that set of
|
96
|
-
# methods. If the method set is not fully supported, a report object
|
97
|
-
# (Safer::Protocol::Error) is generated describing the differences between
|
98
|
-
# the object under test and the Protocol's requirements.
|
99
|
-
#
|
100
|
-
# Safer::Protocol is also intended to make it easier to document the
|
101
|
-
# interfaces required from a Protocol-implementing object. Protocol objects
|
102
|
-
# are derived from Class objects, so one can document the interfaces required
|
103
|
-
# by the protocol by documenting the Class definition.
|
104
|
-
class Protocol
|
105
|
-
|
106
|
-
##
|
107
|
-
# Utility function used during Protocol operations. Given an +Array+,
|
108
|
-
# construct a +Hash+ with the values in the +Array+ as keys. If no block
|
109
|
-
# is provided, then the values for all keys in the +Hash+ will be +true+.
|
110
|
-
# If a block is provided, the block is called for each value in the
|
111
|
-
# +Array+, and its return value will be stored as the value for that
|
112
|
-
# element in the +Hash+. If the block returns +nil+, then the element will
|
113
|
-
# not be stored in the resulting +Hash+.
|
114
|
-
def self._array_to_table(array, &block)
|
115
|
-
if ! block
|
116
|
-
block = proc do |h, el| true ; end
|
117
|
-
end
|
118
|
-
array.inject(Hash.new) do |h, el|
|
119
|
-
newval = block.call(h, el)
|
120
|
-
if newval
|
121
|
-
h[el] = newval
|
122
|
-
end
|
123
|
-
h
|
105
|
+
# Utility function used during Protocol operations. Given an +Array+,
|
106
|
+
# construct a +Hash+ with the values in the +Array+ as keys. If no block
|
107
|
+
# is provided, then the values for all keys in the +Hash+ will be +true+.
|
108
|
+
# If a block is provided, the block is called for each value in the
|
109
|
+
# +Array+, and its return value will be stored as the value for that
|
110
|
+
# element in the +Hash+. If the block returns +nil+, then the element will
|
111
|
+
# not be stored in the resulting +Hash+.
|
112
|
+
def self._array_to_table(array, &block)
|
113
|
+
if ! block
|
114
|
+
block = proc do |h, el| true ; end
|
115
|
+
end
|
116
|
+
array.inject(Hash.new) do |h, el|
|
117
|
+
newval = block.call(h, el)
|
118
|
+
if newval
|
119
|
+
h[el] = newval
|
124
120
|
end
|
125
|
-
|
121
|
+
h
|
122
|
+
end
|
123
|
+
end # Safer::Protocol._array_to_table
|
126
124
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
125
|
+
##
|
126
|
+
# Describes a set of methods that should be implemented by an object, but
|
127
|
+
# are not implemented. The Protocol::Error object will contain two
|
128
|
+
# Protocol::Violation objects: one for class-method protocol
|
129
|
+
# violations, and one for instance-method protocol violations.
|
130
|
+
class Violation
|
131
|
+
Safer::IVar.instance_variable(self, :table)
|
132
|
+
Safer::IVar.instance_variable(self, :report)
|
135
133
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
134
|
+
##
|
135
|
+
# :attr_reader: table
|
136
|
+
# Hash describing method violations. Keys are the names of methods
|
137
|
+
# containing errors. If a method was unimplemented in the class, the
|
138
|
+
# value for that method will be +true+. If a method was implemented, but
|
139
|
+
# had a different arity (number of required / optional arguments) than
|
140
|
+
# the protocol, the value for that method will be the arity discovered in
|
141
|
+
# the object. (The desired arity can be accessed through the
|
142
|
+
# Protocol::Signature for that method.)
|
143
|
+
Safer::IVar.export_reader(self, :table)
|
146
144
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
145
|
+
##
|
146
|
+
# :attr_reader: report
|
147
|
+
# String describing the errors from +table+ in a more human-readable
|
148
|
+
# fashion.
|
149
|
+
Safer::IVar.export_reader(self, :report)
|
152
150
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
151
|
+
##
|
152
|
+
# Create a new Protocol::Violation object.
|
153
|
+
def initialize(table, report)
|
154
|
+
self.safer_protocol_violation__table = table
|
155
|
+
self.safer_protocol_violation__report = report
|
156
|
+
end # Safer::Protocol::Violation#initialize
|
157
|
+
##
|
158
|
+
# Compare two Protocol::Violation objects for content equality.
|
159
|
+
def ==(other_object)
|
160
|
+
self.class == other_object.class &&
|
161
|
+
self.safer_protocol_violation__table == other_object.safer_protocol_violation__table
|
162
|
+
end # Safer::Protocol::Violation#==
|
163
|
+
end # Safer::Protocol::Violation
|
166
164
|
|
165
|
+
##
|
166
|
+
# Hash of method_name => method.arity, describing a set of methods that
|
167
|
+
# we expect to see implemented in another class. Protocol will contain
|
168
|
+
# two of these objects - one describing class methods, and one describing
|
169
|
+
# instance methods.
|
170
|
+
class Signature
|
171
|
+
Safer::IVar.instance_variable(self, :table)
|
167
172
|
##
|
168
|
-
#
|
169
|
-
#
|
170
|
-
#
|
171
|
-
|
172
|
-
class Signature
|
173
|
-
Safer::IVar.instance_variable(self, :table)
|
174
|
-
##
|
175
|
-
# :attr_reader: table
|
176
|
-
# +Hash+ object, in which the keys are the names of methods, and the
|
177
|
-
# value for a key is the required arity of that method.
|
178
|
-
Safer::IVar.export_reader(self, :table)
|
173
|
+
# :attr_reader: table
|
174
|
+
# +Hash+ object, in which the keys are the names of methods, and the
|
175
|
+
# value for a key is the required arity of that method.
|
176
|
+
Safer::IVar.export_reader(self, :table)
|
179
177
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
178
|
+
##
|
179
|
+
# Create a Protocol::Signature object.
|
180
|
+
def initialize(table)
|
181
|
+
self.safer_protocol_signature__table = table
|
182
|
+
end # Safer::Protocol::Signature#initialize
|
183
|
+
##
|
184
|
+
# Compare two Protocol::Signature objects for content equality.
|
185
|
+
def ==(other_object)
|
186
|
+
self.class == other_object.class &&
|
187
|
+
self.safer_protocol_signature__table == other_object.safer_protocol_signature__table
|
188
|
+
end # Safer::Protocol::Signature#==
|
191
189
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
have_error = true
|
207
|
-
end
|
208
|
-
rescue NameError => e
|
209
|
-
report += "#{get_method}(#{name}) unimplemented.\n"
|
210
|
-
error_table[name] = true
|
190
|
+
##
|
191
|
+
# Given an object, and the name of the method for getting a named method
|
192
|
+
# from that object, check that the object implements this function
|
193
|
+
# signature.
|
194
|
+
def find_violations(object, get_method)
|
195
|
+
report = ''
|
196
|
+
have_error = false
|
197
|
+
error_table = Hash.new
|
198
|
+
self.safer_protocol_signature__table.each_pair do |name, arity|
|
199
|
+
begin
|
200
|
+
m = object.send(get_method, name)
|
201
|
+
if m.arity != arity
|
202
|
+
report += "#{get_method}(#{name}) arity #{m.arity} != desired #{arity}.\n"
|
203
|
+
error_table[name] = m.arity
|
211
204
|
have_error = true
|
212
205
|
end
|
206
|
+
rescue NameError => e
|
207
|
+
report += "#{get_method}(#{name}) unimplemented.\n"
|
208
|
+
error_table[name] = true
|
209
|
+
have_error = true
|
213
210
|
end
|
211
|
+
end
|
214
212
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
##
|
223
|
-
# Derive a Signature from an object.
|
224
|
-
def self.create(object, get_methods, get_method, &filter)
|
225
|
-
method_names = object.send(get_methods)
|
226
|
-
method_table = Safer::Protocol._array_to_table(method_names) do
|
227
|
-
|h, method_name|
|
228
|
-
if filter.call(method_name)
|
229
|
-
m = object.send(get_method, method_name)
|
230
|
-
m.arity
|
231
|
-
end
|
232
|
-
end
|
233
|
-
self.new(method_table)
|
234
|
-
end # Safer::Protocol::Signature.create
|
235
|
-
end # Safer::Protocol::Signature
|
213
|
+
if have_error
|
214
|
+
Safer::Protocol::Violation.new(error_table, report)
|
215
|
+
else
|
216
|
+
nil
|
217
|
+
end
|
218
|
+
end # Safer::Protocol::Signature#find_violations
|
236
219
|
|
237
220
|
##
|
238
|
-
#
|
239
|
-
|
240
|
-
|
241
|
-
Safer::
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
##
|
247
|
-
# :attr_reader: error_object
|
248
|
-
# Object that does not properly implement a desired method protocol.
|
249
|
-
Safer::IVar.export_reader(self, :error_object)
|
250
|
-
##
|
251
|
-
# :attr_reader: protocol
|
252
|
-
# Protocol object describing the signature that error_object failed
|
253
|
-
# to properly implement.
|
254
|
-
Safer::IVar.export_reader(self, :protocol)
|
255
|
-
##
|
256
|
-
# :attr_reader: class_violations
|
257
|
-
# Protocol::Violation describing class-method protocol violations.
|
258
|
-
Safer::IVar.export_reader(self, :class_violations)
|
259
|
-
##
|
260
|
-
# :attr_reader: instance_violations
|
261
|
-
# Protocol::Violation describing instance-method protocol violations.
|
262
|
-
Safer::IVar.export_reader(self, :instance_violations)
|
263
|
-
##
|
264
|
-
# :attr_reader: report
|
265
|
-
# String for displaying human-readable error message describing protocol
|
266
|
-
# violations.
|
267
|
-
Safer::IVar.export_reader(self, :report)
|
268
|
-
|
269
|
-
|
270
|
-
##
|
271
|
-
# Create a Safer::Protocol::Error object.
|
272
|
-
# Used internally.
|
273
|
-
def initialize(message, error_object, protocol, class_violations, instance_violations)
|
274
|
-
super(message)
|
275
|
-
self.safer_protocol_error__error_object = error_object
|
276
|
-
self.safer_protocol_error__protocol = protocol
|
277
|
-
self.safer_protocol_error__class_violations = class_violations
|
278
|
-
self.safer_protocol_error__instance_violations = instance_violations
|
279
|
-
report = ''
|
280
|
-
if class_violations
|
281
|
-
report +=
|
282
|
-
"Class method errors:\n" +
|
283
|
-
self.safer_protocol_error__class_violations.report
|
284
|
-
if instance_violations
|
285
|
-
report += "\n\n"
|
286
|
-
end
|
221
|
+
# Derive a Signature from an object.
|
222
|
+
def self.create(object, get_methods, get_method, &filter)
|
223
|
+
method_names = object.send(get_methods)
|
224
|
+
method_table = Safer::Protocol._array_to_table(method_names) do
|
225
|
+
|h, method_name|
|
226
|
+
if filter.call(method_name)
|
227
|
+
m = object.send(get_method, method_name)
|
228
|
+
m.arity
|
287
229
|
end
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
end
|
293
|
-
self.safer_protocol_error__report = report
|
294
|
-
end # Safer::Protocol::Error#initialize
|
230
|
+
end
|
231
|
+
self.new(method_table)
|
232
|
+
end # Safer::Protocol::Signature.create
|
233
|
+
end # Safer::Protocol::Signature
|
295
234
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
end # Safer::Protocol::Error#==
|
235
|
+
##
|
236
|
+
# Error generated when a class does not conform to a protocol signature.
|
237
|
+
class Error < StandardError
|
238
|
+
Safer::IVar.instance_variable(self, :error_object)
|
239
|
+
Safer::IVar.instance_variable(self, :protocol)
|
240
|
+
Safer::IVar.instance_variable(self, :class_violations)
|
241
|
+
Safer::IVar.instance_variable(self, :instance_violations)
|
242
|
+
Safer::IVar.instance_variable(self, :report)
|
305
243
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
244
|
+
##
|
245
|
+
# :attr_reader: error_object
|
246
|
+
# Object that does not properly implement a desired method protocol.
|
247
|
+
Safer::IVar.export_reader(self, :error_object)
|
248
|
+
##
|
249
|
+
# :attr_reader: protocol
|
250
|
+
# Protocol object describing the signature that error_object failed
|
251
|
+
# to properly implement.
|
252
|
+
Safer::IVar.export_reader(self, :protocol)
|
253
|
+
##
|
254
|
+
# :attr_reader: class_violations
|
255
|
+
# Protocol::Violation describing class-method protocol violations.
|
256
|
+
Safer::IVar.export_reader(self, :class_violations)
|
257
|
+
##
|
258
|
+
# :attr_reader: instance_violations
|
259
|
+
# Protocol::Violation describing instance-method protocol violations.
|
260
|
+
Safer::IVar.export_reader(self, :instance_violations)
|
261
|
+
##
|
262
|
+
# :attr_reader: report
|
263
|
+
# String for displaying human-readable error message describing protocol
|
264
|
+
# violations.
|
265
|
+
Safer::IVar.export_reader(self, :report)
|
319
266
|
|
320
|
-
##
|
321
|
-
# Error generated when an instance object does not conform to a desired
|
322
|
-
# protocol.
|
323
|
-
class InstanceError < Safer::Protocol::Error
|
324
|
-
##
|
325
|
-
# Create a Safer::Protocol::Error::InstanceError object.
|
326
|
-
def initialize(error_object, protocol, class_violations, instance_violations)
|
327
|
-
super(
|
328
|
-
"Instance of #{error_object.class} does not implement protocol" +
|
329
|
-
"#{protocol.name}.",
|
330
|
-
error_object, protocol, class_violations, instance_violations)
|
331
|
-
end # Safer::Protocol::Error::InstanceError#initialize
|
332
|
-
end # Safer::Protocol::Error::InstanceError
|
333
|
-
end # Safer::Protocol::Error
|
334
267
|
|
335
268
|
##
|
336
|
-
#
|
337
|
-
#
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
# it does not.
|
352
|
-
def ===(obj)
|
353
|
-
violations = self.safer_protocol_match__method.call(obj)
|
354
|
-
if violations
|
355
|
-
false
|
356
|
-
else
|
357
|
-
true
|
269
|
+
# Create a Safer::Protocol::Error object.
|
270
|
+
# Used internally.
|
271
|
+
def initialize(message, error_object, protocol, class_violations, instance_violations)
|
272
|
+
super(message)
|
273
|
+
self.safer_protocol_error__error_object = error_object
|
274
|
+
self.safer_protocol_error__protocol = protocol
|
275
|
+
self.safer_protocol_error__class_violations = class_violations
|
276
|
+
self.safer_protocol_error__instance_violations = instance_violations
|
277
|
+
report = ''
|
278
|
+
if class_violations
|
279
|
+
report +=
|
280
|
+
"Class method errors:\n" +
|
281
|
+
self.safer_protocol_error__class_violations.report
|
282
|
+
if instance_violations
|
283
|
+
report += "\n\n"
|
358
284
|
end
|
359
|
-
end
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
Safer::
|
285
|
+
end
|
286
|
+
if instance_violations
|
287
|
+
report +=
|
288
|
+
"Instance method errors:\n" +
|
289
|
+
self.safer_protocol_error__instance_violations.report
|
290
|
+
end
|
291
|
+
self.safer_protocol_error__report = report
|
292
|
+
end # Safer::Protocol::Error#initialize
|
367
293
|
|
368
294
|
##
|
369
|
-
#
|
370
|
-
|
371
|
-
|
372
|
-
|
295
|
+
# Compare two Safer::Protocol::Error objects for content equality.
|
296
|
+
def ==(other_object)
|
297
|
+
self.class == other_object.class &&
|
298
|
+
self.error_object == other_object.error_object &&
|
299
|
+
self.protocol == other_object.protocol &&
|
300
|
+
self.class_violations == other_object.class_violations &&
|
301
|
+
self.instance_violations == other_object.instance_violations
|
302
|
+
end # Safer::Protocol::Error#==
|
303
|
+
|
373
304
|
##
|
374
|
-
#
|
375
|
-
# Signatures of required class methods for objects implementing this
|
305
|
+
# Error generated when a Class object does not conform to a desired
|
376
306
|
# protocol.
|
377
|
-
Safer::
|
307
|
+
class ClassError < Safer::Protocol::Error
|
308
|
+
##
|
309
|
+
# Create a Safer::Protocol::Error::ClassError object.
|
310
|
+
def initialize(error_object, protocol, class_violations, instance_violations)
|
311
|
+
super(
|
312
|
+
"Class #{error_object} does not implement protocol" +
|
313
|
+
"#{protocol.name}.",
|
314
|
+
error_object, protocol, class_violations, instance_violations)
|
315
|
+
end # Safer::Protocol::Error::ClassError#initialize
|
316
|
+
end # Safer::Protocol::Error::ClassError
|
317
|
+
|
378
318
|
##
|
379
|
-
#
|
380
|
-
# Signatures of required instance methods for objects implementing this
|
319
|
+
# Error generated when an instance object does not conform to a desired
|
381
320
|
# protocol.
|
382
|
-
Safer::
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
#
|
392
|
-
|
321
|
+
class InstanceError < Safer::Protocol::Error
|
322
|
+
##
|
323
|
+
# Create a Safer::Protocol::Error::InstanceError object.
|
324
|
+
def initialize(error_object, protocol, class_violations, instance_violations)
|
325
|
+
super(
|
326
|
+
"Instance of #{error_object.class} does not implement protocol" +
|
327
|
+
"#{protocol.name}.",
|
328
|
+
error_object, protocol, class_violations, instance_violations)
|
329
|
+
end # Safer::Protocol::Error::InstanceError#initialize
|
330
|
+
end # Safer::Protocol::Error::InstanceError
|
331
|
+
end # Safer::Protocol::Error
|
393
332
|
|
333
|
+
##
|
334
|
+
# Object provides an +===+ operator, which will match when the class
|
335
|
+
# conforms to the protocol. Instances are not created directly by clients,
|
336
|
+
# but instances can be retrieved through
|
337
|
+
# Safer::Protocol#match_class and Safer::Protocol#match_instance.
|
338
|
+
class Match
|
339
|
+
Safer::IVar.instance_variable(self, :method)
|
394
340
|
##
|
395
|
-
#
|
396
|
-
#
|
397
|
-
|
398
|
-
|
399
|
-
#
|
400
|
-
def initialize(name, class_signature, instance_signature)
|
401
|
-
self.safer_protocol__name = name
|
402
|
-
self.safer_protocol__class_signature = class_signature
|
403
|
-
self.safer_protocol__instance_signature = instance_signature
|
404
|
-
self.safer_protocol__match_class = Safer::Protocol::Match.new(self.method(:violations_from_class))
|
405
|
-
self.safer_protocol__match_instance = Safer::Protocol::Match.new(self.method(:violations_from_instance))
|
406
|
-
end # Safer::Protocol#initialize
|
341
|
+
# [<tt>method</tt>] Method to call from owning Protocol to verify that
|
342
|
+
# an object matches an interface.
|
343
|
+
def initialize(method)
|
344
|
+
self.safer_protocol_match__method = method
|
345
|
+
end # Safer::Protocol::Match#initialize
|
407
346
|
|
408
347
|
##
|
409
|
-
#
|
410
|
-
#
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
class_violations = self.safer_protocol__class_signature.find_violations(klass, :method)
|
416
|
-
instance_violations = self.safer_protocol__instance_signature.find_violations(klass, :instance_method)
|
417
|
-
|
418
|
-
if class_violations || instance_violations
|
419
|
-
Error::ClassError.new(klass, self, class_violations, instance_violations)
|
348
|
+
# Returns true if the object conforms to the protocol, and false if
|
349
|
+
# it does not.
|
350
|
+
def ===(obj)
|
351
|
+
violations = self.safer_protocol_match__method.call(obj)
|
352
|
+
if violations
|
353
|
+
false
|
420
354
|
else
|
421
|
-
|
355
|
+
true
|
422
356
|
end
|
423
|
-
end # Safer::Protocol
|
357
|
+
end # Safer::Protocol::Match#===
|
358
|
+
end # Safer::Protocol::Match
|
424
359
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
# violations. Otherwise, returns +nil+.
|
431
|
-
def violations_from_instance(instance)
|
432
|
-
class_violations = self.safer_protocol__class_signature.find_violations(instance.class, :method)
|
433
|
-
instance_violations = self.safer_protocol__instance_signature.find_violations(instance, :method)
|
434
|
-
if class_violations || instance_violations
|
435
|
-
Error::InstanceError.new(instance, self, class_violations, instance_violations)
|
436
|
-
else
|
437
|
-
nil
|
438
|
-
end
|
439
|
-
end # Safer::Protocol#violations_from_instance
|
360
|
+
Safer::IVar.instance_variable(self, :name)
|
361
|
+
Safer::IVar.instance_variable(self, :class_signature)
|
362
|
+
Safer::IVar.instance_variable(self, :instance_signature)
|
363
|
+
Safer::IVar.instance_variable(self, :match_class)
|
364
|
+
Safer::IVar.instance_variable(self, :match_instance)
|
440
365
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
366
|
+
##
|
367
|
+
# :attr_reader: name
|
368
|
+
# Name of this protocol signature. Typically derived from the class name
|
369
|
+
# from which this signature is derived.
|
370
|
+
Safer::IVar.export_reader(self, :name)
|
371
|
+
##
|
372
|
+
# :attr_reader: class_signature
|
373
|
+
# Signatures of required class methods for objects implementing this
|
374
|
+
# protocol.
|
375
|
+
Safer::IVar.export_reader(self, :class_signature)
|
376
|
+
##
|
377
|
+
# :attr_reader: instance_signature
|
378
|
+
# Signatures of required instance methods for objects implementing this
|
379
|
+
# protocol.
|
380
|
+
Safer::IVar.export_reader(self, :instance_signature)
|
381
|
+
##
|
382
|
+
# :attr_reader: match_class
|
383
|
+
# Object provides === operation matching when #violations_from_class would
|
384
|
+
# return no violations.
|
385
|
+
Safer::IVar.export_reader(self, :match_class)
|
386
|
+
##
|
387
|
+
# :attr_reader: match_instance
|
388
|
+
# Object provides === operation matching when #violations_from_instance
|
389
|
+
# would return no violations.
|
390
|
+
Safer::IVar.export_reader(self, :match_instance)
|
450
391
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
392
|
+
##
|
393
|
+
# Create a +Protocol+ object.
|
394
|
+
# Used internally.
|
395
|
+
# [+name+] Value for +name+ field object.
|
396
|
+
# [+class_signature+] Value for +class_signature+ field.
|
397
|
+
# [+instance_signature+] Value for +instance_signature+ field.
|
398
|
+
def initialize(name, class_signature, instance_signature)
|
399
|
+
self.safer_protocol__name = name
|
400
|
+
self.safer_protocol__class_signature = class_signature
|
401
|
+
self.safer_protocol__instance_signature = instance_signature
|
402
|
+
self.safer_protocol__match_class = Safer::Protocol::Match.new(self.method(:violations_from_class))
|
403
|
+
self.safer_protocol__match_instance = Safer::Protocol::Match.new(self.method(:violations_from_instance))
|
404
|
+
end # Safer::Protocol#initialize
|
460
405
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
! baseinstance_table.has_key?(method_name)
|
478
|
-
end
|
406
|
+
##
|
407
|
+
# Given a Class object, find the protocol methods that are not supported by
|
408
|
+
# a class, or its instances.
|
409
|
+
# [_returns_] If protocol violations are found, returns a
|
410
|
+
# Safer::Protocol::Error::ClassError object describing the
|
411
|
+
# violations. Otherwise, returns +nil+.
|
412
|
+
def violations_from_class(klass)
|
413
|
+
class_violations = self.safer_protocol__class_signature.find_violations(klass, :method)
|
414
|
+
instance_violations = self.safer_protocol__instance_signature.find_violations(klass, :instance_method)
|
415
|
+
|
416
|
+
if class_violations || instance_violations
|
417
|
+
Error::ClassError.new(klass, self, class_violations, instance_violations)
|
418
|
+
else
|
419
|
+
nil
|
420
|
+
end
|
421
|
+
end # Safer::Protocol#violations_from_class
|
479
422
|
|
480
|
-
|
481
|
-
|
423
|
+
##
|
424
|
+
# Given an instance object, find the protocol methods that are not supported
|
425
|
+
# by the instance, or its class.
|
426
|
+
# [_returns_] If protocol violations are found, returns a
|
427
|
+
# Safer::Protocol::Error::InstanceError object describing the
|
428
|
+
# violations. Otherwise, returns +nil+.
|
429
|
+
def violations_from_instance(instance)
|
430
|
+
class_violations = self.safer_protocol__class_signature.find_violations(instance.class, :method)
|
431
|
+
instance_violations = self.safer_protocol__instance_signature.find_violations(instance, :method)
|
432
|
+
if class_violations || instance_violations
|
433
|
+
Error::InstanceError.new(instance, self, class_violations, instance_violations)
|
434
|
+
else
|
435
|
+
nil
|
436
|
+
end
|
437
|
+
end # Safer::Protocol#violations_from_instance
|
482
438
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
439
|
+
##
|
440
|
+
# Check that a Class object conforms to this protocol.
|
441
|
+
def class_conforms?(klass)
|
442
|
+
violations = self.violations_from_class(klass)
|
443
|
+
if violations
|
444
|
+
raise violations
|
445
|
+
end
|
446
|
+
true
|
447
|
+
end # Safer::Protocol#class_conforms?
|
448
|
+
|
449
|
+
##
|
450
|
+
# Check that an instance object conforms to this protocol.
|
451
|
+
def instance_conforms?(instance)
|
452
|
+
violations = self.violations_from_instance(instance)
|
453
|
+
if violations
|
454
|
+
raise violations
|
455
|
+
end
|
456
|
+
true
|
457
|
+
end # Safer::Protocol#instance_conforms?
|
497
458
|
|
498
|
-
|
499
|
-
|
459
|
+
##
|
460
|
+
# Given a +Class+ object +klass+ and a +Class+ object +base+, create a
|
461
|
+
# +Protocol+ object representing the methods implemented in +klass+
|
462
|
+
# that are not present in +base+.
|
463
|
+
# [+klass+] +Class+ object from which to derive a +Protocol+.
|
464
|
+
# [+base+] +Class+ object describing routines that will be implemented by
|
465
|
+
# +klass+, but which should not be included in the returned
|
466
|
+
# +Protocol+.
|
467
|
+
def self.create_from_class(klass, base = Object)
|
468
|
+
class_signature = Signature.create(klass, :methods, :method) do
|
469
|
+
|method_name|
|
470
|
+
! Class.respond_to?(method_name)
|
471
|
+
end
|
472
|
+
baseinstance_table = self._array_to_table(base.instance_methods)
|
473
|
+
instance_signature = Signature.create(klass, :instance_methods, :instance_method) do
|
474
|
+
|method_name|
|
475
|
+
! baseinstance_table.has_key?(method_name)
|
476
|
+
end
|
477
|
+
|
478
|
+
self.new(klass.to_s, class_signature, instance_signature)
|
479
|
+
end # Safer::Protocol.create_from_class
|
500
480
|
|
501
|
-
|
502
|
-
|
481
|
+
##
|
482
|
+
# Given an instance object +instance+ and a +Class+ object +baseclass+,
|
483
|
+
# create a +Protocol+ object representing the methods implemented in
|
484
|
+
# +klass+ that are not present in +base+.
|
485
|
+
def self.create_from_instance(instance, baseclass = Object)
|
486
|
+
class_signature = Signature.create(instance.class, :methods, :method) do
|
487
|
+
|method_name|
|
488
|
+
! Class.method_defined?(method_name)
|
489
|
+
end
|
490
|
+
baseinstance_table = self._array_to_table(base.instance_methods)
|
491
|
+
instance_signature = Signature.create(instance, :methods, :method) do
|
492
|
+
|method_name|
|
493
|
+
! baseinstance_table.has_key?(method_name)
|
494
|
+
end
|
495
|
+
|
496
|
+
self.new(instance.class.to_s, class_signature, instance_signature)
|
497
|
+
end # Safer::Protocol.create_from_instance
|
498
|
+
|
499
|
+
end # Safer::Protocol
|