safer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/README.txt +3 -3
- data/Rakefile +1 -1
- data/lib/safer/ivar.rb +115 -20
- data/lib/safer/protocol.rb +208 -82
- data/lib/safer.rb +1 -1
- data/test/test_safer_protocol.rb +64 -43
- metadata +6 -6
data/History.txt
CHANGED
data/README.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= safer
|
2
2
|
|
3
|
-
http://safer.rubyforge.org
|
3
|
+
http://safer.rubyforge.org
|
4
4
|
|
5
5
|
== DESCRIPTION:
|
6
6
|
|
@@ -19,7 +19,7 @@ modules under the safer umbrella:
|
|
19
19
|
|
20
20
|
* Name instance variables after the class in which they are defined. Generate
|
21
21
|
accessor functions for these instance variables.
|
22
|
-
* Define
|
22
|
+
* Define a "Protocol" class, and derive a signature from it. Verify that
|
23
23
|
another class (or an instance of that class) implements the protocol
|
24
24
|
signature.
|
25
25
|
|
@@ -53,7 +53,7 @@ modules under the safer umbrella:
|
|
53
53
|
|
54
54
|
== INSTALL:
|
55
55
|
|
56
|
-
* sudo gem install
|
56
|
+
* sudo gem install safer
|
57
57
|
|
58
58
|
== DEVELOPERS:
|
59
59
|
|
data/Rakefile
CHANGED
data/lib/safer/ivar.rb
CHANGED
@@ -3,10 +3,95 @@ module Safer
|
|
3
3
|
# Create accessor functions for instance variables, in which the accessor
|
4
4
|
# function is prefixed with the name of the class in which the instance
|
5
5
|
# variable is defined.
|
6
|
+
#
|
7
|
+
# ==Usage
|
8
|
+
# Create one or more instance variables using instance_variable .
|
9
|
+
# For example, the following code:
|
10
|
+
# class Outer
|
11
|
+
# Safer::IVar.instance_variable(self, :variable)
|
12
|
+
# class Inner < Outer
|
13
|
+
# Safer::IVar.instance_variable(self, :variable)
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
# puts(Outer.instance_methods.grep(/__variable/).inspect)
|
17
|
+
# puts(Outer::Inner.instance_methods.grep(/__variable/).inspect)
|
18
|
+
# produces the following output:
|
19
|
+
# ["outer__variable", "outer__variable="]
|
20
|
+
# ["outer_inner__variable", "outer_inner__variable=", "outer__variable", "outer__variable="]
|
21
|
+
#
|
22
|
+
# Accessors for Safer::IVar-defined instance variables can be created using
|
23
|
+
# export_reader, export_writer, and export_accessor .
|
24
|
+
#
|
25
|
+
# ==Rationale
|
26
|
+
# Safer::IVar is intended to improve the clarity and consistency of ruby
|
27
|
+
# code in at least the following (related) ways:
|
28
|
+
#
|
29
|
+
# 1. Reducing the probability of instance variable usage errors.
|
30
|
+
# 2. Documenting the instance variables attached to a class.
|
31
|
+
#
|
32
|
+
# ===Error Reduction
|
33
|
+
# Safer::IVar should help in reducing errors in at least the following ways:
|
34
|
+
#
|
35
|
+
# * Encapsulation errors. Traditional ruby instance variables defined by one
|
36
|
+
# class are transparently accessible to all subclasses. They are not,
|
37
|
+
# however, part of the public interface to a class (unless an accessor is
|
38
|
+
# defined), which means they are not generally documented. These two
|
39
|
+
# factors create a situation in which it is quite possible that a subclass
|
40
|
+
# inadvertantly re-define an instance variable in such a way as to render
|
41
|
+
# the subclass unusable. Bugs of this sort can be very difficult to
|
42
|
+
# resolve.
|
43
|
+
#
|
44
|
+
# Because instance variables generated by Safer::IVar are prefixed with a
|
45
|
+
# string derived from the class in which the variable is defined, it is much
|
46
|
+
# less likely that developers _inadvertantly_ re-define instance variables
|
47
|
+
# in sub-classes, dramatically reducing the likelihood of this type of
|
48
|
+
# error.
|
49
|
+
#
|
50
|
+
# * Misspelling errors. For example, suppose you typically write software
|
51
|
+
# using the en-us dialect, and have an object with a +@color+ instance
|
52
|
+
# variable. A en-uk speaker then submits a patch that sets the default
|
53
|
+
# color to green:
|
54
|
+
# --- ex1.rb 2010-09-28 06:24:52.000000000 -0400
|
55
|
+
# +++ ex2.rb 2010-09-28 06:25:00.000000000 -0400
|
56
|
+
# @@ -1,6 +1,7 @@
|
57
|
+
# class Foo
|
58
|
+
# def initialize
|
59
|
+
# @size = 3
|
60
|
+
# + @colour = "green"
|
61
|
+
# end
|
62
|
+
# attr_reader :color
|
63
|
+
# end
|
64
|
+
# This code will not raise any exceptions, but its behavior does not match
|
65
|
+
# developer intent. On the other hand, using Safer::IVar
|
66
|
+
# --- ex1-safer.rb 2010-09-28 06:31:51.000000000 -0400
|
67
|
+
# +++ ex2-safer.rb 2010-09-28 06:32:08.000000000 -0400
|
68
|
+
# @@ -1,7 +1,8 @@
|
69
|
+
# class Foo
|
70
|
+
# Safer::IVar.instance_variable(self, :size, :color)
|
71
|
+
# Safer::IVar.export_reader(self, :color)
|
72
|
+
# def initialize
|
73
|
+
# self.foo__size = 3
|
74
|
+
# + self.foo__colour = "green"
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# The new code will raise an exception at the call to Foo.new, making it
|
78
|
+
# much less likely that the error will go undetected.
|
79
|
+
#
|
80
|
+
# ===Documentation
|
81
|
+
# Traditional ruby instance variables are defined and used in an <i>ad hoc</i>
|
82
|
+
# manner. As such, there is no natural location in which the instance
|
83
|
+
# variables defined by a class can be documented, and no obvious way to
|
84
|
+
# determine the set of instance variables used in a class. Safer::IVar
|
85
|
+
# instance variables will all be associated with a single call to
|
86
|
+
# Safer::IVar.instance_variable. This provides both a natural location for
|
87
|
+
# documenting an instance variable's interpretation, as well as a string to
|
88
|
+
# search for when determining the set of instance variables defined by a
|
89
|
+
# class.
|
90
|
+
#
|
6
91
|
module IVar
|
7
92
|
|
8
93
|
##
|
9
|
-
# Given a
|
94
|
+
# Given a Class object, derive the prefix string for the accessor functions
|
10
95
|
# that instance_variable will create.
|
11
96
|
def self.class_symbol_prefix(klass)
|
12
97
|
klass.to_s.split('::').inject(nil) do |memo, el|
|
@@ -19,7 +104,21 @@ module Safer
|
|
19
104
|
el.downcase
|
20
105
|
end
|
21
106
|
end
|
22
|
-
end
|
107
|
+
end # Safer::IVar.class_symbol_prefix
|
108
|
+
|
109
|
+
##
|
110
|
+
# Used internally.
|
111
|
+
# [+klass+] Class object for which to generate a symbol name.
|
112
|
+
# [+nmlist+] Array of +Symbol+ objects.
|
113
|
+
# [_return_] Array of +Symbol+ objects generated from +klass+ and each
|
114
|
+
# element of +nmlist+.
|
115
|
+
def self._symbol_names(klass, nmlist)
|
116
|
+
kname = self.class_symbol_prefix(klass)
|
117
|
+
|
118
|
+
nmlist.map do |nm|
|
119
|
+
(kname + '__' + nm.to_s).to_sym
|
120
|
+
end
|
121
|
+
end # Safer::IVar._symbol_names
|
23
122
|
|
24
123
|
##
|
25
124
|
# compute accessor routine symbol names, so that the names include the
|
@@ -29,21 +128,17 @@ module Safer
|
|
29
128
|
# [_return_] Array of +Symbol+ objects generated from +klass+ and each
|
30
129
|
# element of +nmlist+.
|
31
130
|
# For example, given the following listing:
|
32
|
-
# class
|
33
|
-
# class
|
131
|
+
# class OuterClass
|
132
|
+
# class InnerClass
|
34
133
|
# SYMNM = Safer::IVar::symbol_names(self, :foo)
|
35
|
-
# puts(SYMNM)
|
134
|
+
# puts(SYMNM.to_s)
|
36
135
|
# end
|
37
136
|
# end
|
38
137
|
# the following output would be produced:
|
39
|
-
#
|
40
|
-
def self.symbol_names(klass, nmlist)
|
41
|
-
|
42
|
-
|
43
|
-
nmlist.map do |nm|
|
44
|
-
(kname + '__' + nm.to_s).to_sym
|
45
|
-
end
|
46
|
-
end # Safer::IVar::symbol_names
|
138
|
+
# outerclass_innerclass__foo
|
139
|
+
def self.symbol_names(klass, *nmlist)
|
140
|
+
self._symbol_names(klass, nmlist)
|
141
|
+
end # Safer::IVar.symbol_names
|
47
142
|
|
48
143
|
##
|
49
144
|
# create accessor routines for an instance variable, with variable name
|
@@ -69,12 +164,12 @@ module Safer
|
|
69
164
|
# The name-mangling signals that these instance variables are, for all
|
70
165
|
# intents and purposes, private to +klass+.
|
71
166
|
def self.instance_variable(klass, *nmlist)
|
72
|
-
self.symbol_names(klass, nmlist).each do |symnm|
|
167
|
+
self.symbol_names(klass, *nmlist).each do |symnm|
|
73
168
|
klass.class_eval do
|
74
169
|
attr_accessor symnm
|
75
170
|
end
|
76
171
|
end
|
77
|
-
end # Safer::IVar
|
172
|
+
end # Safer::IVar.instance_variable
|
78
173
|
|
79
174
|
##
|
80
175
|
# export the reader routines for instance variables in a nicer way.
|
@@ -84,13 +179,13 @@ module Safer
|
|
84
179
|
# Each symbol in +nmlist+ should have previously been given as
|
85
180
|
# an argument to Safer::IVar::instance_variables(+klass+).
|
86
181
|
def self.export_reader(klass, *nmlist)
|
87
|
-
symlist = self.symbol_names(klass, nmlist)
|
182
|
+
symlist = self.symbol_names(klass, *nmlist)
|
88
183
|
nmlist.size.times do |index|
|
89
184
|
thisnm = nmlist[index]
|
90
185
|
thissym = symlist[index]
|
91
186
|
klass.class_eval("def #{thisnm} ; self.#{thissym} ; end")
|
92
187
|
end
|
93
|
-
end # Safer::IVar
|
188
|
+
end # Safer::IVar.export_reader
|
94
189
|
|
95
190
|
##
|
96
191
|
# export the writer routines for instance variables in a nicer way.
|
@@ -100,7 +195,7 @@ module Safer
|
|
100
195
|
# Each symbol in +nmlist+ should have previously been given as
|
101
196
|
# an argument to Safer::IVar::instance_variables(+klass+).
|
102
197
|
def self.export_writer(klass, *nmlist)
|
103
|
-
symlist = self.symbol_names(klass, nmlist)
|
198
|
+
symlist = self.symbol_names(klass, *nmlist)
|
104
199
|
nmlist.size.times do |index|
|
105
200
|
thisnm = nmlist[index]
|
106
201
|
thissym = symlist[index]
|
@@ -108,7 +203,7 @@ module Safer
|
|
108
203
|
"def #{thisnm}=(value) ; self.#{thissym} = value ; end"
|
109
204
|
)
|
110
205
|
end
|
111
|
-
end # Safer::IVar
|
206
|
+
end # Safer::IVar.export_writer
|
112
207
|
|
113
208
|
##
|
114
209
|
# export both reader and writer routines for instance variables in a
|
@@ -121,7 +216,7 @@ module Safer
|
|
121
216
|
def self.export_accessor(klass, *nmlist)
|
122
217
|
self.export_reader(klass, *nmlist)
|
123
218
|
self.export_writer(klass, *nmlist)
|
124
|
-
end # Safer::IVar
|
219
|
+
end # Safer::IVar.export_accessor
|
125
220
|
|
126
221
|
end # Safer::IVar
|
127
222
|
end # Safer
|
data/lib/safer/protocol.rb
CHANGED
@@ -1,25 +1,119 @@
|
|
1
1
|
require 'safer/ivar'
|
2
2
|
|
3
3
|
module Safer
|
4
|
-
## Safer::
|
4
|
+
## Safer::Protocol
|
5
5
|
# Named set of class- and instance- methods, with associated numbers of
|
6
6
|
# arguments. Call +create_from_class+ or +create_from_instance+ to create a
|
7
|
-
# new +
|
7
|
+
# new +Protocol+ object. Call +class_conforms?+ or +instance_conforms?+
|
8
8
|
# to verify that a Class or instance of the class (respectively) conforms to
|
9
9
|
# a protocol.
|
10
|
-
|
10
|
+
#
|
11
|
+
# ==Usage
|
12
|
+
# In the example, we define a class with a single instance variable,
|
13
|
+
# initialized from an argument to #initialize. We must verify that the
|
14
|
+
# object corresponding to this argument implements a particular protocol.
|
15
|
+
#
|
16
|
+
# class YourClass
|
17
|
+
# # Define a class or module containing all the class- and instance-
|
18
|
+
# # methods that you require from objects passed into your methods. Here,
|
19
|
+
# # we expect the +Class+ object to implement a +foo+ method that takes
|
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
|
11
105
|
|
12
106
|
##
|
13
|
-
# Utility function used during
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
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+.
|
20
114
|
def self._array_to_table(array, &block)
|
21
115
|
if ! block
|
22
|
-
block = proc do |el| true ; end
|
116
|
+
block = proc do |h, el| true ; end
|
23
117
|
end
|
24
118
|
array.inject(Hash::new) do |h, el|
|
25
119
|
newval = block.call(h, el)
|
@@ -28,12 +122,12 @@ module Safer
|
|
28
122
|
end
|
29
123
|
h
|
30
124
|
end
|
31
|
-
end
|
125
|
+
end # Safer::Protocol._array_to_table
|
32
126
|
|
33
127
|
##
|
34
128
|
# Describes a set of methods that should be implemented by an object, but
|
35
|
-
# are not implemented. The
|
36
|
-
#
|
129
|
+
# are not implemented. The Protocol::Error object will contain two
|
130
|
+
# Protocol::Violation objects: one for class-method protocol
|
37
131
|
# violations, and one for instance-method protocol violations.
|
38
132
|
class Violation
|
39
133
|
Safer::IVar::instance_variable(self, :table)
|
@@ -47,7 +141,7 @@ module Safer
|
|
47
141
|
# had a different arity (number of required / optional arguments) than
|
48
142
|
# the protocol, the value for that method will be the arity discovered in
|
49
143
|
# the object. (The desired arity can be accessed through the
|
50
|
-
#
|
144
|
+
# Protocol::Signature for that method.)
|
51
145
|
Safer::IVar::export_reader(self, :table)
|
52
146
|
##
|
53
147
|
# :attr_reader: report
|
@@ -56,22 +150,22 @@ module Safer
|
|
56
150
|
Safer::IVar::export_reader(self, :report)
|
57
151
|
|
58
152
|
##
|
59
|
-
# Create a new
|
153
|
+
# Create a new Protocol::Violation object.
|
60
154
|
def initialize(table, report)
|
61
|
-
self.
|
62
|
-
self.
|
63
|
-
end
|
155
|
+
self.safer_protocol_violation__table = table
|
156
|
+
self.safer_protocol_violation__report = report
|
157
|
+
end # Safer::Protocol::Violation#initialize
|
64
158
|
##
|
65
|
-
# Compare two
|
159
|
+
# Compare two Protocol::Violation objects for content equality.
|
66
160
|
def ==(other_object)
|
67
161
|
self.class == other_object.class &&
|
68
|
-
self.
|
69
|
-
end
|
70
|
-
end
|
162
|
+
self.safer_protocol_violation__table == other_object.safer_protocol_violation__table
|
163
|
+
end # Safer::Protocol::Violation#==
|
164
|
+
end # Safer::Protocol::Violation
|
71
165
|
|
72
166
|
##
|
73
167
|
# Hash of method_name => method.arity, describing a set of methods that
|
74
|
-
# we expect to see implemented in another class.
|
168
|
+
# we expect to see implemented in another class. Protocol will contain
|
75
169
|
# two of these objects - one describing class methods, and one describing
|
76
170
|
# instance methods.
|
77
171
|
class Signature
|
@@ -83,16 +177,16 @@ module Safer
|
|
83
177
|
Safer::IVar::export_reader(self, :table)
|
84
178
|
|
85
179
|
##
|
86
|
-
# Create a
|
180
|
+
# Create a Protocol::Signature object.
|
87
181
|
def initialize(table)
|
88
|
-
self.
|
89
|
-
end
|
182
|
+
self.safer_protocol_signature__table = table
|
183
|
+
end # Safer::Protocol::Signature#initialize
|
90
184
|
##
|
91
|
-
# Compare two
|
185
|
+
# Compare two Protocol::Signature objects for content equality.
|
92
186
|
def ==(other_object)
|
93
187
|
self.class == other_object.class &&
|
94
|
-
self.
|
95
|
-
end
|
188
|
+
self.safer_protocol_signature__table == other_object.safer_protocol_signature__table
|
189
|
+
end # Safer::Protocol::Signature#==
|
96
190
|
|
97
191
|
##
|
98
192
|
# Given an object, and the name of the method for getting a named method
|
@@ -102,7 +196,7 @@ module Safer
|
|
102
196
|
report = ''
|
103
197
|
have_error = false
|
104
198
|
error_table = Hash::new
|
105
|
-
self.
|
199
|
+
self.safer_protocol_signature__table.each_pair do |name, arity|
|
106
200
|
begin
|
107
201
|
m = object.send(get_method, name)
|
108
202
|
if m.arity != arity
|
@@ -118,17 +212,17 @@ module Safer
|
|
118
212
|
end
|
119
213
|
|
120
214
|
if have_error
|
121
|
-
Safer::
|
215
|
+
Safer::Protocol::Violation.new(error_table, report)
|
122
216
|
else
|
123
217
|
nil
|
124
218
|
end
|
125
|
-
end
|
219
|
+
end # Safer::Protocol::Signature#find_violations
|
126
220
|
|
127
221
|
##
|
128
222
|
# Derive a Signature from an object.
|
129
223
|
def self.create(object, get_methods, get_method, &filter)
|
130
224
|
method_names = object.send(get_methods)
|
131
|
-
method_table = Safer::
|
225
|
+
method_table = Safer::Protocol._array_to_table(method_names) do
|
132
226
|
|h, method_name|
|
133
227
|
if filter.call(method_name)
|
134
228
|
m = object.send(get_method, method_name)
|
@@ -136,10 +230,10 @@ module Safer
|
|
136
230
|
end
|
137
231
|
end
|
138
232
|
self.new(method_table)
|
139
|
-
end
|
140
|
-
end
|
233
|
+
end # Safer::Protocol::Signature.create
|
234
|
+
end # Safer::Protocol::Signature
|
141
235
|
|
142
|
-
## Safer::
|
236
|
+
## Safer::Protocol::Error
|
143
237
|
# Error generated when a class does not conform to a protocol signature.
|
144
238
|
class Error < StandardError
|
145
239
|
Safer::IVar::instance_variable(self, :error_object)
|
@@ -154,16 +248,16 @@ module Safer
|
|
154
248
|
Safer::IVar::export_reader(self, :error_object)
|
155
249
|
##
|
156
250
|
# :attr_reader: protocol
|
157
|
-
#
|
251
|
+
# Protocol object describing the signature that error_object failed
|
158
252
|
# to properly implement.
|
159
253
|
Safer::IVar::export_reader(self, :protocol)
|
160
254
|
##
|
161
255
|
# :attr_reader: class_violations
|
162
|
-
#
|
256
|
+
# Protocol::Violation describing class-method protocol violations.
|
163
257
|
Safer::IVar::export_reader(self, :class_violations)
|
164
258
|
##
|
165
259
|
# :attr_reader: instance_violations
|
166
|
-
#
|
260
|
+
# Protocol::Violation describing instance-method protocol violations.
|
167
261
|
Safer::IVar::export_reader(self, :instance_violations)
|
168
262
|
##
|
169
263
|
# :attr_reader: report
|
@@ -173,19 +267,19 @@ module Safer
|
|
173
267
|
|
174
268
|
|
175
269
|
##
|
176
|
-
# Create a Safer::
|
270
|
+
# Create a Safer::Protocol::Error object.
|
177
271
|
# Used internally.
|
178
272
|
def initialize(message, error_object, protocol, class_violations, instance_violations)
|
179
273
|
super(message)
|
180
|
-
self.
|
181
|
-
self.
|
182
|
-
self.
|
183
|
-
self.
|
274
|
+
self.safer_protocol_error__error_object = error_object
|
275
|
+
self.safer_protocol_error__protocol = protocol
|
276
|
+
self.safer_protocol_error__class_violations = class_violations
|
277
|
+
self.safer_protocol_error__instance_violations = instance_violations
|
184
278
|
report = ''
|
185
279
|
if class_violations
|
186
280
|
report +=
|
187
281
|
"Class method errors:\n" +
|
188
|
-
self.
|
282
|
+
self.safer_protocol_error__class_violations.report
|
189
283
|
if instance_violations
|
190
284
|
report += "\n\n"
|
191
285
|
end
|
@@ -193,49 +287,49 @@ module Safer
|
|
193
287
|
if instance_violations
|
194
288
|
report +=
|
195
289
|
"Instance method errors:\n" +
|
196
|
-
self.
|
290
|
+
self.safer_protocol_error__instance_violations.report
|
197
291
|
end
|
198
|
-
self.
|
199
|
-
end # Safer::
|
292
|
+
self.safer_protocol_error__report = report
|
293
|
+
end # Safer::Protocol::Error#initialize
|
200
294
|
|
201
295
|
##
|
202
|
-
# Compare two Safer::
|
296
|
+
# Compare two Safer::Protocol::Error objects for content equality.
|
203
297
|
def ==(other_object)
|
204
298
|
self.class == other_object.class &&
|
205
299
|
self.error_object == other_object.error_object &&
|
206
300
|
self.protocol == other_object.protocol &&
|
207
301
|
self.class_violations == other_object.class_violations &&
|
208
302
|
self.instance_violations == other_object.instance_violations
|
209
|
-
end
|
303
|
+
end # Safer::Protocol::Error#==
|
210
304
|
|
211
305
|
##
|
212
306
|
# Error generated when a Class object does not conform to a desired
|
213
307
|
# protocol.
|
214
|
-
class ClassError < Safer::
|
308
|
+
class ClassError < Safer::Protocol::Error
|
215
309
|
##
|
216
|
-
# Create a Safer::
|
310
|
+
# Create a Safer::Protocol::Error::ClassError object.
|
217
311
|
def initialize(error_object, protocol, class_violations, instance_violations)
|
218
312
|
super(
|
219
313
|
"Class #{error_object} does not implement protocol" +
|
220
314
|
"#{protocol.name}.",
|
221
315
|
error_object, protocol, class_violations, instance_violations)
|
222
|
-
end
|
223
|
-
end # Safer::
|
316
|
+
end # Safer::Protocol::Error::ClassError#initialize
|
317
|
+
end # Safer::Protocol::Error::ClassError
|
224
318
|
|
225
319
|
##
|
226
320
|
# Error generated when an instance object does not conform to a desired
|
227
321
|
# protocol.
|
228
|
-
class InstanceError < Safer::
|
322
|
+
class InstanceError < Safer::Protocol::Error
|
229
323
|
##
|
230
|
-
# Create a Safer::
|
324
|
+
# Create a Safer::Protocol::Error::InstanceError object.
|
231
325
|
def initialize(error_object, protocol, class_violations, instance_violations)
|
232
326
|
super(
|
233
327
|
"Instance of #{error_object.class} does not implement protocol" +
|
234
328
|
"#{protocol.name}.",
|
235
329
|
error_object, protocol, class_violations, instance_violations)
|
236
|
-
end
|
237
|
-
end # Safer::
|
238
|
-
end # Safer::
|
330
|
+
end # Safer::Protocol::Error::InstanceError#initialize
|
331
|
+
end # Safer::Protocol::Error::InstanceError
|
332
|
+
end # Safer::Protocol::Error
|
239
333
|
|
240
334
|
|
241
335
|
Safer::IVar::instance_variable(self, :name)
|
@@ -259,46 +353,78 @@ module Safer
|
|
259
353
|
Safer::IVar::export_reader(self, :instance_signature)
|
260
354
|
|
261
355
|
##
|
262
|
-
# Create a +
|
356
|
+
# Create a +Protocol+ object.
|
263
357
|
# Used internally.
|
264
358
|
# [+name+] Value for +name+ field object.
|
265
359
|
# [+class_signature+] Value for +class_signature+ field.
|
266
360
|
# [+instance_signature+] Value for +instance_signature+ field.
|
267
361
|
def initialize(name, class_signature, instance_signature)
|
268
|
-
self.
|
269
|
-
self.
|
270
|
-
self.
|
271
|
-
end # Safer::
|
362
|
+
self.safer_protocol__name = name
|
363
|
+
self.safer_protocol__class_signature = class_signature
|
364
|
+
self.safer_protocol__instance_signature = instance_signature
|
365
|
+
end # Safer::Protocol#initialize
|
366
|
+
|
367
|
+
##
|
368
|
+
# Given a Class object, find the protocol methods that are not supported by
|
369
|
+
# a class, or its instances.
|
370
|
+
# [_returns_] If protocol violations are found, returns a
|
371
|
+
# Safer::Protocol::Error::ClassError object describing the
|
372
|
+
# violations. Otherwise, returns +nil+.
|
373
|
+
def violations_from_class(klass)
|
374
|
+
class_violations = self.safer_protocol__class_signature.find_violations(klass, :method)
|
375
|
+
instance_violations = self.safer_protocol__instance_signature.find_violations(klass, :instance_method)
|
376
|
+
|
377
|
+
if class_violations || instance_violations
|
378
|
+
Error::ClassError.new(klass, self, class_violations, instance_violations)
|
379
|
+
else
|
380
|
+
nil
|
381
|
+
end
|
382
|
+
end # Safer::Protocol#violations_from_class
|
383
|
+
|
384
|
+
##
|
385
|
+
# Given an instance object, find the protocol methods that are not supported
|
386
|
+
# by the instance, or its class.
|
387
|
+
# [_returns_] If protocol violations are found, returns a
|
388
|
+
# Safer::Protocol::Error::InstanceError object describing the
|
389
|
+
# violations. Otherwise, returns +nil+.
|
390
|
+
def violations_from_instance(instance)
|
391
|
+
class_violations = self.safer_protocol__class_signature.find_violations(instance.class, :method)
|
392
|
+
instance_violations = self.safer_protocol__instance_signature.find_violations(instance, :method)
|
393
|
+
if class_violations || instance_violations
|
394
|
+
Error::InstanceError.new(instance, self, class_violations, instance_violations)
|
395
|
+
else
|
396
|
+
nil
|
397
|
+
end
|
398
|
+
end # Safer::Protocol#violations_from_instance
|
272
399
|
|
273
400
|
##
|
274
401
|
# Check that a Class object conforms to this protocol.
|
275
402
|
def class_conforms?(klass)
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
raise(Error::ClassError.new(klass, self, class_violations, instance_violations))
|
403
|
+
violations = self.violations_from_class(klass)
|
404
|
+
if violations
|
405
|
+
raise violations
|
280
406
|
end
|
281
407
|
true
|
282
|
-
end # Safer::
|
408
|
+
end # Safer::Protocol#class_conforms?
|
409
|
+
|
283
410
|
##
|
284
411
|
# Check that an instance object conforms to this protocol.
|
285
412
|
def instance_conforms?(instance)
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
raise(Error::InstanceError.new(instance, self, class_violations, instance_violations))
|
413
|
+
violations = self.violations_from_instance(instance)
|
414
|
+
if violations
|
415
|
+
raise violations
|
290
416
|
end
|
291
417
|
true
|
292
|
-
end # Safer::
|
418
|
+
end # Safer::Protocol#instance_conforms?
|
293
419
|
|
294
420
|
##
|
295
421
|
# Given a +Class+ object +klass+ and a +Class+ object +base+, create a
|
296
|
-
# +
|
422
|
+
# +Protocol+ object representing the methods implemented in +klass+
|
297
423
|
# that are not present in +base+.
|
298
|
-
# [+klass+] +Class+ object from which to derive a +
|
424
|
+
# [+klass+] +Class+ object from which to derive a +Protocol+.
|
299
425
|
# [+base+] +Class+ object describing routines that will be implemented by
|
300
426
|
# +klass+, but which should not be included in the returned
|
301
|
-
# +
|
427
|
+
# +Protocol+.
|
302
428
|
def self.create_from_class(klass, base = Object)
|
303
429
|
class_signature = Signature.create(klass, :methods, :method) do
|
304
430
|
|method_name|
|
@@ -311,11 +437,11 @@ module Safer
|
|
311
437
|
end
|
312
438
|
|
313
439
|
self.new(klass.to_s, class_signature, instance_signature)
|
314
|
-
end # Safer::
|
440
|
+
end # Safer::Protocol.create_from_class
|
315
441
|
|
316
442
|
##
|
317
443
|
# Given an instance object +instance+ and a +Class+ object +baseclass+,
|
318
|
-
# create a +
|
444
|
+
# create a +Protocol+ object representing the methods implemented in
|
319
445
|
# +klass+ that are not present in +base+.
|
320
446
|
def self.create_from_instance(instance, baseclass = Object)
|
321
447
|
class_signature = Signature.create(instance.class, :methods, :method) do
|
@@ -329,7 +455,7 @@ module Safer
|
|
329
455
|
end
|
330
456
|
|
331
457
|
self.new(instance.class.to_s, class_signature, instance_signature)
|
332
|
-
end # Safer::
|
458
|
+
end # Safer::Protocol.create_from_instance
|
333
459
|
|
334
|
-
end # Safer::
|
460
|
+
end # Safer::Protocol
|
335
461
|
end # Safer
|
data/lib/safer.rb
CHANGED
data/test/test_safer_protocol.rb
CHANGED
@@ -34,9 +34,9 @@ end
|
|
34
34
|
|
35
35
|
class TC_SaferProtocol < Test::Unit::TestCase
|
36
36
|
def setup
|
37
|
-
@class_protocol = Safer::
|
38
|
-
@instance_protocol = Safer::
|
39
|
-
@both_protocol = Safer::
|
37
|
+
@class_protocol = Safer::Protocol.create_from_class(TestArityClass)
|
38
|
+
@instance_protocol = Safer::Protocol.create_from_class(TestArityInstance)
|
39
|
+
@both_protocol = Safer::Protocol.create_from_class(TestArityBoth)
|
40
40
|
|
41
41
|
@class_instance = TestArityClass::new
|
42
42
|
@instance_instance = TestArityInstance::new
|
@@ -56,9 +56,19 @@ class TC_SaferProtocol < Test::Unit::TestCase
|
|
56
56
|
|
57
57
|
def get_methods(useinstance)
|
58
58
|
if useinstance
|
59
|
-
|
59
|
+
{
|
60
|
+
:conforms => :instance_conforms?,
|
61
|
+
:violations => :violations_from_instance,
|
62
|
+
:get_object => proc do |obj| obj ; end,
|
63
|
+
:error_type => Safer::Protocol::Error::InstanceError
|
64
|
+
}
|
60
65
|
else
|
61
|
-
|
66
|
+
{
|
67
|
+
:conforms => :class_conforms?,
|
68
|
+
:violations => :violations_from_class,
|
69
|
+
:get_object => proc do |obj| obj.class ; end,
|
70
|
+
:error_type => Safer::Protocol::Error::ClassError
|
71
|
+
}
|
62
72
|
end
|
63
73
|
end
|
64
74
|
|
@@ -71,14 +81,19 @@ class TC_SaferProtocol < Test::Unit::TestCase
|
|
71
81
|
end
|
72
82
|
|
73
83
|
def run_shouldConform(useinstance)
|
74
|
-
|
84
|
+
methods = get_methods(useinstance)
|
75
85
|
assert_nothing_thrown do
|
76
|
-
@class_protocol.send(
|
77
|
-
@instance_protocol.send(
|
78
|
-
@both_protocol.send(
|
79
|
-
@class_protocol.send(
|
80
|
-
@instance_protocol.send(
|
86
|
+
@class_protocol.send(methods[:conforms], methods[:get_object].call(@class_instance))
|
87
|
+
@instance_protocol.send(methods[:conforms], methods[:get_object].call(@instance_instance))
|
88
|
+
@both_protocol.send(methods[:conforms], methods[:get_object].call(@both_instance))
|
89
|
+
@class_protocol.send(methods[:conforms], methods[:get_object].call(@both_instance))
|
90
|
+
@instance_protocol.send(methods[:conforms], methods[:get_object].call(@both_instance))
|
81
91
|
end
|
92
|
+
assert_same(@class_protocol.send(methods[:violations], methods[:get_object].call(@class_instance)), nil)
|
93
|
+
assert_same(@instance_protocol.send(methods[:violations], methods[:get_object].call(@instance_instance)), nil)
|
94
|
+
assert_same(@both_protocol.send(methods[:violations], methods[:get_object].call(@both_instance)), nil)
|
95
|
+
assert_same(@class_protocol.send(methods[:violations], methods[:get_object].call(@both_instance)), nil)
|
96
|
+
assert_same(@instance_protocol.send(methods[:violations], methods[:get_object].call(@both_instance)), nil)
|
82
97
|
end
|
83
98
|
def test_shouldConform
|
84
99
|
run_shouldConform(false)
|
@@ -86,16 +101,18 @@ class TC_SaferProtocol < Test::Unit::TestCase
|
|
86
101
|
end
|
87
102
|
|
88
103
|
def run_arity(useinstance)
|
89
|
-
|
104
|
+
methods = get_methods(useinstance)
|
105
|
+
violations = @instance_protocol.send(methods[:violations], methods[:get_object].call(@error_instance))
|
106
|
+
assert_same(violations.class, methods[:error_type])
|
107
|
+
assert_same(methods[:get_object].call(@error_instance), violations.error_object)
|
108
|
+
assert_same(@instance_protocol, violations.protocol)
|
109
|
+
assert_same(nil, violations.class_violations)
|
110
|
+
assert_equal({ 'arity_empty' => 1 }, violations.instance_violations.table)
|
90
111
|
begin
|
91
|
-
@instance_protocol.send(
|
112
|
+
@instance_protocol.send(methods[:conforms], methods[:get_object].call(@error_instance))
|
92
113
|
flunk("Expected exception.")
|
93
|
-
rescue Safer::
|
94
|
-
|
95
|
-
assert_same(object_get.call(@error_instance), e.error_object)
|
96
|
-
assert_same(@instance_protocol, e.protocol)
|
97
|
-
assert_same(nil, e.class_violations)
|
98
|
-
assert_equal({ 'arity_empty' => 1 }, e.instance_violations.table)
|
114
|
+
rescue Safer::Protocol::Error => e
|
115
|
+
assert_equal(violations, e)
|
99
116
|
end
|
100
117
|
end
|
101
118
|
def test_arity
|
@@ -104,37 +121,41 @@ class TC_SaferProtocol < Test::Unit::TestCase
|
|
104
121
|
end
|
105
122
|
|
106
123
|
def run_classInstanceError(useinstance)
|
107
|
-
|
124
|
+
methods = get_methods(useinstance)
|
125
|
+
|
126
|
+
class_instance_violations = @instance_protocol.send(methods[:violations], methods[:get_object].call(@class_instance))
|
127
|
+
assert_same(class_instance_violations.class, methods[:error_type])
|
128
|
+
assert_same(class_instance_violations.error_object, methods[:get_object].call(@class_instance))
|
129
|
+
assert_same(class_instance_violations.protocol, @instance_protocol)
|
130
|
+
assert_same(nil, class_instance_violations.class_violations)
|
131
|
+
assert_equal(class_instance_violations.instance_violations.table.keys.size, @instance_protocol.instance_signature.table.keys.size)
|
132
|
+
@instance_protocol.instance_signature.table.each_pair do |key, arity|
|
133
|
+
assert(class_instance_violations.instance_violations.table.has_key?(key))
|
134
|
+
assert_same(class_instance_violations.instance_violations.table[key], true)
|
135
|
+
end
|
108
136
|
|
109
|
-
class_instance_error = nil
|
110
137
|
begin
|
111
|
-
@instance_protocol.send(
|
138
|
+
@instance_protocol.send(methods[:conforms], methods[:get_object].call(@class_instance))
|
112
139
|
flunk("Expected exception.")
|
113
|
-
rescue Safer::
|
114
|
-
|
115
|
-
assert_same(e.error_object, object_get.call(@class_instance))
|
116
|
-
assert_same(e.protocol, @instance_protocol)
|
117
|
-
assert_same(nil, e.class_violations)
|
118
|
-
assert_equal(e.instance_violations.table.keys.size, @instance_protocol.instance_signature.table.keys.size)
|
119
|
-
@instance_protocol.instance_signature.table.each_pair do |key, arity|
|
120
|
-
assert(e.instance_violations.table.has_key?(key))
|
121
|
-
assert_same(e.instance_violations.table[key], true)
|
122
|
-
end
|
123
|
-
class_instance_error = e
|
140
|
+
rescue Safer::Protocol::Error => e
|
141
|
+
assert_equal(e, class_instance_violations)
|
124
142
|
end
|
125
143
|
|
144
|
+
both_instance_violations = @both_protocol.send(methods[:violations], methods[:get_object].call(@class_instance))
|
145
|
+
assert_same(both_instance_violations.class, methods[:error_type])
|
146
|
+
assert_same(both_instance_violations.error_object, methods[:get_object].call(@class_instance))
|
147
|
+
assert_same(both_instance_violations.protocol, @both_protocol)
|
148
|
+
assert_equal(
|
149
|
+
both_instance_violations.instance_violations, class_instance_violations.instance_violations)
|
150
|
+
assert_same(
|
151
|
+
both_instance_violations.class_violations, class_instance_violations.class_violations)
|
152
|
+
assert_equal(both_instance_violations.report, class_instance_violations.report)
|
153
|
+
|
126
154
|
begin
|
127
|
-
@both_protocol.send(
|
155
|
+
@both_protocol.send(methods[:conforms], methods[:get_object].call(@class_instance))
|
128
156
|
flunk("Expected exception.")
|
129
|
-
rescue Safer::
|
130
|
-
|
131
|
-
assert_same(e.error_object, object_get.call(@class_instance))
|
132
|
-
assert_same(e.protocol, @both_protocol)
|
133
|
-
assert_equal(
|
134
|
-
e.instance_violations, class_instance_error.instance_violations)
|
135
|
-
assert_same(
|
136
|
-
e.class_violations, class_instance_error.class_violations)
|
137
|
-
assert_equal(e.report, class_instance_error.report)
|
157
|
+
rescue Safer::Protocol::Error => e
|
158
|
+
assert_equal(both_instance_violations, e)
|
138
159
|
end
|
139
160
|
end
|
140
161
|
def test_classInstanceError
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Aidan Cully
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-10-10 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -83,7 +83,7 @@ files:
|
|
83
83
|
- test/test_safer_ivar.rb
|
84
84
|
- test/test_safer_protocol.rb
|
85
85
|
has_rdoc: true
|
86
|
-
homepage: http://safer.rubyforge.org
|
86
|
+
homepage: http://safer.rubyforge.org
|
87
87
|
licenses: []
|
88
88
|
|
89
89
|
post_install_message:
|
@@ -112,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
112
|
version: "0"
|
113
113
|
requirements: []
|
114
114
|
|
115
|
-
rubyforge_project:
|
115
|
+
rubyforge_project: safer
|
116
116
|
rubygems_version: 1.3.7
|
117
117
|
signing_key:
|
118
118
|
specification_version: 3
|