contracts 0.0.7 → 0.0.8
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 +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
|