constrain 0.2.2 → 0.3.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.
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