contracts-lite 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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