constrain 0.2.2 → 0.3.0

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
  SHA256:
3
- metadata.gz: 21ca1b88c0ffb91b40a2a2335ead91e2570108f8b67eaa9ea7fa3ebe00ada4c4
4
- data.tar.gz: '083da1149466231ca8de0dc216cb00c46c540ebc1b7558a0d26422c33b5af9c4'
3
+ metadata.gz: fda0b95a5d43c197c9df1134d68dbe4fbc1f8f71ad9dc7a8e995daf729eaea2c
4
+ data.tar.gz: 2fe722e5085bc9b396e203b3f3514a1f123c18843e293f902392519ef6a3d5ed
5
5
  SHA512:
6
- metadata.gz: 7b40d638495bf36bd661fd1f8ab7845ec177a3f5cb2f73ddfd400976a9b02f9f8b2f16b2b0301b287cd419c43a2e961e895f96b6e9fd7a46545c96c71a954a28
7
- data.tar.gz: 7be37bc44024db00f04d1355cc4bf340ca8728b2ff3014e27bda54f551bab85843f383f61890a8eaf63c65a14907061b3ce49f676732e1b0ad774b0e0e727541
6
+ metadata.gz: bc98410262f21b5fea23182cad7c9e80e48a4aff7ff64dc7ff2113581440a191bfa9b77a28045624caf4f6e3102251e175841604c64461287ea7eb1062174bf2
7
+ data.tar.gz: c19a11b5ab9e22b49ad2204b276658d26b109296815e06298620fdb53104f6ce70625095b4ac5b3fbd3aff79664866de7afb3507cea1b5d1737aaf714c902c7e
data/README.md CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  `Constrain` allows you to check if an object match a class expression. It is
4
4
  typically used to check the type of method parameters and is an alternative to
5
- using Ruby-3 .rbs files but with a different syntax and only dynamic checks
5
+ using Ruby-3 .rbs files
6
6
 
