contracts-lite 0.14.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.markdown +80 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +23 -0
  5. data/README.md +102 -0
  6. data/TODO.markdown +6 -0
  7. data/TUTORIAL.md +747 -0
  8. data/benchmarks/bench.rb +67 -0
  9. data/benchmarks/hash.rb +69 -0
  10. data/benchmarks/invariants.rb +91 -0
  11. data/benchmarks/io.rb +62 -0
  12. data/benchmarks/wrap_test.rb +57 -0
  13. data/contracts.gemspec +13 -0
  14. data/lib/contracts.rb +231 -0
  15. data/lib/contracts/builtin_contracts.rb +541 -0
  16. data/lib/contracts/call_with.rb +97 -0
  17. data/lib/contracts/core.rb +52 -0
  18. data/lib/contracts/decorators.rb +47 -0
  19. data/lib/contracts/engine.rb +26 -0
  20. data/lib/contracts/engine/base.rb +136 -0
  21. data/lib/contracts/engine/eigenclass.rb +50 -0
  22. data/lib/contracts/engine/target.rb +70 -0
  23. data/lib/contracts/error_formatter.rb +121 -0
  24. data/lib/contracts/errors.rb +71 -0
  25. data/lib/contracts/formatters.rb +134 -0
  26. data/lib/contracts/invariants.rb +68 -0
  27. data/lib/contracts/method_handler.rb +195 -0
  28. data/lib/contracts/method_reference.rb +100 -0
  29. data/lib/contracts/support.rb +59 -0
  30. data/lib/contracts/validators.rb +139 -0
  31. data/lib/contracts/version.rb +3 -0
  32. data/script/rubocop +7 -0
  33. data/spec/builtin_contracts_spec.rb +461 -0
  34. data/spec/contracts_spec.rb +748 -0
  35. data/spec/error_formatter_spec.rb +68 -0
  36. data/spec/fixtures/fixtures.rb +710 -0
  37. data/spec/invariants_spec.rb +17 -0
  38. data/spec/module_spec.rb +18 -0
  39. data/spec/override_validators_spec.rb +162 -0
  40. data/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  41. data/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  42. data/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  43. data/spec/spec_helper.rb +102 -0
  44. data/spec/support.rb +10 -0
  45. data/spec/support_spec.rb +21 -0
  46. data/spec/validators_spec.rb +47 -0
  47. metadata +94 -0
