contracts 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/builtin_contracts.rb +7 -0
- data/lib/contracts.rb +42 -15
- data/lib/decorators.rb +23 -6
- data/lib/foo.rb +10 -5
- metadata +3 -3
data/lib/builtin_contracts.rb
CHANGED
data/lib/contracts.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'decorators'
|
2
2
|
require 'builtin_contracts'
|
3
|
-
|
4
3
|
module Contracts
|
5
4
|
def self.included(base)
|
6
5
|
base.extend MethodDecorators
|
@@ -40,11 +39,11 @@ class Contract < Decorator
|
|
40
39
|
file, line = data[:method].source_location
|
41
40
|
position = file + ":" + line.to_s
|
42
41
|
end
|
43
|
-
|
42
|
+
method_name = data[:method].is_a?(Proc) ? "Proc" : data[:method].name
|
44
43
|
%{Contract violation:
|
45
44
|
Expected: #{expected},
|
46
45
|
Actual: #{data[:arg].inspect}
|
47
|
-
Value guarded in: #{data[:class]}::#{
|
46
|
+
Value guarded in: #{data[:class]}::#{method_name}
|
48
47
|
With Contract: #{data[:contracts].map { |t| t.is_a?(Class) ? t.name : t.class.name }.join(", ") }
|
49
48
|
At: #{position} }
|
50
49
|
end
|
@@ -99,6 +98,8 @@ class Contract < Decorator
|
|
99
98
|
validate_hash(arg, contract)
|
100
99
|
when Contracts::Args
|
101
100
|
valid? arg, contract.contract
|
101
|
+
when Func
|
102
|
+
arg.is_a?(Method) || arg.is_a?(Proc)
|
102
103
|
else
|
103
104
|
if contract.respond_to? :valid?
|
104
105
|
mkerror(contract.valid?(arg), arg, contract)
|
@@ -108,22 +109,41 @@ class Contract < Decorator
|
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
111
|
-
def call(
|
112
|
+
def call(*args, &blk)
|
113
|
+
call_with(nil, *args, &blk)
|
114
|
+
end
|
115
|
+
|
116
|
+
def call_with(this, *args, &blk)
|
112
117
|
_args = blk ? args + [blk] : args
|
113
118
|
if _args.size != @contracts.size - 1
|
114
119
|
# so it's not *args
|
115
|
-
|
120
|
+
unless @contracts.any? { |contract| contract.is_a? Contracts::Args }
|
116
121
|
raise %{The number of arguments doesn't match the number of contracts.
|
117
122
|
Did you forget to write a contract for the return value of the function?
|
118
123
|
Or if you want a variable number of arguments using *args, use the Args contract.
|
119
124
|
Args: #{args.inspect}
|
120
125
|
Contracts: #{@contracts.map { |t| t.is_a?(Class) ? t.name : t.class.name }.join(", ")}}
|
121
126
|
end
|
122
|
-
end
|
127
|
+
end
|
128
|
+
|
123
129
|
res = Contract.validate_all(_args, @contracts[0, @contracts.size - 1], @klass, @method)
|
124
130
|
return if res == false
|
125
131
|
|
126
|
-
|
132
|
+
# contracts on methods
|
133
|
+
|
134
|
+
contracts.each_with_index do |contract, i|
|
135
|
+
if contract.is_a? Func
|
136
|
+
args[i] = Contract.new(@klass, args[i], *contract.contracts)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
if @method.respond_to? :bind
|
141
|
+
# instance method
|
142
|
+
result = @method.bind(this).call(*args, &blk)
|
143
|
+
else
|
144
|
+
# class method
|
145
|
+
result = @method.call(*args, &blk)
|
146
|
+
end
|
127
147
|
|
128
148
|
if args.size == @contracts.size - 1
|
129
149
|
Contract.validate(result, @contracts[-1], @klass, @method, @contracts)
|
@@ -161,18 +181,25 @@ Contracts: #{@contracts.map { |t| t.is_a?(Class) ? t.name : t.class.name }.join(
|
|
161
181
|
mkerror(valid, arg, contract)
|
162
182
|
end
|
163
183
|
|
164
|
-
def self.validate_all(
|
165
|
-
# we assume that any mismatch in # of
|
184
|
+
def self.validate_all(params, contracts, klass, method)
|
185
|
+
# we assume that any mismatch in # of params/contracts
|
166
186
|
# has been checked befoer this point.
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
187
|
+
args_index = contracts.index do |contract|
|
188
|
+
contract.is_a? Contracts::Args
|
189
|
+
end
|
190
|
+
if args_index
|
191
|
+
# there is a *args at this index.
|
192
|
+
# Now we need to see how many arguments this contract
|
193
|
+
# accounts for and just duplicate the contract for all
|
194
|
+
# of those args.
|
195
|
+
args_contract = contracts[args_index]
|
196
|
+
while contracts.size < params.size
|
197
|
+
contracts.insert(args_index, args_contract.dup)
|
171
198
|
end
|
172
199
|
end
|
173
200
|
|
174
|
-
|
175
|
-
result = validate(
|
201
|
+
params.zip(contracts).each do |param, contract|
|
202
|
+
result = validate(param, contract, klass, method, contracts)
|
176
203
|
return result if result == false
|
177
204
|
end
|
178
205
|
end
|
data/lib/decorators.rb
CHANGED
@@ -11,6 +11,16 @@ module MethodDecorators
|
|
11
11
|
# to find the decorator for that method. This is how we associate decorators
|
12
12
|
# with methods.
|
13
13
|
def method_added(name)
|
14
|
+
common_method_added name, false
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def singleton_method_added name
|
19
|
+
common_method_added name, true
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def common_method_added name, is_class_method
|
14
24
|
return unless @decorators
|
15
25
|
|
16
26
|
decorators = @decorators.dup
|
@@ -24,7 +34,15 @@ module MethodDecorators
|
|
24
34
|
# a reference to the method gets passed into the contract here. This is good because
|
25
35
|
# we are going to redefine this method with a new name below...so this reference is
|
26
36
|
# now the *only* reference to the old method that exists.
|
27
|
-
|
37
|
+
if klass.respond_to? :new
|
38
|
+
if is_class_method
|
39
|
+
decorator = klass.new(self, method(name), *args)
|
40
|
+
else
|
41
|
+
decorator = klass.new(self, instance_method(name), *args)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
decorator = klass
|
45
|
+
end
|
28
46
|
@decorated_methods[name] << decorator
|
29
47
|
end
|
30
48
|
|
@@ -33,16 +51,15 @@ module MethodDecorators
|
|
33
51
|
# The decorator in turn has a reference to the actual method, so it can call it
|
34
52
|
# on its own, after doing it's decorating of course.
|
35
53
|
class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
|
36
|
-
def #{name}(*args, &blk)
|
54
|
+
def #{is_class_method ? "self." : ""}#{name}(*args, &blk)
|
37
55
|
ret = nil
|
38
|
-
self
|
39
|
-
ret = decorator.
|
56
|
+
self.#{is_class_method ? "" : "class."}decorated_methods[#{name.inspect}].each do |decorator|
|
57
|
+
ret = decorator.call_with(self, *args, &blk)
|
40
58
|
end
|
41
59
|
ret
|
42
60
|
end
|
43
61
|
ruby_eval
|
44
|
-
|
45
|
-
end
|
62
|
+
end
|
46
63
|
|
47
64
|
def decorate(klass, *args)
|
48
65
|
@decorators ||= []
|
data/lib/foo.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
require 'contracts'
|
2
2
|
use_contracts(self)
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
return 1, 2
|
4
|
+
add = Proc.new do |x|
|
5
|
+
1
|
7
6
|
end
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
Contract ArrayOf[Num], Func[String, Num], ArrayOf[Num]
|
9
|
+
def map nums, func
|
10
|
+
nums.map do |num|
|
11
|
+
func.call(num)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
p map([1, 2, 3], add)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contracts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 8
|
10
|
+
version: 0.0.8
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Aditya Bhargava
|