contracts 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7928ae4cc32437fe0b85893e7f381a47864309e8
4
- data.tar.gz: cfdd1ee61dd1e002bec1337815f75ce56c942b0e
3
+ metadata.gz: 588796ed6d26a1394810a86debd6f0b285dfb7e1
4
+ data.tar.gz: bff5d90964ae5bb9911b66236c5898ad741c159b
5
5
  SHA512:
6
- metadata.gz: a68ba2b3654e8882954789e2370a88108c822dc97d390062c9507ab06b56f4fb8fb6739b4051a6a71ad3648742969bf936f096892f78ab5bc831108d667c563d
7
- data.tar.gz: 2da1f049afd136edfdea28b9b9e1b4142ec721151b44bfde574075786338cea065cffc00273815edcd7b7b84f49926826fc69ffda462d3b94a87a8ce1b4ad831
6
+ metadata.gz: 678e7911e3dee12d273a8839712583bd0cc1272bde6da8aa8bdc27685751a66c9c9a25c39b1182c6056ff1d8bce191b8982783a18abadeff2526501040f61133
7
+ data.tar.gz: 5c8b7cd54ebff1b8d9d75393a2c1f0d45df096c295c3387eefd54a1a01695afe8fe1ef4975bc09705d1a6d081761fe42ffb8512d926df6cc0104e10f764844c6
@@ -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
@@ -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(Contracts::Args)` - e.g. `Args[Num]`,
549
- - `override_validator(Contracts::Func)` - e.g. `Func[Num => Num]`,
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
@@ -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
- common(base)
16
+ base.send(:include, Core)
16
17
  end
17
18
 
18
19
  def self.extended(base)
19
- common(base)
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
@@ -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
- args.slice!(-1) if blk
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)