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.
@@ -352,4 +352,11 @@ module Contracts
352
352
  ('a'..'z').to_a.shuffle[0, 10].join
353
353
  end
354
354
  end
355
+
356
+ class Func < CallableClass
357
+ attr_reader :contracts
358
+ def initialize(*contracts)
359
+ @contracts = contracts
360
+ end
361
+ end
355
362
  end
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]}::#{data[:method].name}
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(this, *args, &blk)
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
- if !@contracts[-2].is_a? Contracts::Args
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
- result = @method.bind(this).call(*args, &blk)
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(args, contracts, klass, method)
165
- # we assume that any mismatch in # of args/contracts
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
- if args.size != contracts.size
168
- # assumed: contracts[-1].is_a? Args
169
- while contracts.size < args.size
170
- contracts << contracts[-1].dup
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
- args.zip(contracts).each do |arg, contract|
175
- result = validate(arg, contract, klass, method, contracts)
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
- decorator = klass.respond_to?(:new) ? klass.new(self, instance_method(name), *args) : klass
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.class.decorated_methods[#{name.inspect}].each do |decorator|
39
- ret = decorator.call(self, *args, &blk)
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
- super
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
- Contract [Num, String]
5
- def mult
6
- return 1, 2
4
+ add = Proc.new do |x|
5
+ 1
7
6
  end
8
7
 
9
- f = mult
10
- p f
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: 17
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 7
10
- version: 0.0.7
9
+ - 8
10
+ version: 0.0.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Aditya Bhargava