contracts 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +13 -5
  2. data/CHANGELOG.markdown +8 -0
  3. data/Gemfile +3 -0
  4. data/README.md +14 -10
  5. data/TUTORIAL.md +34 -1
  6. data/benchmarks/io.rb +3 -3
  7. data/cucumber.yml +1 -0
  8. data/features/README.md +17 -0
  9. data/features/basics/functype.feature +71 -0
  10. data/features/basics/simple_example.feature +210 -0
  11. data/features/builtin_contracts/README.md +22 -0
  12. data/features/builtin_contracts/and.feature +103 -0
  13. data/features/builtin_contracts/any.feature +44 -0
  14. data/features/builtin_contracts/args.feature +1 -0
  15. data/features/builtin_contracts/array_of.feature +1 -0
  16. data/features/builtin_contracts/bool.feature +64 -0
  17. data/features/builtin_contracts/enum.feature +1 -0
  18. data/features/builtin_contracts/eq.feature +1 -0
  19. data/features/builtin_contracts/exactly.feature +1 -0
  20. data/features/builtin_contracts/func.feature +1 -0
  21. data/features/builtin_contracts/hash_of.feature +1 -0
  22. data/features/builtin_contracts/keyword_args.feature +1 -0
  23. data/features/builtin_contracts/maybe.feature +1 -0
  24. data/features/builtin_contracts/nat.feature +115 -0
  25. data/features/builtin_contracts/neg.feature +115 -0
  26. data/features/builtin_contracts/none.feature +145 -0
  27. data/features/builtin_contracts/not.feature +1 -0
  28. data/features/builtin_contracts/num.feature +64 -0
  29. data/features/builtin_contracts/or.feature +83 -0
  30. data/features/builtin_contracts/pos.feature +116 -0
  31. data/features/builtin_contracts/range_of.feature +1 -0
  32. data/features/builtin_contracts/respond_to.feature +78 -0
  33. data/features/builtin_contracts/send.feature +147 -0
  34. data/features/builtin_contracts/set_of.feature +1 -0
  35. data/features/builtin_contracts/xor.feature +99 -0
  36. data/features/support/env.rb +6 -0
  37. data/lib/contracts.rb +1 -1
  38. data/lib/contracts/builtin_contracts.rb +356 -351
  39. data/lib/contracts/core.rb +11 -2
  40. data/lib/contracts/formatters.rb +2 -2
  41. data/lib/contracts/validators.rb +6 -0
  42. data/lib/contracts/version.rb +1 -1
  43. data/script/cucumber +5 -0
  44. data/script/docs-release +3 -0
  45. data/script/docs-staging +3 -0
  46. data/spec/builtin_contracts_spec.rb +1 -1
  47. data/spec/contracts_spec.rb +29 -0
  48. data/spec/fixtures/fixtures.rb +12 -0
  49. data/spec/validators_spec.rb +25 -3
  50. metadata +42 -9
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 588796ed6d26a1394810a86debd6f0b285dfb7e1
4
- data.tar.gz: bff5d90964ae5bb9911b66236c5898ad741c159b
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MzAzMDEzNDY4MjVjYWM2Njk2ZWViYTI0MTQ4MTI5ZGZiMGZhOTNhNA==
5
+ data.tar.gz: !binary |-
6
+ NDFhYTJmODVkY2FlNWE3ZGI2ZjcxM2JiODMyODBlZjM4MmRjZDkwZQ==
5
7
  SHA512:
