contracts 0.11.0 → 0.12.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 (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
+ """