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 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)