contracts 0.10.1 → 0.11.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +9 -1
- data/TUTORIAL.md +51 -34
- data/lib/contracts.rb +10 -38
- data/lib/contracts/builtin_contracts.rb +16 -0
- data/lib/contracts/call_with.rb +3 -2
- data/lib/contracts/core.rb +45 -0
- data/lib/contracts/validators.rb +6 -0
- data/lib/contracts/version.rb +1 -1
- data/spec/builtin_contracts_spec.rb +36 -4
- data/spec/contracts_spec.rb +12 -2
- data/spec/fixtures/fixtures.rb +103 -84
- data/spec/module_spec.rb +2 -2
- data/spec/override_validators_spec.rb +4 -4
- data/spec/ruby_version_specific/contracts_spec_1.9.rb +2 -2
- data/spec/ruby_version_specific/contracts_spec_2.0.rb +2 -2
- data/spec/ruby_version_specific/contracts_spec_2.1.rb +4 -4
- data/spec/validators_spec.rb +25 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 588796ed6d26a1394810a86debd6f0b285dfb7e1
|
4
|
+
data.tar.gz: bff5d90964ae5bb9911b66236c5898ad741c159b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 678e7911e3dee12d273a8839712583bd0cc1272bde6da8aa8bdc27685751a66c9c9a25c39b1182c6056ff1d8bce191b8982783a18abadeff2526501040f61133
|
7
|
+
data.tar.gz: 5c8b7cd54ebff1b8d9d75393a2c1f0d45df096c295c3387eefd54a1a01695afe8fe1ef4975bc09705d1a6d081761fe42ffb8512d926df6cc0104e10f764844c6
|
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## v0.11.0
|
2
|
+
- Enhancement: add `include Contracts::Core` that doesn't pollute the namespace as much as `include Contracts` - [Oleksii Federov](https://github.com/waterlink) [#185](https://github.com/egonSchiele/contracts.ruby/pull/185)
|
3
|
+
- Bugfix: fail if a non-hash is provided to a `HashOf` contract - [Abe Voelker](https://github.com/abevoelker) [#190](https://github.com/egonSchiele/contracts.ruby/pull/190)
|
4
|
+
- Bugfix: bugfix for using varargs and `Maybe[Proc]` together - [Adit Bhargava](https://github.com/egonSchiele) [#188](https://github.com/egonSchiele/contracts.ruby/pull/188)
|
5
|
+
- Bugfix: make KeywordArgs fail if unexpected keys are passed in - [Abe Voelker](https://github.com/abevoelker) [#187](https://github.com/egonSchiele/contracts.ruby/pull/187)
|
6
|
+
- Feature: range contract added - [Oleksii Fedorov](https://github.com/waterlink) [#184](https://github.com/egonSchiele/contracts.ruby/pull/184)
|
7
|
+
- Feature: enum contract added - [Dennis Günnewig](https://github.com/dg-ratiodata) [#181](https://github.com/egonSchiele/contracts.ruby/pull/181)
|
8
|
+
|
1
9
|
## v0.10.1
|
2
10
|
|
3
11
|
- Enhancement: make `@pattern_match` instance variable not render ruby warning. Required to use new aruba versions in rspec tests - [Dennis Günnewig](https://github.com/dg-ratiodata) [#179](https://github.com/egonSchiele/contracts.ruby/pull/179)
|
@@ -18,7 +26,7 @@
|
|
18
26
|
- MAJOR fix in pattern-matching: If the return contract for a pattern-matched function fails, it should NOT try the next pattern-match function. Pattern-matching is only for params, not return values.
|
19
27
|
- raise an error if multiple defns have the same contract for pattern matching.
|
20
28
|
|
21
|
-
- New syntax for functions with no input params (the old style still works)
|
29
|
+
- New syntax for functions with no input params (the old style still works)
|
22
30
|
Old way:
|
23
31
|
```ruby
|
24
32
|
Contract nil => 1
|
data/TUTORIAL.md
CHANGED
@@ -17,7 +17,7 @@ contracts.ruby brings code contracts to the Ruby language. Code contracts allow
|
|
17
17
|
A simple example:
|
18
18
|
|
19
19
|
```ruby
|
20
|
-
Contract Num, Num => Num
|
20
|
+
Contract Contracts::Num, Contracts::Num => Contracts::Num
|
21
21
|
def add(a, b)
|
22
22
|
a + b
|
23
23
|
end
|
@@ -31,9 +31,9 @@ Copy this code into a file and run it:
|
|
31
31
|
require 'contracts'
|
32
32
|
|
33
33
|
class Math
|
34
|
-
include Contracts
|
34
|
+
include Contracts::Core
|
35
35
|
|
36
|
-
Contract Num, Num => Num
|
36
|
+
Contract Contracts::Num, Contracts::Num => Contracts::Num
|
37
37
|
def self.add(a, b)
|
38
38
|
a + b
|
39
39
|
end
|
@@ -88,6 +88,7 @@ contracts.ruby comes with a lot of built-in contracts, including the following:
|
|
88
88
|
* [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]`
|
89
89
|
* [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]`
|
90
90
|
* [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]`
|
91
|
+
* [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]`
|
91
92
|
|
92
93
|
* Keyword arguments
|
93
94
|
* [`KeywordArgs`](http://www.rubydoc.info/gems/contracts/Contracts/KeywordArgs) – checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts, e.g. `KeywordArgs[:number => Num, :description => Optional[String]]`
|
@@ -104,6 +105,21 @@ contracts.ruby comes with a lot of built-in contracts, including the following:
|
|
104
105
|
|
105
106
|
To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts).
|
106
107
|
|
108
|
+
It is recommended to use shortcut for referring builtin contracts:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# define shortcut somewhere at the top level of your codebase:
|
112
|
+
C = Contracts
|
113
|
+
|
114
|
+
# and use it:
|
115
|
+
Contract C::Maybe[C::Num], String => C::Num
|
116
|
+
```
|
117
|
+
|
118
|
+
Shortcut name should not be necessary `C`, can be anything that you are comfort
|
119
|
+
with while typing and anything that does not conflict with libraries you use.
|
120
|
+
|
121
|
+
All examples after this point assume you have chosen a shortcut as `C::`.
|
122
|
+
|
107
123
|
## More Examples
|
108
124
|
|
109
125
|
### Hello, World
|
@@ -126,7 +142,7 @@ You always need to specify a contract for the return value. In this example, `he
|
|
126
142
|
### A Double Function
|
127
143
|
|
128
144
|
```ruby
|
129
|
-
Contract Or[Fixnum, Float] => Or[Fixnum, Float]
|
145
|
+
Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float]
|
130
146
|
def double(x)
|
131
147
|
2 * x
|
132
148
|
end
|
@@ -136,19 +152,19 @@ Sometimes you want to be able to choose between a few contracts. `Or` takes a va
|
|
136
152
|
This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[Fixnum, Float]` is. The longer way to write it would have been:
|
137
153
|
|
138
154
|
```ruby
|
139
|
-
Contract Or.new(Fixnum, Float) => Or.new(Fixnum, Float)
|
155
|
+
Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float)
|
140
156
|
```
|
141
157
|
|
142
158
|
All the built-in contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write
|
143
159
|
|
144
160
|
```ruby
|
145
|
-
Contract Or[Fixnum, Float] => Or[Fixnum, Float]
|
161
|
+
Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float]
|
146
162
|
```
|
147
163
|
|
148
164
|
or
|
149
165
|
|
150
166
|
```ruby
|
151
|
-
Contract Or.new(Fixnum, Float) => Or.new(Fixnum, Float)
|
167
|
+
Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float)
|
152
168
|
```
|
153
169
|
|
154
170
|
whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `Fixnum` and `Float`. Use that instance to validate the argument.
|
@@ -156,7 +172,7 @@ whichever you prefer. They both mean the same thing here: make a new instance of
|
|
156
172
|
### A Product Function
|
157
173
|
|
158
174
|
```ruby
|
159
|
-
Contract ArrayOf[Num] => Num
|
175
|
+
Contract C::ArrayOf[C::Num] => C::Num
|
160
176
|
def product(vals)
|
161
177
|
total = 1
|
162
178
|
vals.each do |val|
|
@@ -179,7 +195,7 @@ product([1, 2, 3, "foo"])
|
|
179
195
|
### Another Product Function
|
180
196
|
|
181
197
|
```ruby
|
182
|
-
Contract Args[Num] => Num
|
198
|
+
Contract C::Args[C::Num] => C::Num
|
183
199
|
def product(*vals)
|
184
200
|
total = 1
|
185
201
|
vals.each do |val|
|
@@ -203,7 +219,7 @@ If an array is one of the arguments and you know how many elements it's going to
|
|
203
219
|
|
204
220
|
```ruby
|
205
221
|
# a function that takes an array of two elements...a person's age and a person's name.
|
206
|
-
Contract [Num, String] => nil
|
222
|
+
Contract [C::Num, String] => nil
|
207
223
|
def person(data)
|
208
224
|
p data
|
209
225
|
end
|
@@ -217,7 +233,7 @@ Here's a contract that requires a Hash. We can put contracts on each of the keys
|
|
217
233
|
|
218
234
|
```ruby
|
219
235
|
# note the parentheses around the hash; without those you would get a syntax error
|
220
|
-
Contract ({ :age => Num, :name => String }) => nil
|
236
|
+
Contract ({ :age => C::Num, :name => String }) => nil
|
221
237
|
def person(data)
|
222
238
|
p data
|
223
239
|
end
|
@@ -241,7 +257,7 @@ even though we don't specify a type for `:foo`.
|
|
241
257
|
Peruse this contract on the keys and values of a Hash.
|
242
258
|
|
243
259
|
```ruby
|
244
|
-
Contract HashOf[Symbol, Num] => Num
|
260
|
+
Contract C::HashOf[Symbol, C::Num] => C::Num
|
245
261
|
def give_largest_value(hsh)
|
246
262
|
hsh.values.max
|
247
263
|
end
|
@@ -269,14 +285,14 @@ def connect(host, port:, user:, password:)
|
|
269
285
|
You can of course put `Hash` contract on it:
|
270
286
|
|
271
287
|
```ruby
|
272
|
-
Contract String, { :port => Num, :user => String, :password => String } => Connection
|
288
|
+
Contract String, { :port => C::Num, :user => String, :password => String } => Connection
|
273
289
|
def connect(host, port:, user:, password:)
|
274
290
|
```
|
275
291
|
|
276
292
|
But this will not quite work if you want to have a default values:
|
277
293
|
|
278
294
|
```ruby
|
279
|
-
Contract String, { :port => Num, :user => String, :password => String } => Connection
|
295
|
+
Contract String, { :port => C::Num, :user => String, :password => String } => Connection
|
280
296
|
def connect(host, port: 5000, user:, password:)
|
281
297
|
# ...
|
282
298
|
end
|
@@ -296,13 +312,13 @@ ContractError: Contract violation for argument 2 of 2:
|
|
296
312
|
At: (irb):12
|
297
313
|
```
|
298
314
|
|
299
|
-
This can be fixed with contract `{ :port => Maybe[Num], ... }`, but that will
|
315
|
+
This can be fixed with contract `{ :port => C::Maybe[C::Num], ... }`, but that will
|
300
316
|
allow `nil` to be passed in, which is not the original intent.
|
301
317
|
|
302
318
|
So that is where `KeywordArgs` and `Optional` contracts jump in:
|
303
319
|
|
304
320
|
```ruby
|
305
|
-
Contract String, KeywordArgs[ :port => Optional[Num], :user => String, :password => String ] => Connection
|
321
|
+
Contract String, C::KeywordArgs[ :port => C::Optional[C::Num], :user => String, :password => String ] => Connection
|
306
322
|
def connect(host, port: 5000, user:, password:)
|
307
323
|
```
|
308
324
|
|
@@ -319,7 +335,7 @@ def map(arr, func)
|
|
319
335
|
`map` takes an array, and a function. Suppose you want to add a contract to this function. You could try this:
|
320
336
|
|
321
337
|
```ruby
|
322
|
-
Contract ArrayOf[Any], Proc => ArrayOf[Any]
|
338
|
+
Contract C::ArrayOf[C::Any], Proc => C::ArrayOf[C::Any]
|
323
339
|
def map(arr, func)
|
324
340
|
```
|
325
341
|
|
@@ -334,7 +350,7 @@ But suppose you want to have a contract on the Proc too! Suppose you want to mak
|
|
334
350
|
Here's a `map` function that requires an array of numbers, and a function that takes a number and returns a number:
|
335
351
|
|
336
352
|
```ruby
|
337
|
-
Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]
|
353
|
+
Contract C::ArrayOf[C::Num], C::Func[C::Num => C::Num] => C::ArrayOf[C::Num]
|
338
354
|
def map(arr, func)
|
339
355
|
ret = []
|
340
356
|
arr.each do |x|
|
@@ -362,7 +378,7 @@ def map(arr, &block)
|
|
362
378
|
NOTE: This is not valid:
|
363
379
|
|
364
380
|
```ruby
|
365
|
-
Contract ArrayOf[Num], Func => ArrayOf[Num]
|
381
|
+
Contract C::ArrayOf[C::Num], C::Func => C::ArrayOf[C::Num]
|
366
382
|
def map(arr, &func)
|
367
383
|
```
|
368
384
|
|
@@ -372,7 +388,7 @@ Here I am using `Func` without specifying a contract, like `Func[Num => Num]`. T
|
|
372
388
|
Treat the return value as an array. For example, here's a function that returns two numbers:
|
373
389
|
|
374
390
|
```ruby
|
375
|
-
Contract Num => [Num, Num]
|
391
|
+
Contract C::Num => [C::Num, C::Num]
|
376
392
|
def mult(x)
|
377
393
|
return x, x+1
|
378
394
|
end
|
@@ -383,7 +399,7 @@ end
|
|
383
399
|
If you use a contract a lot, it's a good idea to give it a meaningful synonym that tells the reader more about what your code returns. For example, suppose you have many functions that return a `Hash` or `nil`. If a `Hash` is returned, it contains information about a person. Your contact might look like this:
|
384
400
|
|
385
401
|
```ruby
|
386
|
-
Contract String => Or[Hash, nil]
|
402
|
+
Contract String => C::Or[Hash, nil]
|
387
403
|
def some_func(str)
|
388
404
|
```
|
389
405
|
|
@@ -415,7 +431,7 @@ The first two don't need any extra work to define: you can just use any constant
|
|
415
431
|
### A Proc
|
416
432
|
|
417
433
|
```ruby
|
418
|
-
Contract lambda { |x| x.is_a? Numeric } => Num
|
434
|
+
Contract lambda { |x| x.is_a? Numeric } => C::Num
|
419
435
|
def double(x)
|
420
436
|
```
|
421
437
|
|
@@ -460,7 +476,7 @@ The `Or` contract takes a sequence of contracts, and passes if any of them pass.
|
|
460
476
|
This class inherits from `CallableClass`, which allows us to use `[]` when using the class:
|
461
477
|
|
462
478
|
```ruby
|
463
|
-
Contract Or[Fixnum, Float] => Num
|
479
|
+
Contract C::Or[Fixnum, Float] => C::Num
|
464
480
|
def double(x)
|
465
481
|
2 * x
|
466
482
|
end
|
@@ -469,7 +485,7 @@ end
|
|
469
485
|
Without `CallableClass`, we would have to use `.new` instead:
|
470
486
|
|
471
487
|
```ruby
|
472
|
-
Contract Or.new(Fixnum, Float) => Num
|
488
|
+
Contract C::Or.new(Fixnum, Float) => C::Num
|
473
489
|
def double(x)
|
474
490
|
# etc
|
475
491
|
```
|
@@ -543,10 +559,11 @@ Possible validator overrides:
|
|
543
559
|
|
544
560
|
- `override_validator(MyCustomContract)` - allows to add some special behaviour for custom contracts,
|
545
561
|
- `override_validator(Proc)` - e.g. `lambda { true }`,
|
546
|
-
- `override_validator(Array)` - e.g. `[Num, String]`,
|
547
|
-
- `override_validator(Hash)` - e.g. `{ :a => Num, :b => String }`,
|
548
|
-
- `override_validator(
|
549
|
-
- `override_validator(Contracts::
|
562
|
+
- `override_validator(Array)` - e.g. `[C::Num, String]`,
|
563
|
+
- `override_validator(Hash)` - e.g. `{ :a => C::Num, :b => String }`,
|
564
|
+
- `override_validator(Range)` - e.g. `(1..10)`,
|
565
|
+
- `override_validator(Contracts::Args)` - e.g. `C::Args[C::Num]`,
|
566
|
+
- `override_validator(Contracts::Func)` - e.g. `C::Func[C::Num => C::Num]`,
|
550
567
|
- `override_validator(:valid)` - allows to override how contracts that respond to `:valid?` are handled,
|
551
568
|
- `override_validator(:class)` - allows to override how class/module contract constants are handled,
|
552
569
|
- `override_validator(:default)` - otherwise, raw value contracts.
|
@@ -564,7 +581,7 @@ You can use contracts for method overloading! This is commonly called "pattern m
|
|
564
581
|
For example, here's a factorial function without method overloading:
|
565
582
|
|
566
583
|
```ruby
|
567
|
-
Contract Num => Num
|
584
|
+
Contract C::Num => C::Num
|
568
585
|
def fact x
|
569
586
|
if x == 1
|
570
587
|
x
|
@@ -582,7 +599,7 @@ def fact x
|
|
582
599
|
x
|
583
600
|
end
|
584
601
|
|
585
|
-
Contract Num => Num
|
602
|
+
Contract C::Num => C::Num
|
586
603
|
def fact x
|
587
604
|
x * fact(x - 1)
|
588
605
|
end
|
@@ -608,7 +625,7 @@ end
|
|
608
625
|
Note that the second `get_ticket` contract above could have been simplified to:
|
609
626
|
|
610
627
|
```ruby
|
611
|
-
Contract Num => Ticket
|
628
|
+
Contract C::Num => Ticket
|
612
629
|
```
|
613
630
|
|
614
631
|
This is because the first contract eliminated the possibility of `age` being less than 12. However, the simpler contract is less explicit; you may want to "spell out" the age condition for clarity, especially if the method is overloaded with many contracts.
|
@@ -619,7 +636,7 @@ Usage is the same as contracts in classes:
|
|
619
636
|
|
620
637
|
```ruby
|
621
638
|
module M
|
622
|
-
include Contracts
|
639
|
+
include Contracts::Core
|
623
640
|
|
624
641
|
Contract String => String
|
625
642
|
def self.parse
|
@@ -638,13 +655,13 @@ A simple example:
|
|
638
655
|
|
639
656
|
```ruby
|
640
657
|
class MyBirthday < Struct.new(:day, :month)
|
641
|
-
include Contracts
|
658
|
+
include Contracts::Core
|
642
659
|
include Contracts::Invariants
|
643
660
|
|
644
661
|
invariant(:day) { 1 <= day && day <= 31 }
|
645
662
|
invariant(:month) { 1 <= month && month <= 12 }
|
646
663
|
|
647
|
-
Contract None => Fixnum
|
664
|
+
Contract C::None => Fixnum
|
648
665
|
def silly_next_day!
|
649
666
|
self.day += 1
|
650
667
|
end
|
data/lib/contracts.rb
CHANGED
@@ -9,48 +9,15 @@ require "contracts/engine"
|
|
9
9
|
require "contracts/method_handler"
|
10
10
|
require "contracts/validators"
|
11
11
|
require "contracts/call_with"
|
12
|
+
require "contracts/core"
|
12
13
|
|
13
14
|
module Contracts
|
14
15
|
def self.included(base)
|
15
|
-
|
16
|
+
base.send(:include, Core)
|
16
17
|
end
|
17
18
|
|
18
19
|
def self.extended(base)
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.common(base)
|
23
|
-
return if base.respond_to?(:Contract)
|
24
|
-
|
25
|
-
base.extend(MethodDecorators)
|
26
|
-
|
27
|
-
base.instance_eval do
|
28
|
-
def functype(funcname)
|
29
|
-
contracts = Engine.fetch_from(self).decorated_methods_for(:class_methods, funcname)
|
30
|
-
if contracts.nil?
|
31
|
-
"No contract for #{self}.#{funcname}"
|
32
|
-
else
|
33
|
-
"#{funcname} :: #{contracts[0]}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
base.class_eval do
|
39
|
-
# TODO: deprecate
|
40
|
-
# Required when contracts are included in global scope
|
41
|
-
def Contract(*args)
|
42
|
-
self.class.Contract(*args)
|
43
|
-
end
|
44
|
-
|
45
|
-
def functype(funcname)
|
46
|
-
contracts = Engine.fetch_from(self.class).decorated_methods_for(:instance_methods, funcname)
|
47
|
-
if contracts.nil?
|
48
|
-
"No contract for #{self.class}.#{funcname}"
|
49
|
-
else
|
50
|
-
"#{funcname} :: #{contracts[0]}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
20
|
+
base.send(:extend, Core)
|
54
21
|
end
|
55
22
|
end
|
56
23
|
|
@@ -118,6 +85,7 @@ class Contract < Contracts::Decorator
|
|
118
85
|
maybe_a_proc = last_contract.is_a?(Contracts::Maybe) && last_contract.include_proc?
|
119
86
|
|
120
87
|
@has_proc_contract = is_a_proc || maybe_a_proc || last_contract.is_a?(Contracts::Func)
|
88
|
+
|
121
89
|
# ====
|
122
90
|
|
123
91
|
# == @has_options_contract
|
@@ -235,20 +203,24 @@ class Contract < Contracts::Decorator
|
|
235
203
|
|
236
204
|
# a better way to handle this might be to take this into account
|
237
205
|
# before throwing a "mismatched # of args" error.
|
206
|
+
# returns true if it appended nil
|
238
207
|
def maybe_append_block! args, blk
|
239
|
-
return unless @has_proc_contract && !blk &&
|
208
|
+
return false unless @has_proc_contract && !blk &&
|
240
209
|
(@args_contract_index || args.size < args_contracts.size)
|
241
210
|
args << nil
|
211
|
+
true
|
242
212
|
end
|
243
213
|
|
244
214
|
# Same thing for when we have named params but didn't pass any in.
|
215
|
+
# returns true if it appended nil
|
245
216
|
def maybe_append_options! args, blk
|
246
|
-
return unless @has_options_contract
|
217
|
+
return false unless @has_options_contract
|
247
218
|
if @has_proc_contract && args_contracts[-2].is_a?(Hash) && !args[-2].is_a?(Hash)
|
248
219
|
args.insert(-2, {})
|
249
220
|
elsif args_contracts[-1].is_a?(Hash) && !args[-1].is_a?(Hash)
|
250
221
|
args << {}
|
251
222
|
end
|
223
|
+
true
|
252
224
|
end
|
253
225
|
|
254
226
|
# Used to determine type of failure exception this contract should raise in case of failure
|
@@ -201,6 +201,20 @@ module Contracts
|
|
201
201
|
end
|
202
202
|
end
|
203
203
|
|
204
|
+
# Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in
|
205
|
+
# the list, the contract passes.
|
206
|
+
#
|
207
|
+
# Example: <tt>Enum[:a, :b, :c]</tt>?
|
208
|
+
class Enum < CallableClass
|
209
|
+
def initialize(*vals)
|
210
|
+
@vals = vals
|
211
|
+
end
|
212
|
+
|
213
|
+
def valid?(val)
|
214
|
+
@vals.include? val
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
204
218
|
# Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes,
|
205
219
|
# otherwise the contract fails.
|
206
220
|
# Example: <tt>Eq[Class]</tt>
|
@@ -346,6 +360,7 @@ module Contracts
|
|
346
360
|
end
|
347
361
|
|
348
362
|
def valid?(hash)
|
363
|
+
return false unless hash.is_a?(Hash)
|
349
364
|
keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
|
350
365
|
vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
|
351
366
|
|
@@ -371,6 +386,7 @@ module Contracts
|
|
371
386
|
end
|
372
387
|
|
373
388
|
def valid?(hash)
|
389
|
+
return false unless hash.keys - options.keys == []
|
374
390
|
options.all? do |key, contract|
|
375
391
|
Optional._valid?(hash, key, contract)
|
376
392
|
end
|
data/lib/contracts/call_with.rb
CHANGED
@@ -4,7 +4,7 @@ module Contracts
|
|
4
4
|
args << blk if blk
|
5
5
|
|
6
6
|
# Explicitly append blk=nil if nil != Proc contract violation anticipated
|
7
|
-
maybe_append_block!(args, blk)
|
7
|
+
nil_block_appended = maybe_append_block!(args, blk)
|
8
8
|
|
9
9
|
# Explicitly append options={} if Hash contract is present
|
10
10
|
maybe_append_options!(args, blk)
|
@@ -66,7 +66,8 @@ module Contracts
|
|
66
66
|
end
|
67
67
|
|
68
68
|
# If we put the block into args for validating, restore the args
|
69
|
-
|
69
|
+
# OR if we added a fake nil at the end because a block wasn't passed in.
|
70
|
+
args.slice!(-1) if blk || nil_block_appended
|
70
71
|
result = if method.respond_to?(:call)
|
71
72
|
# proc, block, lambda, etc
|
72
73
|
method.call(*args, &blk)
|