contracts 0.3 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/builtin_contracts.rb +28 -6
- data/lib/contracts.rb +14 -7
- data/lib/decorators.rb +46 -5
- metadata +4 -4
data/lib/builtin_contracts.rb
CHANGED
@@ -218,22 +218,23 @@ module Contracts
|
|
218
218
|
end
|
219
219
|
end
|
220
220
|
|
221
|
-
# Takes a class +A+. If argument
|
222
|
-
#
|
223
|
-
|
221
|
+
# Takes a class +A+. If argument is an object of type +A+, the contract passes.
|
222
|
+
# If it is a subclass of A (or not related to A in any way), it fails.
|
223
|
+
# Example: <tt>Exactly[Numeric]</tt>
|
224
|
+
class Exactly < CallableClass
|
224
225
|
def initialize(cls)
|
225
226
|
@cls = cls
|
226
227
|
end
|
227
228
|
|
228
229
|
def valid?(val)
|
229
|
-
val.
|
230
|
+
val.class == @cls
|
230
231
|
end
|
231
232
|
|
232
233
|
def to_s
|
233
|
-
"
|
234
|
+
"exactly #{@cls.inspect}"
|
234
235
|
end
|
235
236
|
end
|
236
|
-
|
237
|
+
|
237
238
|
# Takes a variable number of contracts. The contract
|
238
239
|
# passes if all of those contracts fail for the given argument.
|
239
240
|
# Example: <tt>Not[nil]</tt>
|
@@ -313,6 +314,27 @@ module Contracts
|
|
313
314
|
end
|
314
315
|
end
|
315
316
|
|
317
|
+
# Use this to specify the Hash characteristics. Takes two contracts,
|
318
|
+
# one for hash keys and one for hash values.
|
319
|
+
# Example: <tt>HashOf[Symbol, String]</tt>
|
320
|
+
class HashOf < CallableClass
|
321
|
+
def initialize(key, value)
|
322
|
+
@key = key
|
323
|
+
@value = value
|
324
|
+
end
|
325
|
+
|
326
|
+
def valid?(hash)
|
327
|
+
keys_match = hash.keys.map {|k| Contract.valid?(k, @key) }.all?
|
328
|
+
vals_match = hash.values.map {|v| Contract.valid?(v, @value) }.all?
|
329
|
+
|
330
|
+
[keys_match, vals_match].all?
|
331
|
+
end
|
332
|
+
|
333
|
+
def to_s
|
334
|
+
"Hash<#{@key.to_s}, #{@value.to_s}>"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
316
338
|
# Takes a Contract.
|
317
339
|
# The contract passes if the contract passes or the given value is nil.
|
318
340
|
# Maybe(foo) is equivalent to Or[foo, nil].
|
data/lib/contracts.rb
CHANGED
@@ -21,7 +21,7 @@ module Contracts
|
|
21
21
|
if contracts.nil?
|
22
22
|
"No contract for #{self}.#{funcname}"
|
23
23
|
else
|
24
|
-
"#{funcname} :: #{contracts}"
|
24
|
+
"#{funcname} :: #{contracts[0]}"
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -35,7 +35,7 @@ module Contracts
|
|
35
35
|
if contracts.nil?
|
36
36
|
"No contract for #{self.class}.#{funcname}"
|
37
37
|
else
|
38
|
-
"#{funcname} :: #{contracts}"
|
38
|
+
"#{funcname} :: #{contracts[0]}"
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -100,7 +100,14 @@ class Contract < Decorator
|
|
100
100
|
position = file + ":" + line.to_s
|
101
101
|
end
|
102
102
|
method_name = data[:method].is_a?(Proc) ? "Proc" : data[:method].name
|
103
|
-
|
103
|
+
|
104
|
+
header = if data[:return_value]
|
105
|
+
"Contract violation for return value:"
|
106
|
+
else
|
107
|
+
"Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:"
|
108
|
+
end
|
109
|
+
|
110
|
+
%{#{header}
|
104
111
|
Expected: #{expected},
|
105
112
|
Actual: #{data[:arg].inspect}
|
106
113
|
Value guarded in: #{data[:class]}::#{method_name}
|
@@ -177,7 +184,7 @@ class Contract < Decorator
|
|
177
184
|
if contract.respond_to? :valid?
|
178
185
|
lambda { |arg| contract.valid?(arg) }
|
179
186
|
elsif klass == Class
|
180
|
-
lambda { |arg|
|
187
|
+
lambda { |arg| arg.is_a?(contract) }
|
181
188
|
else
|
182
189
|
lambda { |arg| contract == arg }
|
183
190
|
end
|
@@ -204,14 +211,14 @@ class Contract < Decorator
|
|
204
211
|
j = i < last_index ? i : last_index
|
205
212
|
#unless true #@args_contracts[i].valid?(args[i])
|
206
213
|
unless @args_validators[j][_args[i]]
|
207
|
-
call_function = Contract.failure_callback({:arg => _args[i], :contract => @args_contracts[j], :class => @klass, :method => @method, :contracts => self})
|
214
|
+
call_function = Contract.failure_callback({:arg => _args[i], :contract => @args_contracts[j], :class => @klass, :method => @method, :contracts => self, :arg_pos => i+1, :total_args => _args.size})
|
208
215
|
return unless call_function
|
209
216
|
end
|
210
217
|
end
|
211
218
|
|
212
219
|
if @has_func_contracts
|
213
220
|
# contracts on methods
|
214
|
-
|
221
|
+
@args_contracts.each_with_index do |contract, i|
|
215
222
|
if contract.is_a? Contracts::Func
|
216
223
|
args[i] = Contract.new(@klass, args[i], *contract.contracts)
|
217
224
|
end
|
@@ -226,7 +233,7 @@ class Contract < Decorator
|
|
226
233
|
@method.call(*args, &blk)
|
227
234
|
end
|
228
235
|
unless @ret_validator[result]
|
229
|
-
Contract.failure_callback({:arg => result, :contract => @ret_contract, :class => @klass, :method => @method, :contracts => self})
|
236
|
+
Contract.failure_callback({:arg => result, :contract => @ret_contract, :class => @klass, :method => @method, :contracts => self, :return_value => true})
|
230
237
|
end
|
231
238
|
result
|
232
239
|
end
|
data/lib/decorators.rb
CHANGED
@@ -40,12 +40,14 @@ module MethodDecorators
|
|
40
40
|
decorator = klass.new(self, method(name), *args)
|
41
41
|
@decorated_methods[:class_methods][name] ||= []
|
42
42
|
@decorated_methods[:class_methods][name] << decorator
|
43
|
-
|
43
|
+
# private_instance_methods is an array of strings on 1.8 and an array of symbols on 1.9
|
44
|
+
is_private = self.private_methods.include?(name) || self.private_methods.include?(name.to_s)
|
44
45
|
else
|
45
46
|
decorator = klass.new(self, instance_method(name), *args)
|
46
47
|
@decorated_methods[:instance_methods][name] ||= []
|
47
48
|
@decorated_methods[:instance_methods][name] << decorator
|
48
|
-
|
49
|
+
# private_instance_methods is an array of strings on 1.8 and an array of symbols on 1.9
|
50
|
+
is_private = self.private_instance_methods.include?(name) || self.private_instance_methods.include?(name.to_s)
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
@@ -53,9 +55,46 @@ module MethodDecorators
|
|
53
55
|
# just calls the decorator passing in all args that were to be passed into the method.
|
54
56
|
# The decorator in turn has a reference to the actual method, so it can call it
|
55
57
|
# on its own, after doing it's decorating of course.
|
56
|
-
|
58
|
+
|
59
|
+
=begin
|
60
|
+
Very important: THe line `current = #{self}` in the start is crucial.
|
61
|
+
Not having it means that any method that used contracts could NOT use `super`
|
62
|
+
(see this issue for example: https://github.com/egonSchiele/contracts.ruby/issues/27).
|
63
|
+
Here's why: Suppose you have this code:
|
64
|
+
|
65
|
+
class Foo
|
66
|
+
Contract nil => String
|
67
|
+
def to_s
|
68
|
+
"Foo"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Bar < Foo
|
73
|
+
Contract nil => String
|
74
|
+
def to_s
|
75
|
+
super + "Bar"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
b = Bar.new
|
80
|
+
p b.to_s
|
81
|
+
|
82
|
+
`to_s` in Bar calls `super`. So you expect this to call `Foo`'s to_s. However,
|
83
|
+
we have overwritten the function (that's what this next defn is). So it gets a
|
84
|
+
reference to the function to call by looking at `decorated_methods`.
|
85
|
+
|
86
|
+
Now, this line used to read something like:
|
87
|
+
|
88
|
+
current = self#{is_class_method ? "" : ".class"}
|
89
|
+
|
90
|
+
In that case, `self` would always be `Bar`, regardless of whether you were calling
|
91
|
+
Foo's to_s or Bar's to_s. So you would keep getting Bar's decorated_methods, which
|
92
|
+
means you would always call Bar's to_s...infinite recursion! Instead, you want to
|
93
|
+
call Foo's version of decorated_methods. So the line needs to be `current = #{self}`.
|
94
|
+
=end
|
95
|
+
method_def = %{
|
57
96
|
def #{is_class_method ? "self." : ""}#{name}(*args, &blk)
|
58
|
-
current =
|
97
|
+
current = #{self}
|
59
98
|
ancestors = current.ancestors
|
60
99
|
ancestors.shift # first one is just the class itself
|
61
100
|
while current && !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
|
@@ -86,7 +125,9 @@ module MethodDecorators
|
|
86
125
|
result
|
87
126
|
end
|
88
127
|
#{is_private ? "private #{name.inspect}" : ""}
|
89
|
-
|
128
|
+
}
|
129
|
+
|
130
|
+
class_eval method_def, __FILE__, __LINE__ + 1
|
90
131
|
end
|
91
132
|
|
92
133
|
def decorate(klass, *args)
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contracts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 3
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
8
|
+
- 4
|
9
|
+
version: "0.4"
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Aditya Bhargava
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2014-05-08 00:00:00 Z
|
18
18
|
dependencies: []
|
19
19
|
|
20
20
|
description: This library provides contracts for Ruby. Contracts let you clearly express how your code behaves, and free you from writing tons of boilerplate, defensive code.
|