contracts 0.3 → 0.4
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/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.
|