contracts 0.7 → 0.8

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: 065efb039a13c2917e10fc96dd77dd2fe858bc08
4
- data.tar.gz: 98f3253cc22a5da08f4d3414361db7218c438107
3
+ metadata.gz: 124deda42f022fb31b53dc3389bd51ff85860d30
4
+ data.tar.gz: 8ea3f78006952432311338880c563cdbdf327f33
5
5
  SHA512:
6
- metadata.gz: b8486b6e85a38517371403aa4cb7b3e6795ee01a2170e663eab8ff772507971a8e7bfa050b3b98dabb8fc60772153f544a14e9e419025a7c899ebda682b82338
7
- data.tar.gz: 44cfdb1101dbe48269e88c5758196e2bc11ce2238f70a791c0f62aa36d0754994dd00f49f4c8e0d98cca43a4b254a085cc83af3673964b6e31198ba66c14527e
6
+ metadata.gz: 644753384168c11234cd6a77b6d64384cc0f6fca93f3614e4d71ae3629ec7d05d85b02bfbd0150b3db21f94e04fe980f784d0e4decee0e5dcfd912f66526b2fb
7
+ data.tar.gz: 8a92fd259e98d4320fe5a4a52cc0d2d6cd01e7362b0fc6ed809001ef4e4618a7d1fab4513b2cb2db5946028fdf7a1c745e13491bf9d1e4e897f2dfe39446aafe
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ gemspec
4
4
 
5
5
  group :test do
6
6
  gem "rspec"
7
+ gem "rubocop", "~> 0.29", :platform => [:ruby_20, :ruby_21]
7
8
  end
8
9
 
9
10
  group :development do
@@ -1,15 +1,22 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contracts (0.6)
4
+ contracts (0.7)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
8
8
  specs:
9
+ ast (2.0.0)
10
+ astrolabe (1.3.0)
11
+ parser (>= 2.2.0.pre.3, < 3.0)
9
12
  diff-lcs (1.2.5)
10
13
  hirb (0.7.2)
11
14
  method_profiler (2.0.1)
12
15
  hirb (>= 0.6.0)
16
+ parser (2.2.0.3)
17
+ ast (>= 1.1, < 3.0)
18
+ powerpack (0.1.0)
19
+ rainbow (2.0.0)
13
20
  rspec (3.1.0)
14
21
  rspec-core (~> 3.1.0)
15
22
  rspec-expectations (~> 3.1.0)
@@ -22,7 +29,14 @@ GEM
22
29
  rspec-mocks (3.1.3)
23
30
  rspec-support (~> 3.1.0)
24
31
  rspec-support (3.1.2)
32
+ rubocop (0.29.1)
33
+ astrolabe (~> 1.3)
34
+ parser (>= 2.2.0.1, < 3.0)
35
+ powerpack (~> 0.1)
36
+ rainbow (>= 1.99.1, < 3.0)
37
+ ruby-progressbar (~> 1.4)
25
38
  ruby-prof (0.15.2)
39
+ ruby-progressbar (1.7.5)
26
40
 
27
41
  PLATFORMS
28
42
  ruby
@@ -31,4 +45,5 @@ DEPENDENCIES
31
45
  contracts!
32
46
  method_profiler
33
47
  rspec
48
+ rubocop (~> 0.29)
34
49
  ruby-prof