@@ -0,0 +1,541 @@
1
+ require "contracts/formatters"
2
+ require "set"
3
+
4
+ # rdoc
5
+ # This module contains all the builtin contracts.
6
+ # If you want to use them, first:
7
+ #
8
+ # import Contracts
9
+ #
10
+ # And then use these or write your own!
11
+ #
12
+ # A simple example:
13
+ #
14
+ # Contract Num, Num => Num
15
+ # def add(a, b)
16
+ # a + b
17
+ # end
18
+ #
19
+ # The contract is <tt>Contract Num, Num, Num</tt>.
20
+ # That says that the +add+ function takes two numbers and returns a number.
21
+ module Contracts
22
+ module Builtin
23
+ # Check that an argument is +Numeric+.
24
+ class Num
25
+ def self.valid? val
26
+ val.is_a? Numeric
27
+ end
28
+ end
29
+
30
+ # Check that an argument is a positive number.
31
+ class Pos
32
+ def self.valid? val
33
+ val && val.is_a?(Numeric) && val > 0
34
+ end
35
+ end
36
+
37
+ # Check that an argument is a negative number.
38
+ class Neg
39
+ def self.valid? val
40
+ val && val.is_a?(Numeric) && val < 0
41
+ end
42
+ end
43
+
44
+ # Check that an argument is an +Integer+.
45
+ class Int
46
+ def self.valid? val
47
+ val && val.is_a?(Integer)
48
+ end
49
+ end
50
+
51
+ # Check that an argument is a natural number (includes zero).
52
+ class Nat
53
+ def self.valid? val
54
+ val && val.is_a?(Integer) && val >= 0
55
+ end
56
+ end
57
+
58
+ # Check that an argument is a positive natural number (excludes zero).
59
+ class NatPos
60
+ def self.valid? val
61
+ val && val.is_a?(Integer) && val > 0
62
+ end
63
+ end
64
+
65
+ # Passes for any argument.
66
+ class Any
67
+ def self.valid? val
68
+ true
69
+ end
70
+ end
71
+
72
+ # Fails for any argument.
73
+ class None
74
+ def self.valid? val
75
+ false
76
+ end
77
+ end
78
+
79
+ # Use this when you are writing your own contract classes.
80
+ # Allows your contract to be called with <tt>[]</tt> instead of <tt>.new</tt>:
81
+ #
82
+ # Old: <tt>Or.new(param1, param2)</tt>
83
+ #
84
+ # New: <tt>Or[param1, param2]</tt>
85
+ #
86
+ # Of course, <tt>.new</tt> still works.
87
+ class CallableClass
88
+ include ::Contracts::Formatters
89
+ def self.[](*vals)
90
+ new(*vals)
91
+ end
92
+ end
93
+
94
+ # Takes a variable number of contracts.
95
+ # The contract passes if any of the contracts pass.
96
+ # Example: <tt>Or[Fixnum, Float]</tt>
97
+ class Or < CallableClass
98
+ def initialize(*vals)
99
+ @vals = vals
100
+ end
101
+
102
+ def valid?(val)
103
+ @vals.any? do |contract|
104
+ res, _ = Contract.valid?(val, contract)
105
+ res
106
+ end
107
+ end
108
+
109
+ def to_s
110
+ @vals[0, @vals.size-1].map do |x|
111
+ InspectWrapper.create(x)
112
+ end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
113
+ end
114
+ end
115
+
116
+ # Takes a variable number of contracts.
117
+ # The contract passes if exactly one of those contracts pass.
118
+ # Example: <tt>Xor[Fixnum, Float]</tt>
119
+ class Xor < CallableClass
120
+ def initialize(*vals)
121
+ @vals = vals
122
+ end
123
+
124
+ def valid?(val)
125
+ results = @vals.map do |contract|
126
+ res, _ = Contract.valid?(val, contract)
127
+ res
128
+ end
129
+ results.count(true) == 1
130
+ end
131
+
132
+ def to_s
133
+ @vals[0, @vals.size-1].map do |x|
134
+ InspectWrapper.create(x)
135
+ end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
136
+ end
137
+ end
138
+
139
+ # Takes a variable number of contracts.
140
+ # The contract passes if all contracts pass.
141
+ # Example: <tt>And[Fixnum, Float]</tt>
142
+ class And < CallableClass
143
+ def initialize(*vals)
144
+ @vals = vals
145
+ end
146
+
147
+ def valid?(val)
148
+ @vals.all? do |contract|
149
+ res, _ = Contract.valid?(val, contract)
150
+ res
151
+ end
152
+ end
153
+
154
+ def to_s
155
+ @vals[0, @vals.size-1].map do |x|
156
+ InspectWrapper.create(x)
157
+ end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s
158
+ end
159
+ end
160
+
161
+ # Takes a variable number of method names as symbols.
162
+ # The contract passes if the argument responds to all
163
+ # of those methods.
164
+ # Example: <tt>RespondTo[:password, :credit_card]</tt>
165
+ class RespondTo < CallableClass
166
+ def initialize(*meths)
167
+ @meths = meths
168
+ end
169
+
170
+ def valid?(val)
171
+ @meths.all? do |meth|
172
+ val.respond_to? meth
173
+ end
174
+ end
175
+
176
+ def to_s
177
+ "a value that responds to #{@meths.inspect}"
178
+ end
179
+ end
180
+
181
+ # Takes a variable number of method names as symbols.
182
+ # Given an argument, all of those methods are called
183
+ # on the argument one by one. If they all return true,
184
+ # the contract passes.
185
+ # Example: <tt>Send[:valid?]</tt>
186
+ class Send < CallableClass
187
+ def initialize(*meths)
188
+ @meths = meths
189
+ end
190
+
191
+ def valid?(val)
192
+ @meths.all? do |meth|
193
+ val.send(meth)
194
+ end
195
+ end
196
+
197
+ def to_s
198
+ "a value that returns true for all of #{@meths.inspect}"
199
+ end
200
+ end
201
+
202
+ # Takes a class +A+. If argument is object of type +A+, the contract passes.
203
+ # If it is a subclass of A (or not related to A in any way), it fails.
204
+ # Example: <tt>Exactly[Numeric]</tt>
205
+ class Exactly < CallableClass
206
+ def initialize(cls)
207
+ @cls = cls
208
+ end
209
+
210
+ def valid?(val)
211
+ val.class == @cls
212
+ end
213
+
214
+ def to_s
215
+ "exactly #{@cls.inspect}"
216
+ end
217
+ end
218
+
219
+ # Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in
220
+ # the list, the contract passes.
221
+ #
222
+ # Example: <tt>Enum[:a, :b, :c]</tt>?
223
+ class Enum < CallableClass
224
+ def initialize(*vals)
225
+ @vals = vals
226
+ end
227
+
228
+ def valid?(val)
229
+ @vals.include? val
230
+ end
231
+ end
232
+
233
+ # Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes,
234
+ # otherwise the contract fails.
235
+ # Example: <tt>Eq[Class]</tt>
236
+ class Eq < CallableClass
237
+ def initialize(value)
238
+ @value = value
239
+ end
240
+
241
+ def valid?(val)
242
+ @value.equal?(val)
243
+ end
244
+
245
+ def to_s
246
+ "to be equal to #{@value.inspect}"
247
+ end
248
+ end
249
+
250
+ # Takes a variable number of contracts. The contract
251
+ # passes if all of those contracts fail for the given argument.
252
+ # Example: <tt>Not[nil]</tt>
253
+ class Not < CallableClass
254
+ def initialize(*vals)
255
+ @vals = vals
256
+ end
257
+
258
+ def valid?(val)
259
+ @vals.all? do |contract|
260
+ res, _ = Contract.valid?(val, contract)
261
+ !res
262
+ end
263
+ end
264
+
265
+ def to_s
266
+ "a value that is none of #{@vals.inspect}"
267
+ end
268
+ end
269
+
270
+ # @private
271
+ # Takes a collection(responds to :each) type and a contract.
272
+ # The related argument must be of specified collection type.
273
+ # Checks the contract against every element of the collection.
274
+ # If it passes for all elements, the contract passes.
275
+ # Example: <tt>CollectionOf[Array, Num]</tt>
276
+ class CollectionOf < CallableClass
277
+ def initialize(collection_class, contract)
278
+ @collection_class = collection_class
279
+ @contract = contract
280
+ end
281
+
282
+ def valid?(vals)
283
+ return false unless vals.is_a?(@collection_class)
284
+ vals.all? do |val|
285
+ res, _ = Contract.valid?(val, @contract)
286
+ res
287
+ end
288
+ end
289
+
290
+ def to_s
291
+ "a collection #{@collection_class} of #{@contract}"
292
+ end
293
+
294
+ class Factory
295
+ def initialize(collection_class, &before_new)
296
+ @collection_class = collection_class
297
+ @before_new = before_new
298
+ end
299
+
300
+ def new(contract)
301
+ @before_new && @before_new.call
302
+ CollectionOf.new(@collection_class, contract)
303
+ end
304
+
305
+ alias_method :[], :new
306
+ end
307
+ end
308
+
309
+ # Takes a contract. The related argument must be an array.
310
+ # Checks the contract against every element of the array.
311
+ # If it passes for all elements, the contract passes.
312
+ # Example: <tt>ArrayOf[Num]</tt>
313
+ ArrayOf = CollectionOf::Factory.new(Array)
314
+
315
+ # Takes a contract. The related argument must be a set.
316
+ # Checks the contract against every element of the set.
317
+ # If it passes for all elements, the contract passes.
318
+ # Example: <tt>SetOf[Num]</tt>
319
+ SetOf = CollectionOf::Factory.new(Set)
320
+
321
+ # Used for <tt>*args</tt> (variadic functions). Takes a contract
322
+ # and uses it to validate every element passed in
323
+ # through <tt>*args</tt>.
324
+ # Example: <tt>Args[Or[String, Num]]</tt>
325
+ class Args < CallableClass
326
+ attr_reader :contract
327
+ def initialize(contract)
328
+ @contract = contract
329
+ end
330
+
331
+ def to_s
332
+ "Args[#{@contract}]"
333
+ end
334
+ end
335
+
336
+ class Bool
337
+ def self.valid? val
338
+ val.is_a?(TrueClass) || val.is_a?(FalseClass)
339
+ end
340
+ end
341
+
342
+ # Use this to specify a Range object of a particular datatype.
343
+ # Example: <tt>RangeOf[Nat]</tt>, <tt>RangeOf[Date]</tt>, ...
344
+ class RangeOf < CallableClass
345
+ def initialize(contract)
346
+ @contract = contract
347
+ end
348
+
349
+ def valid?(val)
350
+ val.is_a?(Range) &&
351
+ Contract.valid?(val.first, @contract) &&
352
+ Contract.valid?(val.last, @contract)
353
+ end
354
+
355
+ def to_s
356
+ "a range of #{@contract}"
357
+ end
358
+ end
359
+
360
+ # Use this to specify the Hash characteristics. Takes two contracts,
361
+ # one for hash keys and one for hash values.
362
+ # Example: <tt>HashOf[Symbol, String]</tt>
363
+ class HashOf < CallableClass
364
+ INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract"
365
+
366
+ def initialize(key, value = nil)
367
+ if value
368
+ @key = key
369
+ @value = value
370
+ else
371
+ validate_hash(key)
372
+ @key = key.keys.first
373
+ @value = key[@key]
374
+ end
375
+ end
376
+
377
+ def valid?(hash)
378
+ return false unless hash.is_a?(Hash)
379
+ keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
380
+ vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
381
+
382
+ [keys_match, vals_match].all?
383
+ end
384
+
385
+ def to_s
386
+ "Hash<#{@key}, #{@value}>"
387
+ end
388
+
389
+ private
390
+
391
+ def validate_hash(hash)
392
+ fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1
393
+ end
394
+ end
395
+
396
+ # Use this to specify the Hash characteristics. This contracts fails
397
+ # if there are any extra keys that don't have contracts on them.
398
+ # Example: <tt>StrictHash[{ a: String, b: Bool }]</tt>
399
+ class StrictHash < CallableClass
400
+ attr_reader :contract_hash
401
+
402
+ def initialize(contract_hash)
403
+ @contract_hash = contract_hash
404
+ end
405
+
406
+ def valid?(arg)
407
+ return false unless arg.is_a?(Hash)
408
+
409
+ contract_hash.all? do |key, _v|
410
+ contract_hash.key?(key) && Contract.valid?(arg[key], contract_hash[key])
411
+ end
412
+ end
413
+ end
414
+
415
+ # Use this for specifying contracts for keyword arguments
416
+ # Example: <tt>KeywordArgs[ e: Range, f: Optional[Num] ]</tt>
417
+ class KeywordArgs < CallableClass
418
+ def initialize(options)
419
+ @options = options
420
+ end
421
+
422
+ def valid?(hash)
423
+ return false unless hash.is_a?(Hash)
424
+ return false unless hash.keys - options.keys == []
425
+ options.all? do |key, contract|
426
+ Optional._valid?(hash, key, contract)
427
+ end
428
+ end
429
+
430
+ def to_s
431
+ "KeywordArgs[#{options}]"
432
+ end
433
+
434
+ def inspect
435
+ to_s
436
+ end
437
+
438
+ private
439
+
440
+ attr_reader :options
441
+ end
442
+
443
+ # Use this for specifying contracts for class arguments
444
+ # Example: <tt>DescendantOf[ e: Range, f: Optional[Num] ]</tt>
445
+ class DescendantOf < CallableClass
446
+ def initialize(parent_class)
447
+ @parent_class = parent_class
448
+ end
449
+
450
+ def valid?(given_class)
451
+ given_class.is_a?(Class) && given_class.ancestors.include?(parent_class)
452
+ end
453
+
454
+ def to_s
455
+ "DescendantOf[#{parent_class}]"
456
+ end
457
+
458
+ def inspect
459
+ to_s
460
+ end
461
+
462
+ private
463
+
464
+ attr_reader :parent_class
465
+ end
466
+
467
+ # Use this for specifying optional keyword argument
468
+ # Example: <tt>Optional[Num]</tt>
469
+ class Optional < CallableClass
470
+ UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH =
471
+ "Unable to use Optional contract outside of KeywordArgs contract"
472
+
473
+ def self._valid?(hash, key, contract)
474
+ return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional)
475
+ contract.within_opt_hash!
476
+ !hash.key?(key) || Contract.valid?(hash[key], contract)
477
+ end
478
+
479
+ def initialize(contract)
480
+ @contract = contract
481
+ @within_opt_hash = false
482
+ end
483
+
484
+ def within_opt_hash!
485
+ @within_opt_hash = true
486
+ self
487
+ end
488
+
489
+ def valid?(value)
490
+ ensure_within_opt_hash
491
+ Contract.valid?(value, contract)
492
+ end
493
+
494
+ def to_s
495
+ "Optional[#{formatted_contract}]"
496
+ end
497
+
498
+ def inspect
499
+ to_s
500
+ end
501
+
502
+ private
503
+
504
+ attr_reader :contract, :within_opt_hash
505
+
506
+ def ensure_within_opt_hash
507
+ return if within_opt_hash
508
+ fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH
509
+ end
510
+
511
+ def formatted_contract
512
+ Formatters::InspectWrapper.create(contract)
513
+ end
514
+ end
515
+
516
+ # Takes a Contract.
517
+ # The contract passes if the contract passes or the given value is nil.
518
+ # Maybe(foo) is equivalent to Or[foo, nil].
519
+ class Maybe < Or
520
+ def initialize(*vals)
521
+ super(*(vals + [nil]))
522
+ end
523
+
524
+ def include_proc?
525
+ @vals.include? Proc
526
+ end
527
+ end
528
+
529
+ # Used to define contracts on functions passed in as arguments.
530
+ # Example: <tt>Func[Num => Num] # the function should take a number and return a number</tt>
531
+ class Func < CallableClass
532
+ attr_reader :contracts
533
+ def initialize(*contracts)
534
+ @contracts = contracts
535
+ end
536
+ end
537
+ end
538
+
539
+ # Users can still include `Contracts::Core` & `Contracts::Builtin`
540
+ include Builtin
541
+ end