safer 0.1.0 → 0.2.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 +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
|