data/README.md CHANGED
@@ -1,6 +1,4 @@
1
- # contracts.ruby
2
-
3
- [![Build Status](https://travis-ci.org/egonSchiele/contracts.ruby.png?branch=master)](https://travis-ci.org/egonSchiele/contracts.ruby)
1
+ # contracts.ruby [![Build Status](https://travis-ci.org/egonSchiele/contracts.ruby.png?branch=master)](https://travis-ci.org/egonSchiele/contracts.ruby) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby)
4
2
 
5
3
  Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code.
6
4
 
@@ -65,11 +63,21 @@ Using contracts.ruby results in very little slowdown. Check out [this blog post]
65
63
 
66
64
  If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :)
67
65
 
66
+ ## Testimonials
67
+
68
+ > Contracts literally saves us hours of pain at Snowplow every day
69
+
70
+ Alexander Dean, creator of [Snowplow](https://github.com/snowplow/snowplow)
71
+
72
+ > Contracts caught a bug that saved us several hundred dollars. It took less than 30 seconds to add the contract.
73
+
74
+ Michael Tomer
75
+
68
76
  ## Credits
69
77
 
70
78
  Inspired by [contracts.coffee](http://disnetdev.com/contracts.coffee/).
71
79
 
72
- Copyright 2012 [Aditya Bhargava](http://adit.io).
80
+ Copyright 2012-2015 [Aditya Bhargava](http://adit.io).
73
81
  Major improvements by [Alexey Fedorov](https://github.com/waterlink).
74
82
 
75
83
  BSD Licensed.
@@ -59,17 +59,33 @@ You can also see the contract for a function with the `functype` method:
59
59
  functype(:add)
60
60
  => "add :: Num, Num => Num"
61
61
 
62
- This can be useful if you're in a repl and want to figure out how a function should be used.
63
-
64
- ## Builtin Contracts
65
-
66
- `Num` is one of the builtin contracts that contracts.ruby comes with. The builtin contracts are in the `Contracts` namespace. The easiest way to use them is to include the `Contracts` module in your class/module.
67
-
68
- contracts.ruby comes with a lot of builtin contracts, including:
69
-
70
- Num, Pos, Neg, Any, None, Or, Xor, And, Not, RespondTo, Send, Exactly, ArrayOf, HashOf, Bool, Maybe
71
-
72
- To see all the builtin contracts and what they do, check out the [rdoc](http://rubydoc.info/gems/contracts/Contracts).
62
+ This can be useful if you're in a REPL and want to figure out how a function should be used.
63
+
64
+ ## Built-in Contracts
65
+
66
+ `Num` is one of the built-in contracts that contracts.ruby comes with. The built-in contracts are in the `Contracts` namespace. The easiest way to use them is to include the `Contracts` module in your class/module.
67
+
68
+ contracts.ruby comes with a lot of built-in contracts, including the following:
69
+
70
+ * [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Num) checks that the argument is `Numeric`
71
+ * [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Pos) – checks that the argument is a positive number
72
+ * [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Neg) – checks that the argument is a negative number
73
+ * [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Nat) – checks that the argument is a natural number
74
+ * [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Bool) – checks that the argument is `true` or `false`
75
+ * [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Any) – Passes for any argument. Use when the argument has no constraints.
76
+ * [`None`](http://www.rubydoc.info/gems/contracts/Contracts/None) – Fails for any argument. Use when the method takes no arguments.
77
+ * [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Or) – passes if any of the given contracts pass, e.g. `Or[Fixnum, Float]`
78
+ * [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Fixnum, Float]`
79
+ * [`And`](http://www.rubydoc.info/gems/contracts/Contracts/And) – passes if all contracts pass, e.g. `And[Fixnum, Float]`
80
+ * [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]`
81
+ * [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]`
82
+ * [`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]`
83
+ * [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Maybe) – passes if the argument is `nil`, or if the given contract passes
84
+ * [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]`
85
+ * [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Send) – checks that all named methods return true, e.g. `Send[:valid?]`
86
+ * [`Exactly`](http://www.rubydoc.info/gems/contracts/Contracts/Exactly) – checks that the argument has the given type, not accepting sub-classes, e.g. `Exactly[Numeric]`.
87
+
88
+ To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts).
73
89
 
74
90
  ## More Examples
75
91
 
@@ -106,7 +122,7 @@ This introduces some new syntax. One of the valid values for a contract is an in
106
122
  Contract Or.new(Fixnum, Float) => Or.new(Fixnum, Float)
107
123
  ```
108
124
 
109
- All the builtin contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write
125
+ All the built-in contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write
110
126
 
111
127
  ```ruby
112
128
  Contract Or[Fixnum, Float] => Or[Fixnum, Float]
@@ -224,8 +240,26 @@ give_largest_value("a" => 1, 2 => 2, c: 3)
224
240
 
225
241
  ### Contracts On Functions
226
242
 
227
- If you're writing higher-order functions (functions that take functions as parameters) and want to write a contract for the passed-in function, you can!
228
- Use the `Func` contract. `Func` takes a contract as it's argument, and uses that contract on the function that you pass in.
243
+ Lets say you are writing a simple map function:
244
+
245
+ ```ruby
246
+ def map(arr, func)
247
+ ```
248
+
249
+ `map` takes an array, and a function. Suppose you want to add a contract to this function. You could try this:
250
+
251
+ ```ruby
252
+ Contract ArrayOf[Any], Proc => ArrayOf[Any]
253
+ def map(arr, func)
254
+ ```
255
+
256
+ This says that the second argument should be a `Proc`. You can call the function like so:
257
+
258
+ ```ruby
259
+ p map([1, 2, 3], lambda { |x| x + 1 }) # works
260
+ ```
261
+
262
+ But suppose you want to have a contract on the Proc too! Suppose you want to make sure that the Proc returns a number. Use the `Func` contract. `Func` takes a contract as it's argument, and uses that contract on the function that you pass in.
229
263
 
230
264
  Here's a `map` function that requires an array of numbers, and a function that takes a number and returns a number:
231
265
 
@@ -240,13 +274,24 @@ def map(arr, func)
240
274
  end
241
275
  ```
242
276
 
243
- This will add the contract `Num => Num` on `func`. Try it with these two examples:
277
+ Earlier, we used `Proc`, which just says "make sure the second variable is a Proc". Now we are using `Func[Num => Num]`, which says "make sure the second variable is a Proc that takes a number and returns a number". Better!
278
+
279
+ Try this map function with these two examples:
244
280
 
245
281
  ```ruby
246
282
  p map([1, 2, 3], lambda { |x| x + 1 }) # works
247
283
  p map([1, 2, 3], lambda { |x| "oops" }) # fails, the lambda returns a string.
248
284
  ```
249
285
 
286
+ NOTE: This is not valid:
287
+
288
+ ```ruby
289
+ Contract ArrayOf[Num], Func => ArrayOf[Num]
290
+ def map(arr, &func)
291
+ ```
292
+
293
+ Here I am using `Func` without specifying a contract, like `Func[Num => Num]`. That's not a legal contract. If you just want to validate that the second argument is a proc, use `Proc`.
294
+
250
295
  ### Returning Multiple Values
251
296
  Treat the return value as an array. For example, here's a function that returns two numbers:
252
297
 
@@ -491,8 +536,8 @@ class MyBirthday < Struct.new(:day, :month)
491
536
  include Contracts
492
537
  include Contracts::Invariants
493
538
 
494
- Invariant(:day) { 1 <= day && day <= 31 }
495
- Invariant(:month) { 1 <= month && month <= 12 }
539
+ invariant(:day) { 1 <= day && day <= 31 }
540
+ invariant(:month) { 1 <= month && month <= 12 }
496
541
 
497
542
  Contract None => Fixnum
498
543
  def silly_next_day!
@@ -514,7 +559,11 @@ If you run it, last line will generate invariant violation:
514
559
  At: main.rb:9
515
560
  ```
516
561
 
517
- Which means, that after `#silly_next_day!` all checks specified in `Invariant` statement will be verified, and if at least one fail, then Invariant violation error will be raised.
562
+ Which means, that after `#silly_next_day!` all checks specified in `invariant` statement will be verified, and if at least one fail, then invariant violation error will be raised.
563
+
564
+ ## Auto-generate documentation using contracts
565
+
566
+ If you are generating documentation for your code with [YARD](http://yardoc.org/), check out [yard-contracts](https://github.com/sfcgeorge/yard-contracts). It will automatically annotate your functions with contracts information. Instead of documenting each parameter for a function yourself, you can just add a contract and yard-contracts will generate the documentation for you!
518
567
 
519
568
  ## Misc
520
569
 
@@ -1,8 +1,8 @@
1
- require './lib/contracts'
2
- require 'benchmark'
3
- require 'rubygems'
4
- require 'method_profiler'
5
- require 'ruby-prof'
1
+ require "./lib/contracts"
2
+ require "benchmark"
3
+ require "rubygems"
4
+ require "method_profiler"
5
+ require "ruby-prof"
6
6
 
7
7
  include Contracts
8
8
 
@@ -16,22 +16,22 @@ def contracts_add a, b
16
16
  end
17
17
 
18
18
  def explicit_add a, b
19
- raise unless a.is_a?(Numeric)
20
- raise unless b.is_a?(Numeric)
19
+ fail unless a.is_a?(Numeric)
20
+ fail unless b.is_a?(Numeric)
21
21
  c = a + b
22
- raise unless c.is_a?(Numeric)
22
+ fail unless c.is_a?(Numeric)
23
23
  c
24
24
  end
25
25
 
26
26
  def benchmark
27
27
  Benchmark.bm 30 do |x|
28
- x.report 'testing add' do
29
- 1000000.times do |_|
28
+ x.report "testing add" do
29
+ 1_000_000.times do |_|
30
30
  add(rand(1000), rand(1000))
31
31
  end
32
32
  end
33
- x.report 'testing contracts add' do
34
- 1000000.times do |_|
33
+ x.report "testing contracts add" do
34
+ 1_000_000.times do |_|
35
35
  contracts_add(rand(1000), rand(1000))
36
36
  end
37
37
  end
@@ -46,20 +46,20 @@ def profile
46
46
  profilers << MethodProfiler.observe(Contracts::Decorator)
47
47
  profilers << MethodProfiler.observe(Contracts::Support)
48
48
  profilers << MethodProfiler.observe(UnboundMethod)
49
- 10000.times do |_|
49
+ 10_000.times do |_|
50
50
  contracts_add(rand(1000), rand(1000))
51
51
  end
52
52
  profilers.each { |p| puts p.report }
53
53
  end
54
54
 
55
55
  def ruby_prof
56
- RubyProf.start
57
- 100000.times do |_|
58
- contracts_add(rand(1000), rand(1000))
59
- end
60
- result = RubyProf.stop
61
- printer = RubyProf::FlatPrinter.new(result)
62
- printer.print(STDOUT)
56
+ RubyProf.start
57
+ 100_000.times do |_|
58
+ contracts_add(rand(1000), rand(1000))
59
+ end
60
+ result = RubyProf.stop
61
+ printer = RubyProf::FlatPrinter.new(result)
62
+ printer.print(STDOUT)
63
63
  end
64
64
 
65
65
  benchmark
@@ -1,24 +1,34 @@
1
- require './lib/contracts'
2
- require 'benchmark'
3
- require 'rubygems'
4
- require 'method_profiler'
5
- require 'ruby-prof'
1
+ require "./lib/contracts"
2
+ require "benchmark"
3
+ require "rubygems"
4
+ require "method_profiler"
5
+ require "ruby-prof"
6
6
 
7
- class Obj < Struct.new(:value)
7
+ class Obj
8
8
  include Contracts
9
9
 
10
+ attr_accessor :value
11
+ def initialize value
12
+ @value = value
13
+ end
14
+
10
15
  Contract Num, Num => Num
11
16
  def contracts_add a, b
12
17
  a + b
13
18
  end
14
19
  end
15
20
 
16
- class ObjWithInvariants < Struct.new(:value)
21
+ class ObjWithInvariants
17
22
  include Contracts
18
23
  include Contracts::Invariants
19
24
 
20
- Invariant(:value_not_nil) { value != nil }
21
- Invariant(:value_not_string) { !value.is_a?(String) }
25
+ invariant(:value_not_nil) { value != nil }
26
+ invariant(:value_not_string) { !value.is_a?(String) }
27
+
28
+ attr_accessor :value
29
+ def initialize value
30
+ @value = value
31
+ end
22
32
 
23
33
  Contract Num, Num => Num
24
34
  def contracts_add a, b
@@ -31,16 +41,16 @@ def benchmark
31
41
  obj_with_invariants = ObjWithInvariants.new(3)
32
42
 
33
43
  Benchmark.bm 30 do |x|
34
- x.report 'testing contracts add' do
35
- 1000000.times do |_|
44
+ x.report "testing contracts add" do
45
+ 1_000_000.times do |_|
36
46
  obj.contracts_add(rand(1000), rand(1000))
37
47
  end
38
48
  end
39
- x.report 'testing contracts add with invariants' do
40
- 1000000.times do |_|
49
+ x.report "testing contracts add with invariants" do
50
+ 1_000_000.times do |_|
41
51
  obj_with_invariants.contracts_add(rand(1000), rand(1000))
42
52
  end
43
- end
53
+ end
44
54
  end
45
55
  end
46
56
 
@@ -55,19 +65,19 @@ def profile
55
65
  profilers << MethodProfiler.observe(Contracts::Invariants::InvariantExtension)
56
66
  profilers << MethodProfiler.observe(UnboundMethod)
57
67
 
58
- 10000.times do |_|
68
+ 10_000.times do |_|
59
69
  obj_with_invariants.contracts_add(rand(1000), rand(1000))
60
- end
70
+ end
61
71
 
62
72
  profilers.each { |p| puts p.report }
63
73
  end
64
74
 
65
75
  def ruby_prof
66
- RubyProf.start
76
+ RubyProf.start
67
77
 
68
78
  obj_with_invariants = ObjWithInvariants.new(3)
69
79
 
70
- 100000.times do |_|
80
+ 100_000.times do |_|
71
81
  obj_with_invariants.contracts_add(rand(1000), rand(1000))
72
82
  end
73
83
 
@@ -0,0 +1,62 @@
1
+ require "./lib/contracts"
2
+ require "benchmark"
3
+ require "rubygems"
4
+ require "method_profiler"
5
+ require "ruby-prof"
6
+ require "open-uri"
7
+
8
+ include Contracts
9
+
10
+ def download url
11
+ open("https://www.#{url}/").read
12
+ end
13
+
14
+ Contract String => String
15
+ def contracts_download url
16
+ open("https://www.#{url}").read
17
+ end
18
+
19
+ @urls = %w{facebook.com google.com bing.com}
20
+
21
+ def benchmark
22
+ Benchmark.bm 30 do |x|
23
+ x.report "testing download" do
24
+ 100.times do |_|
25
+ download(@urls.sample)
26
+ end
27
+ end
28
+ x.report "testing contracts download" do
29
+ 100.times do |_|
30
+ contracts_download(@urls.sample)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def profile
37
+ profilers = []
38
+ profilers << MethodProfiler.observe(Contract)
39
+ profilers << MethodProfiler.observe(Object)
40
+ profilers << MethodProfiler.observe(Contracts::MethodDecorators)
41
+ profilers << MethodProfiler.observe(Contracts::Decorator)
42
+ profilers << MethodProfiler.observe(Contracts::Support)
43
+ profilers << MethodProfiler.observe(UnboundMethod)
44
+ 10.times do |_|
45
+ contracts_download(@urls.sample)
46
+ end
47
+ profilers.each { |p| puts p.report }
48
+ end
49
+
50
+ def ruby_prof
51
+ RubyProf.start
52
+ 10.times do |_|
53
+ contracts_download(@urls.sample)
54
+ end
55
+ result = RubyProf.stop
56
+ printer = RubyProf::FlatPrinter.new(result)
57
+ printer.print(STDOUT)
58
+ end
59
+
60
+ benchmark
61
+ profile
62
+ ruby_prof if ENV["FULL_BENCH"] # takes some time