constrain 0.1.0 → 0.1.1
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 +132 -34
- data/TODO +5 -0
- data/lib/constrain.rb +10 -6
- data/lib/constrain/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef3357b49faba9f9d3bfe5dbfb3d27b70d1493456b4dea29afb96c811b4eff8b
|
4
|
+
data.tar.gz: 590bb604f5566f76d4473f1f9191ef2ca21bfdcf9a904b92a0b6fa0388263c12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec0d44e4cb0a7b4c3c8291bc10d821000769d36fe30c44aed85c30b3993076796169f7efa394cd47b0694744b47e6354e7413fb1c8b3a814d3a42d91cdeff04d
|
7
|
+
data.tar.gz: 9b60ecec9cb4edd2dc507b10835df150094b9fc3a939735be6935a93df4d5f049bbf4fe963eebb7ebf07a4faf21fa13cf3908a4ea44ac93178f97ef9b29d2612
|
data/README.md
CHANGED
@@ -4,6 +4,23 @@
|
|
4
4
|
typically used to check the type of method parameters and is an alternative to
|
5
5
|
using Ruby-3 .rbs files but with a different syntax and only dynamic checks
|
6
6
|
|
7
|
+
```ruby
|
8
|
+
include Constrain
|
9
|
+
|
10
|
+
# f takes a String and an array of Integer objects and raises otherwise
|
11
|
+
def f(a, b)
|
12
|
+
constrain a, String
|
13
|
+
constrain b, [Integer]
|
14
|
+
...
|
15
|
+
end
|
16
|
+
|
17
|
+
f("Hello", [1, 2]) # Doesn't raise
|
18
|
+
f("Hello", "world") # Boom
|
19
|
+
```
|
20
|
+
|
21
|
+
It is intended to be an aid in development only and to be deactivated in
|
22
|
+
production (TODO: Make it possible to deactivate)
|
23
|
+
|
7
24
|
Constrain works with ruby-2 (and maybe ruby-3)
|
8
25
|
|
9
26
|
## Installation
|
@@ -24,62 +41,139 @@ Or install it yourself as:
|
|
24
41
|
|
25
42
|
## Usage
|
26
43
|
|
27
|
-
You
|
44
|
+
You will typically include Constrain globally to have #constrain available everywhere
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
require 'constrain'
|
48
|
+
|
49
|
+
# Include globally to make #constrain available everywhere
|
50
|
+
include Constrain
|
51
|
+
|
52
|
+
def f(a, b, c)
|
53
|
+
constrain a, Integer # An integer
|
54
|
+
constrain b, [Symbol, String] => Integer # Hash with String or Symbol keys
|
55
|
+
constrain c, [String], NilClass # Array of strings or nil
|
56
|
+
...
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
The alternative is to include the constrain Module in a common root class to
|
61
|
+
have it available in all child class
|
62
|
+
|
63
|
+
The #constrain method has the following signature
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
constrain(value, *class-expressions, message = nil)
|
67
|
+
```
|
28
68
|
|
29
|
-
|
69
|
+
It checks that the value matches at least one of the class-expressions
|
70
|
+
and raise a Constrain::TypeError if not. The error message can be customized by
|
71
|
+
added the message argument. #constrain also raise a Constrain::Error exception
|
72
|
+
if there is an error in the class expression. It is typically used to
|
73
|
+
type-check parameters in methods
|
30
74
|
|
31
|
-
|
32
|
-
def f(a, b)
|
33
|
-
constrain a, String
|
34
|
-
constrain b, [Integer]
|
35
|
-
end
|
75
|
+
Constrain also defines a #check class method with the signature
|
36
76
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
expression. Both methods raise a Constrain::Error if the expression is invalid
|
77
|
+
```ruby
|
78
|
+
Constrain.check(value, *class-expression) -> true or false
|
79
|
+
```
|
41
80
|
|
42
|
-
|
81
|
+
It matches value against the class expressions like #constrain but returns true
|
82
|
+
or false as result
|
83
|
+
|
84
|
+
## Class Expressions
|
43
85
|
|
44
86
|
Constrain#constrain and Constrain::check use class expressions composed of
|
45
|
-
Class objects, Proc objects, or arrays and hashes of class
|
87
|
+
Class objects, Proc objects, or arrays and hashes of class expressions. Class
|
46
88
|
objects match if the value is an instance of the class:
|
47
89
|
|
48
|
-
|
49
|
-
|
90
|
+
```ruby
|
91
|
+
constrain 42, Integer # Success
|
92
|
+
constrain 42, String # Failure
|
93
|
+
```
|
94
|
+
|
95
|
+
More than one class expression is allowed. It matches if at least one of the expressions match:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
constrain "str", Symbol, String # Success
|
99
|
+
constrain :sym, Symbol, String # Success
|
100
|
+
constrain 42, Symbol, String # Failure
|
101
|
+
```
|
50
102
|
|
51
|
-
|
52
|
-
you to do value comparison for those types:
|
103
|
+
#### nil, true and false
|
53
104
|
|
54
|
-
|
55
|
-
|
105
|
+
NilClass is a valid argument and can be used to allow nil values:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
constrain nil, Integer # Failure
|
109
|
+
constrain nil, Integer, NilClass # Success
|
110
|
+
```
|
111
|
+
|
112
|
+
Boolean values are a special case since ruby doesn't have a boolean type use a
|
113
|
+
list to match for a boolean argument:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
constrain true, TrueClass, FalseClass # Success
|
117
|
+
constrain false, TrueClass, FalseClass # Success
|
118
|
+
constrain nil, TrueClass, FalseClass # Failure
|
119
|
+
```
|
120
|
+
|
121
|
+
#### Proc objects
|
56
122
|
|
57
123
|
Proc objects are called with the value as argument and should return truish or falsy:
|
58
124
|
|
59
|
-
|
60
|
-
|
61
|
-
|
125
|
+
```ruby
|
126
|
+
constrain 42, lambda { |value| value > 1 } # Success
|
127
|
+
constrain 0, lambda { |value| value > 1 } # Failure
|
128
|
+
```
|
129
|
+
|
130
|
+
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:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
constrain 0, Integer # Success
|
134
|
+
constrain 0, lambda { |value| value > 1 } # Failure
|
135
|
+
```
|
62
136
|
|
63
|
-
Proc objects can check every aspect of an object
|
64
|
-
|
65
|
-
|
137
|
+
Proc objects can check every aspect of an object but you should not overuse it
|
138
|
+
because as checks becomes more complex they tend to include business logic that
|
139
|
+
should be kept in the production code. Constrain is only thouhgt of as a tool
|
140
|
+
to catch developer errors - not errors that stem from corrupted data
|
141
|
+
|
142
|
+
#### Arrays
|
66
143
|
|
67
144
|
Arrays match if the value is an Array and all its element match the given class expression:
|
68
145
|
|
69
|
-
|
70
|
-
|
146
|
+
```ruby
|
147
|
+
constrain [42], [Integer] # Success
|
148
|
+
constrain [42], [String] # Failure
|
149
|
+
```
|
71
150
|
|
72
151
|
Arrays can be nested
|
73
152
|
|
74
|
-
|
153
|
+
```ruby
|
154
|
+
constrain [[42]], [[Integer]] # Success
|
155
|
+
constrain [42], [[Integer]] # Failure
|
156
|
+
```
|
157
|
+
|
158
|
+
More than one element class is allowed
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
constrain ["str"], [String, Symbol] # Success
|
162
|
+
constrain [:sym], [String, Symbol] # Success
|
163
|
+
constrain [42], [String, Symbol] # Failure
|
164
|
+
```
|
165
|
+
|
166
|
+
Note that `[` ... `]` is treated specially in hashes
|
75
167
|
|
76
|
-
|
168
|
+
#### Hashes
|
77
169
|
|
78
170
|
Hashes match if value is a hash and every key/value pair match one of the given
|
79
171
|
key-class/value-class expressions:
|
80
172
|
|
81
|
-
|
82
|
-
|
173
|
+
```ruby
|
174
|
+
constrain({"str" => 42}, String => Integer) # Success
|
175
|
+
constrain({"str" => 42}, String => String) # Failure
|
176
|
+
```
|
83
177
|
|
84
178
|
Note that the parenthesis are needed because otherwise the Ruby parser would
|
85
179
|
interpret the hash as block argument to #constrain
|
@@ -89,13 +183,17 @@ expression match. List are annotated as an array but contains more than one
|
|
89
183
|
element so that `[String, Symbol]` matches either a String or a Symbol value
|
90
184
|
while `[String]` matches an array of String objects:
|
91
185
|
|
92
|
-
|
93
|
-
|
186
|
+
```ruby
|
187
|
+
constrain({ sym: 42 }, [Symbol, String] => Integer) # Success
|
188
|
+
constrain({ [sym] => 42 }, [Symbol, String] => Integer) # Failure
|
189
|
+
```
|
94
190
|
|
95
191
|
To specify an array of Symbol or String objects in hash keys or values, make
|
96
192
|
sure the list expression is enclosed in an array:
|
97
193
|
|
98
|
-
|
194
|
+
```ruby
|
195
|
+
constrain({ [sym] => 42 }, [[Symbol, String]] => Integer) # Success
|
196
|
+
```
|
99
197
|
|
100
198
|
## Contributing
|
101
199
|
|
data/TODO
ADDED
data/lib/constrain.rb
CHANGED
@@ -6,8 +6,8 @@ module Constrain
|
|
6
6
|
|
7
7
|
# Raised if types doesn't match a class expression
|
8
8
|
class TypeError < Error
|
9
|
-
def initialize(value, exprs)
|
10
|
-
super "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}"
|
9
|
+
def initialize(value, exprs, msg = nil)
|
10
|
+
super msg || "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}"
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -15,10 +15,14 @@ module Constrain
|
|
15
15
|
# Constrain::Error if the expression is invalid and a Constrain::TypeError if
|
16
16
|
# the value doesn't match
|
17
17
|
def constrain(value, *exprs)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
msg = exprs.pop if exprs.last.is_a?(String)
|
19
|
+
begin
|
20
|
+
!exprs.empty? or raise Error, "Empty class expression"
|
21
|
+
exprs.any? { |expr| Constrain.check(value, expr) } or raise TypeError.new(value, exprs, msg)
|
22
|
+
rescue Error => ex
|
23
|
+
ex.set_backtrace(caller[1..-1])
|
24
|
+
raise
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
28
|
# Return true if the value matches the class expression. Raises a
|
data/lib/constrain/version.rb
CHANGED
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.1.
|
4
|
+
version: 0.1.1
|
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-
|
11
|
+
date: 2021-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: simplecov
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- Gemfile
|
47
47
|
- README.md
|
48
48
|
- Rakefile
|
49
|
+
- TODO
|
49
50
|
- bin/console
|
50
51
|
- bin/setup
|
51
52
|
- constrain.gemspec
|