7
7
  ```ruby
8
+ require 'constrain'
8
9
  include Constrain
9
10
 
10
11
  # f takes a String and an array of Integer objects and raises otherwise
@@ -23,22 +24,6 @@ production (TODO: Make it possible to deactivate)
23
24
 
24
25
  Constrain works with ruby-2 (and maybe ruby-3)
25
26
 
26
- ## Installation
27
-
28
- Add this line to your application's Gemfile:
29
-
30
- ```ruby
31
- gem 'constrain'
32
- ```
33
-
34
- And then execute:
35
-
36
- $ bundle install
37
-
38
- Or install it yourself as:
39
-
40
- $ gem install constrain
41
-
42
27
  ## Usage
43
28
 
44
29
  You will typically include Constrain globally to have #constrain available everywhere
@@ -62,14 +47,19 @@ have it available in all child classes
62
47
 
63
48
  ## Methods
64
49
 
65
- #### constrain(value, \*class-expressions, message = nil, unwind: 0)
50
+ #### constrain(value, \*expressions, message = nil, unwind: 0)
66
51
 
67
- Return the given value if it matches at least one of the class-expressions and raise a
68
- Constrain::TypeError if not. The error message can be customized by added the
69
- message argument and a number of backtrace leves can be skipped by setting
70
- :unwind option. By default the backtrace will refer to the point of the call of
71
- \#constrain. \#constrain raises a Constrain::Error exception if there is
72
- an error in the syntax of the class expression
52
+ Return the given value if it matches at least one of the expressions and raise a
53
+ Constrain::TypeError if not. The value is matched against the expressions using
54
+ the #=== operator so anything you can put into the 'when' clause of a 'case'
55
+ statement can be used. #constrain raise a Constrain::MatchError if the value
56
+ doesn't match any expression
57
+
58
+ The error message can be customized by added the message argument and a number
59
+ of backtrace leves can be skipped by setting :unwind option. By default the
60
+ backtrace will refer to the point of the call of \#constrain. \#constrain
61
+ raises a Constrain::Error exception if there is an error in the syntax of the
62
+ class expression
73
63
 
74
64
  \#constrain is typically used to type-check parameters in methods where you
75
65
  want an exception if the parameters doesn't match the expected, but because it
@@ -77,84 +67,71 @@ returns the value if successful it can be used to check the validity of
77
67
  variables in expressions too, eg. `return constrain(result_of_complex_computation, Integer)`
78
68
  to check the return value of a method
79
69
 
80
- #### Constrain.constrain(value, \*class-expressions, message = nil, unwind: 0)
70
+ #### Constrain.constrain(value, \*expressions, message = nil, unwind: 0)
81
71
 
82
72
  Class method version of #constrain. It is automatically added to classes that
83
73
  include Constrain
84
74
 
85
75
 
86
- #### Constrain.constrain?(value, \*class-expression) -> true or false
76
+ #### Constrain.constrain?(value, \*expressions) -> true or false
87
77
 
88
78
  It matches value against the class expressions like #constrain but returns true
89
- or false as result and can be used to handle complex type expressions
90
- dynamically. It is automatically added to classes that include Constrain.
91
- Constrain.constrain? raises a Constrain::Error exception if there is an error
92
- in the syntax of the class expression
79
+ or false as result. It is automatically added to classes that include
80
+ Constrain. Constrain.constrain? raises a Constrain::Error exception if there
81
+ is an error in the syntax of the class expression
93
82
 
94
- ## Class Expressions
95
83
 
96
- Constrain#constrain and Constrain::constrain? use class expressions composed of
97
- class or module objects, Proc objects, or arrays and hashes of class expressions. Class or module
98
- objects match if `value.is_a?(class_or_module)` returns true:
84
+ ## Expressions
99
85
 
100
- ```ruby
101
- constrain 42, Integer # Success
102
- constrain 42, Comparable # Success
103
- constrain nil, Comparable # Failure
104
- ```
86
+ Expressions can be simple values, class expressions, or lambdas. You can mix
87
+ simple values and class expressions but not lambdas
105
88
 
106
- More than one class expression is allowed. It matches if at least one of the expressions match:
107
89
 
108
- ```ruby
109
- constrain "str", Symbol, String # Success
110
- constrain :sym, Symbol, String # Success
111
- constrain 42, Symbol, String # Failure
112
- ```
90
+ ### Simple expressions
113
91
 
114
- #### nil, true and false
115
-
116
- NilClass is a valid argument and can be used to allow nil values:
92
+ Simple values is an easy way to check arguments with a limited set of allowed
93
+ values like
117
94
 
118
95
  ```ruby
119
- constrain nil, Integer # Failure
120
- constrain nil, Integer, NilClass # Success
96
+ def print_color(color)
97
+ constrain color, :red, :yellow, :green
98
+ ...
99
+ end
121
100
  ```
122
101
 
123
- Boolean values are a special case since ruby doesn't have a boolean type use a
124
- list to match for a boolean argument:
102
+ Simple values are compared to the expected result using the #=== operator. This
103
+ means you can use regular expressions too:
125
104
 
126
105
  ```ruby
127
- constrain true, TrueClass, FalseClass # Success
128
- constrain false, TrueClass, FalseClass # Success
129
- constrain nil, TrueClass, FalseClass # Failure
106
+ # Simple email regular expression (https://stackoverflow.com/a/719543)
107
+ EMAIL_RE = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/
108
+
109
+ def email(address)
110
+ constrain address, EMAIL_RE
111
+ ...
112
+ end
130
113
  ```
131
114
 
132
- #### Proc objects
115
+ ### Class Expressions
133
116
 
134
- Proc objects are called with the value as argument and should return truish or falsy:
117
+ Constrain#constrain and Constrain::constrain? use class expressions composed of
118
+ class or module objects, Proc objects, or arrays and hashes of class expressions. Class or module
119
+ objects match if `value.is_a?(class_or_module)` returns true:
135
120
 
136
121
  ```ruby
