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 +4 -4
- data/README.md +122 -72
- data/TODO +1 -0
- data/lib/constrain/version.rb +1 -1
- data/lib/constrain.rb +9 -19
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fda0b95a5d43c197c9df1134d68dbe4fbc1f8f71ad9dc7a8e995daf729eaea2c
|
4
|
+
data.tar.gz: 2fe722e5085bc9b396e203b3f3514a1f123c18843e293f902392519ef6a3d5ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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, \*
|
50
|
+
#### constrain(value, \*expressions, message = nil, unwind: 0)
|
66
51
|
|
67
|
-
Return the given value if it matches at least one of the
|
68
|
-
Constrain::TypeError if not. The
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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, \*
|
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, \*
|
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
|
90
|
-
|
91
|
-
|
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
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
120
|
-
constrain
|
96
|
+
def print_color(color)
|
97
|
+
constrain color, :red, :yellow, :green
|
98
|
+
...
|
99
|
+
end
|
121
100
|
```
|
122
101
|
|
123
|
-
|
124
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
115
|
+
### Class Expressions
|
133
116
|
|
134
|
-
|
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,
|
138
|
-
constrain
|
122
|
+
constrain 42, Integer # Success
|
123
|
+
constrain 42, Comparable # Success
|
124
|
+
constrain nil, Comparable # Failure
|
139
125
|
```
|
140
126
|
|
141
|
-
|
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
|
145
|
-
constrain
|
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
data/lib/constrain/version.rb
CHANGED
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
|
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::
|
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
|
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
|
-
|
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.
|
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-
|
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.
|
74
|
+
rubygems_version: 3.2.26
|
75
75
|
signing_key:
|
76
76
|
specification_version: 4
|
77
77
|
summary: Dynamic in-file type checking
|