6
- metadata.gz: 678e7911e3dee12d273a8839712583bd0cc1272bde6da8aa8bdc27685751a66c9c9a25c39b1182c6056ff1d8bce191b8982783a18abadeff2526501040f61133
7
- data.tar.gz: 5c8b7cd54ebff1b8d9d75393a2c1f0d45df096c295c3387eefd54a1a01695afe8fe1ef4975bc09705d1a6d081761fe42ffb8512d926df6cc0104e10f764844c6
8
+ metadata.gz: !binary |-
9
+ ZjNmODA2MDM5NGI0MjVjMzI0MDNlYmRjZWMzOTUxNmFhYmE0NWU1OGIwNzEz
10
+ ZDAwMmNhZWVkMTMxMDZkZTUwOGMwN2VhYjBlOTg2MGFhNmEzNjM1N2Q0NTJi
11
+ YzMzN2Y1ZTBiMWIwZDgyYWM0MmM5N2IyMjQ3ZWVmZGMwYzQxZDE=
12
+ data.tar.gz: !binary |-
13
+ ZTI2OWM5ZTNmYjFmMTI1NjU5ODUwNzM4MTg3NDBjYjkxM2RkZDJiYTBjNTcy
14
+ ZGY4NzYzZWU2NmM3Njc2ZmFlZjAzYzBkZDdhYTQ3ZmViNDYyMDY5Yzc2N2M3
15
+ Y2U5NTczYjdiODZjZjAxMzNiOGVjYTkyNzlhY2UyYmM3NDQzOWQ=
@@ -1,4 +1,12 @@
1
+ ## v0.12.0
2
+
3
+ - Feature: add `Regexp` validator - [Gert Goet](https://github.com/eval) [#196](https://github.com/egonSchiele/contracts.ruby/pull/196)
4
+ - Docs: bootstrap cucumber/aruba/relish setup - [Oleksii Fedorov](https://github.com/waterlink) [#195](https://github.com/egonSchiele/contracts.ruby/pull/195)
5
+ - Bugfix: allow to `extend` module, that has `Contracts` or `Contracts::Core` included without harming current module/class `Contracts` functionality, see: [#176](https://github.com/egonSchiele/contracts.ruby/issues/176) - [Oleksii Fedorov](https://github.com/waterlink) [#198](https://github.com/egonSchiele/contracts.ruby/pull/198)
6
+ - Enhancement: add `include Contracts::Builtin` to allow users to use builtin contracts without `Contracts::` prefix together with `include Contracts::Core` - [PikachuEXE](https://github.com/PikachuEXE) [#199](https://github.com/egonSchiele/contracts.ruby/pull/199)
7
+
1
8
  ## v0.11.0
9
+
2
10
  - 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
11
  - 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
12
  - Bugfix: bugfix for using varargs and `Maybe[Proc]` together - [Adit Bhargava](https://github.com/egonSchiele) [#188](https://github.com/egonSchiele/contracts.ruby/pull/188)
data/Gemfile CHANGED
@@ -4,10 +4,13 @@ gemspec
4
4
 
5
5
  group :test do
6
6
  gem "rspec"
7
+ gem "aruba"
8
+ gem "cucumber", "~> 1.3.20"
7
9
  gem "rubocop", "~> 0.29.1", :platform => [:ruby_20, :ruby_21, :ruby_22]
8
10
  end
9
11
 
10
12
  group :development do
13
+ gem "relish"
11
14
  gem "method_profiler"
12
15
  gem "ruby-prof"
13
16
  gem "rake"
data/README.md CHANGED
@@ -23,24 +23,28 @@ This says that double expects a number and returns a number. Here's the full cod
23
23
 
24
24
  ```ruby
25
25
  require 'contracts'
26
- include Contracts
27
26
 
28
- Contract Num => Num
29
- def double(x)
30
- x * 2
27
+ class Example
28
+ include Contracts::Core
29
+ include Contracts::Builtin
30
+
31
+ Contract Num => Num
32
+ def double(x)
33
+ x * 2
34
+ end
31
35
  end
32
36
 
33
- puts double("oops")
37
+ puts Example.new.double("oops")
34
38
  ```
35
39
 
36
40
  Save this in a file and run it. Notice we are calling `double` with `"oops"`, which is not a number. The contract fails with a detailed error message:
37
41
 
38
- ./contracts.rb:34:in `failure_callback': Contract violation: (RuntimeError)
39
- Expected: Contracts::Num,
42
+ ParamContractError: Contract violation for argument 1 of 1:
43
+ Expected: Num,
40
44
  Actual: "oops"
41
- Value guarded in: Object::double
42
- With Contract: Contracts::Num, Contracts::Num
43
- At: main.rb:6
45
+ Value guarded in: Example::double
46
+ With Contract: Num => Num
47
+ At: main.rb:8
44
48
  ...stack trace...
45
49
 
46
50
  Instead of throwing an exception, you could log it, print a clean error message for your user...whatever you want. contracts.ruby is here to help you handle bugs better, not to get in your way.
@@ -63,7 +63,7 @@ This can be useful if you're in a REPL and want to figure out how a function sho
63
63
 
64
64
  ## Built-in Contracts
65
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.
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::Builtin` module in your class/module.
67
67
 
68
68
  contracts.ruby comes with a lot of built-in contracts, including the following:
69
69
 
@@ -120,6 +120,22 @@ with while typing and anything that does not conflict with libraries you use.
120
120
 
121
121
  All examples after this point assume you have chosen a shortcut as `C::`.
122
122
 
123
+ If you are sure, that builtin contracts will not nameclash with your own code
124
+ and libraries you may use, then you can include all builtin contracts in your
125
+ class/module:
126
+
127
+ ```ruby
128
+ class Example
129
+ include Contracts::Core
130
+ include Contracts::Builtin
131
+
132
+ Contract Maybe[Num], Or[Float, String] => Bool
133
+ def complicated_algorithm(a, b)
134
+ # ...
135
+ end
136
+ end
137
+ ```
138
+
123
139
  ## More Examples
124
140
 
125
141
  ### Hello, World
@@ -271,6 +287,22 @@ give_largest_value(a: 1, b: 2, c: 3) # returns 3
271
287
  give_largest_value("a" => 1, 2 => 2, c: 3)
272
288
  ```
273
289
 
290
+ ### Contracts On Strings
291
+
292
+ When you want a contract to match not just any string (i.e. `Contract String => nil`), you can use regular expressions:
293
+ ```ruby
294
+ Contract /World|Mars/i => nil
295
+ def greet(name)
296
+ puts "Hello #{name}!"
297
+ end
298
+ ```
299
+
300
+ Using logical combinations you can combine existing definitions, instead of writing 1 big regular expression:
301
+ ```ruby
302
+ Contract C::And[default_mail_regexp, /#{AppConfig.domain}\z/] => nil
303
+ def send_admin_invite(email)
304
+ ```
305
+
274
306
  ### Contracts On Keyword Arguments
275
307
 
276
308
  ruby 2.0+, but can be used for normal hashes too, when keyword arguments are
@@ -562,6 +594,7 @@ Possible validator overrides:
562
594
  - `override_validator(Array)` - e.g. `[C::Num, String]`,
563
595
  - `override_validator(Hash)` - e.g. `{ :a => C::Num, :b => String }`,
564
596
  - `override_validator(Range)` - e.g. `(1..10)`,
597
+ - `override_validator(Regexp)` - e.g. `/foo/`,
565
598
  - `override_validator(Contracts::Args)` - e.g. `C::Args[C::Num]`,
566
599
  - `override_validator(Contracts::Func)` - e.g. `C::Func[C::Num => C::Num]`,
567
600
  - `override_validator(:valid)` - allows to override how contracts that respond to `:valid?` are handled,
@@ -8,15 +8,15 @@ require "open-uri"
8
8
  include Contracts
9
9
 
10
10
  def download url
11
- open("https://www.#{url}/").read
11
+ open("http://www.#{url}/").read
12
12
  end
13
13
 
14
14
  Contract String => String
15
15
  def contracts_download url
16
- open("https://www.#{url}").read
16
+ open("http://www.#{url}").read
17
17
  end
18
18
 
19
- @urls = %w{facebook.com google.com bing.com}
19
+ @urls = %w{google.com bing.com}
20
20
 
21
21
  def benchmark
22
22
  Benchmark.bm 30 do |x|
@@ -0,0 +1 @@
1
+ default: --require features
@@ -0,0 +1,17 @@
1
+ contracts.ruby brings code contracts to the Ruby language.
2
+
3
+ Example:
4
+
5
+ ```ruby
6
+ class Example
7
+ include Contracts::Core
8
+ C = Contracts
9
+
10
+ Contract C::Num, C::Num => C::Num
11
+ def add(a, b)
12
+ a + b
13
+ end
14
+ end
15
+ ```
16
+
17
+ This documentation is [open source](https://github.com/egonSchiele/contracts.ruby/tree/master/features). If you find it incomplete or confusing, please [submit an issue](https://github.com/egonSchiele/contracts.ruby/issues), or, better yet, [a pull request](https://github.com/egonSchiele/contracts.ruby).
@@ -0,0 +1,71 @@
1
+ Feature: Fetching contracted function type
2
+
3
+ You can use `functype(name)` method for that:
4
+
5
+ ```ruby
6
+ functype(:add) # => "add :: Num, Num => Num"
7
+ ```
8
+
9
+ Background:
10
+ Given a file named "example.rb" with:
11
+ """ruby
12
+ require "contracts"
13
+ C = Contracts
14
+
15
+ class Example
16
+ include Contracts::Core
17
+
18
+ Contract C::Num, C::Num => C::Num
19
+ def add(a, b)
20
+ a + b
21
+ end
22
+
23
+ Contract String => String
24
+ def self.greeting(name)
25
+ "Hello, #{name}"
26
+ end
27
+
28
+ class << self
29
+ Contract C::Num => C::Num
30
+ def increment(number)
31
+ number + 1
32
+ end
33
+ end
34
+ end
35
+ """
36
+
37
+ Scenario: functype on instance method
38
+ Given a file named "instance_method_functype.rb" with:
39
+ """ruby
40
+ require "./example"
41
+ puts Example.new.functype(:add)
42
+ """
43
+ When I run `ruby instance_method_functype.rb`
44
+ Then the output should contain:
45
+ """
46
+ add :: Num, Num => Num
47
+ """
48
+
49
+ Scenario: functype on class method
50
+ Given a file named "class_method_functype.rb" with:
51
+ """ruby
52
+ require "./example"
53
+ puts Example.functype(:greeting)
54
+ """
55
+ When I run `ruby class_method_functype.rb`
56
+ Then the output should contain:
57
+ """
58
+ greeting :: String => String
59
+ """
60
+
61
+ Scenario: functype on singleton method
62
+ Given a file named "singleton_method_functype.rb" with:
63
+ """ruby
64
+ require "./example"
65
+ puts Example.functype(:increment)
66
+ """
67
+ When I run `ruby singleton_method_functype.rb`
68
+ Then the output should contain:
69
+ """
70
+ increment :: Num => Num
71
+ """
@@ -0,0 +1,210 @@
1
+ Feature: Simple examples and Contract violations
2
+
3
+ Contracts.ruby allows specification of contracts on per-method basis, where
4
+ method arguments and return value will be validated upon method call.
5
+
6
+ Example:
7
+
8
+ ```ruby
9
+ Contract C::Num, C::Num => C::Num
10
+ def add(a, b)
11
+ a + b
12
+ end
13
+ ```
14
+
15
+ Here `Contract arg_contracts... => return_contract` defines list of argument
16
+ contracts `args_contracts...` as `C::Num, C::Num` (i.e.: both arguments
17
+ should be numbers) and return value contract `return_contract` as `C::Num`
18
+ (i.e.: return value should be a number too).
19
+
20
+ `Contract arg_contracts... => return_contract` affects next defined instance,
21
+ class or singleton method, meaning that all of these work:
22
+
23
+ - [Instance method](#instance-method),
24
+
25
+ - [Class method](#class-method),
26
+
27
+ - [Singleton method](#singleton-method).
28
+
29
+ Whenever invalid argument is passed to a contracted method, corresponding
30
+ `ContractError` will be raised. That happens right after bad value got into
31
+ system protected by contracts and prevents error propagation: first
32
+ non-contracts library frame in exception's backtrace is a culprit for passing
33
+ an invalid argument - you do not need to verify 20-30 frames to find a
34
+ culprit! Example of such error: [instance method contract
35
+ violation](#instance-method-contract-violation).
36
+
37
+ Whenever invalid return value is returned from a contracted method,
38
+ corresponding `ContractError` will be raised. That happens right after method
39
+ returned this value and prevents error propagation: `At: your_filename.rb:17`
40
+ part of error message points directly to a culprit method. Example of such
41
+ error: [return value contract
42
+ violation](#singleton-method-return-value-contract-violation).
43
+
44
+ Contract violation error consists of such parts:
45
+ - Violation type:
46
+ - `Contract violation for argument X of Y: (ParamContractError)`,
47
+ - `Contract violation for return value (ReturnContractError)`.
48
+ - Expected contract, example: `Expected: Num`.
49
+ - Actual value, example: `Actual: "foo"`.
50
+ - Location of violated contract, example: `Value guarded in: Example::add`.
51
+ - Full contract, example: `With Contract: Num, Num => Num`.
52
+ - Source code location of contracted method, example: `At: lib/your_library/some_class.rb:17`.
53
+
54
+ Scenario: Instance method
55
+ Given a file named "instance_method.rb" with:
56
+ """ruby
57
+ require "contracts"
58
+ C = Contracts
59
+
60
+ class Example
61
+ include Contracts::Core
62
+
63
+ Contract C::Num, C::Num => C::Num
64
+ def add(a, b)
65
+ a + b
66
+ end
67
+ end
68
+
69
+ puts Example.new.add(2, 2)
70
+ """
71
+ When I run `ruby instance_method.rb`
72
+ Then the output should contain:
73
+ """
74
+ 4
75
+ """
76
+
77
+ Scenario: Instance method contract violation
78
+ Given a file named "instance_method_violation.rb" with:
79
+ """ruby
80
+ require "contracts"
81
+ C = Contracts
82
+
83
+ class Example
84
+ include Contracts::Core
85
+
86
+ Contract C::Num, C::Num => C::Num
87
+ def add(a, b)
88
+ a + b
89
+ end
90
+ end
91
+
92
+ puts Example.new.add(2, "foo")
93
+ """
94
+ When I run `ruby instance_method_violation.rb`
95
+ Then the output should contain:
96
+ """
97
+ : Contract violation for argument 2 of 2: (ParamContractError)
98
+ Expected: Num,
99
+ Actual: "foo"
100
+ Value guarded in: Example::add
101
+ With Contract: Num, Num => Num
102
+ At: instance_method_violation.rb:8
103
+ """
104
+
105
+ Scenario: Class method
106
+ Given a file named "class_method.rb" with:
107
+ """ruby
108
+ require "contracts"
109
+ C = Contracts
110
+
111
+ class Example
112
+ include Contracts::Core
113
+
114
+ Contract C::Num, C::Num => C::Num
115
+ def self.add(a, b)
116
+ a + b
117
+ end
118
+ end
119
+
120
+ puts Example.add(2, 2)
121
+ """
122
+ When I run `ruby class_method.rb`
123
+ Then the output should contain:
124
+ """
125
+ 4
126
+ """
127
+
128
+ Scenario: Class method contract violation
129
+ Given a file named "class_method_violation.rb" with:
130
+ """ruby
131
+ require "contracts"
132
+ C = Contracts
133
+
134
+ class Example
135
+ include Contracts::Core
136
+
137
+ Contract C::Num, C::Num => C::Num
138
+ def self.add(a, b)
139
+ a + b
140
+ end
141
+ end
142
+
143
+ puts Example.add(:foo, 2)
144
+ """
145
+ When I run `ruby class_method_violation.rb`
146
+ Then the output should contain:
147
+ """
148
+ : Contract violation for argument 1 of 2: (ParamContractError)
149
+ Expected: Num,
150
+ Actual: :foo
151
+ Value guarded in: Example::add
152
+ With Contract: Num, Num => Num
153
+ At: class_method_violation.rb:8
154
+ """
155
+
156
+ Scenario: Singleton method
157
+ Given a file named "singleton_method.rb" with:
158
+ """ruby
159
+ require "contracts"
160
+ C = Contracts
161
+
162
+ class Example
163
+ include Contracts::Core
164
+
165
+ class << self
166
+ Contract C::Num, C::Num => C::Num
167
+ def add(a, b)
168
+ a + b
169
+ end
170
+ end
171
+ end
172
+
173
+ puts Example.add(2, 2)
174
+ """
175
+ When I run `ruby singleton_method.rb`
176
+ Then the output should contain:
177
+ """
178
+ 4
179
+ """
180
+
181
+ Scenario: Singleton method return value contract violation
182
+ Given a file named "singleton_method_violation.rb" with:
183
+ """ruby
184
+ require "contracts"
185
+ C = Contracts
186
+
187
+ class Example
188
+ include Contracts::Core
189
+
190
+ class << self
191
+ Contract C::Num, C::Num => C::Num
192
+ def add(a, b)
193
+ # notice here non-number is returned
194
+ nil
195
+ end
196
+ end
197
+ end
198
+
199
+ puts Example.add(2, 2)
200
+ """
201
+ When I run `ruby singleton_method_violation.rb`
202
+ Then the output should contain:
203
+ """
204
+ : Contract violation for return value: (ReturnContractError)
205
+ Expected: Num,
206
+ Actual: nil
207
+ Value guarded in: Example::add
208
+ With Contract: Num, Num => Num
209
+ At: singleton_method_violation.rb:9
210
+ """