137
- constrain 42, lambda { |value| value > 1 } # Success
138
- constrain 0, lambda { |value| value > 1 } # Failure
122
+ constrain 42, Integer # Success
123
+ constrain 42, Comparable # Success
124
+ constrain nil, Comparable # Failure
139
125
  ```
140
126
 
141
- Note that it is not possible to first match against a class expression and then use the proc object. You will either have to check for the type too in the proc object or make two calls to #constrain:
127
+ More than one class expression is allowed. It matches if at least one of the expressions match:
142
128
 
143
129
  ```ruby
144
- constrain 0, Integer # Success
145
- constrain 0, lambda { |value| value > 1 } # Failure
130
+ constrain "str", Symbol, String # Success
131
+ constrain :sym, Symbol, String # Success
132
+ constrain 42, Symbol, String # Failure
146
133
  ```
147
134
 
148
- Proc objects are a little more verbose than checking the constraint without
149
- \#constrain but it allows the use of the :unwind option to manipulate the
150
- apparent origin in the source of the exception
151
-
152
- Note that even though Proc objects can check every aspect of an object but you
153
- should not overuse it because as checks becomes more complex they tend to
154
- include business logic that should be kept in the production code. Constrain is
155
- only thouhgt of as a tool to catch developer errors - not errors that stem from
156
- corrupted data
157
-
158
135
  #### Arrays
159
136
 
160
137
  Arrays match if the value is an Array and all its element match the given class expression:
@@ -211,6 +188,79 @@ sure the list expression is enclosed in an array:
211
188
  constrain({ [sym] => 42 }, [[Symbol, String]] => Integer) # Success
212
189
  ```
213
190
 
191
+ #### nil, true and false
192
+
193
+ NilClass is a valid argument and can be used to allow nil values:
194
+
195
+ ```ruby
196
+ constrain nil, Integer # Failure
197
+ constrain nil, Integer, NilClass # Success
198
+ ```
199
+
200
+ Boolean values are a special case since ruby doesn't have a boolean type use a
201
+ list to match for a boolean argument:
202
+
203
+ ```ruby
204
+ constrain true, TrueClass, FalseClass # Success
205
+ constrain false, TrueClass, FalseClass # Success
206
+ constrain nil, TrueClass, FalseClass # Failure
207
+ ```
208
+
209
+ But note that it is often easier to use value expressions:
210
+
211
+ ```ruby
212
+ constrain true, true, false # Success
213
+ constrain false, true, false # Success
214
+ constrain nil, true, false # Failure
215
+ constrain nil, true, false, nil # Success
216
+ ```
217
+
218
+ ### Lambda expressions
219
+
220
+ Proc objects are called with the value as argument and should return truish or falsy:
221
+
222
+ ```ruby
223
+ constrain 42, lambda { |value| value > 1 } # Success
224
+ constrain 0, lambda { |value| value > 1 } # Failure
225
+ ```
226
+
227
+ Note that it is not possible to first match against a class expression and then
228
+ use the proc object. You will either have to check for the type too in the proc
229
+ object or make two calls to #constrain:
230
+
231
+ ```ruby
232
+ constrain 0, Integer # Success
233
+ constrain 0, lambda { |value| value > 1 } # Failure
234
+ ```
235
+
236
+ Alternatively, you can use Constrain::constrain? to mix classes or value with lambdas:
237
+
238
+ ```ruby
239
+ constrain 0, lambda { |value| Constrain::constrain?(Integer) && value > 1 } # Failure
240
+ ```
241
+
242
+ Note that even though Proc objects can check every aspect of an object, you
243
+ should not overuse it because as checks becomes more complex they tend to
244
+ include business logic that should be kept in the production code. Constrain is
245
+ only thouhgt of as a tool to catch developer errors - not errors that stem from
246
+ corrupted data
247
+
248
+ ## Installation
249
+
250
+ Add this line to your application's Gemfile:
251
+
252
+ ```ruby
253
+ gem 'constrain'
254
+ ```
255
+
256
+ And then execute:
257
+
258
+ $ bundle install
259
+
260
+ Or install it yourself as:
261
+
262
+ $ gem install constrain
263
+
214
264
  ## Contributing
215
265
 
216
266
  Bug reports and pull requests are welcome on GitHub at https://github.com/clrgit/constrain.
data/TODO CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  o Class | Class syntax
3
3
  o attr_reader :variable, ClassExpr
4
+ o 'constrain arg, :one_value, :another_value, 1, 2, 3'
4
5
 
5
6
  + constrain value, class-expr, "Error message"
@@ -1,3 +1,3 @@
1
1
  module Constrain
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/constrain.rb CHANGED
@@ -1,25 +1,11 @@
1
1
  require "constrain/version"
2
- module Foo
3
-
4
- module InstanceMethods
5
- def bar1
6
- 'bar1'
7
- end
8
- end
9
-
10
- module ClassMethods
11
- def bar2
12
- 'bar2'
13
- end
14
- end
15
- end
16
2
 
17
3
  module Constrain
18
4
  # Raised on any error
19
5
  class Error < StandardError; end
20
6
 
21
7
  # Raised if types doesn't match a class expression
22
- class TypeError < Error
8
+ class MatchError < Error
23
9
  def initialize(value, exprs, msg = nil, unwind: 0)
24
10
  super msg || "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}"
25
11
  end
@@ -34,15 +20,18 @@ module Constrain
34
20
  Constrain.do_constrain(value, *exprs)
35
21
  end
36
22
 
23
+ # Like #constrain but returns true/false to indicate the result instead of
24
+ # raising an exception
37
25
  def constrain?(value, expr)
38
26
  Constrain.do_constrain?(value, expr)
39
27
  end
40
28
 
41
29
  # :call-seq:
42
30
  # constrain(value, *class-expressions, unwind: 0)
31
+ # constrain(value, *values, unwind: 0)
43
32
  #
44
33
  # Check that value matches one of the class expressions. Raises a
45
- # Constrain::Error if the expression is invalid and a Constrain::TypeError if
34
+ # Constrain::Error if the expression is invalid and a Constrain::MatchError if
46
35
  # the value doesn't match. The exception's backtrace skips :unwind number of
47
36
  # entries
48
37
  def self.constrain(value, *exprs)
@@ -79,7 +68,7 @@ module Constrain
79
68
  begin
80
69
  !exprs.empty? or raise Error, "Empty class expression"
81
70
  exprs.any? { |expr| Constrain.do_constrain?(value, expr) } or
82
- raise TypeError.new(value, exprs, msg, unwind: unwind)
71
+ raise MatchError.new(value, exprs, msg, unwind: unwind)
83
72
  rescue Error => ex
84
73
  ex.set_backtrace(caller[1 + unwind..-1])
85
74
  raise
@@ -109,7 +98,7 @@ module Constrain
109
98
  when Proc
110
99
  expr.call(value)
111
100
  else
112
- raise Error, "Illegal expression #{expr.inspect}"
101
+ expr === value
113
102
  end
114
103
  end
115
104
 
@@ -126,11 +115,12 @@ module Constrain
126
115
  def self.fmt_expr(expr)
127
116
  case expr
128
117
  when Class, Module; expr.to_s
118
+ when Regexp; expr.to_s
129
119
  when Array; "[" + expr.map { |expr| fmt_expr(expr) }.join(", ") + "]"
130
120
  when Hash; "{" + expr.map { |k,v| "#{fmt_expr(k)} => #{fmt_expr(v)}" }.join(", ") + "}"
131
121
  when Proc; "Proc@#{expr.source_location.first}:#{expr.source_location.last}"
132
122
  else
133
- raise Error, "Illegal expression"
123
+ raise Error, "Illegal expression: #{expr.inspect}"
134
124
  end
135
125
  end
136
126
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: constrain
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-24 00:00:00.000000000 Z
11
+ date: 2021-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simplecov
@@ -71,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
71
  - !ruby/object:Gem::Version
72
72
  version: '0'
73
73
  requirements: []
74
- rubygems_version: 3.1.4
74
+ rubygems_version: 3.2.26
75
75
  signing_key:
76
76
  specification_version: 4
77
77
  summary: Dynamic in-file type checking