constrain 0.8.0 → 0.10.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: 7b497524127f01a38cc3c6f4061e91e0cad65b587538d57f69b194a550d46bf1
4
- data.tar.gz: 95cff0709eb06ab088fee635c5629de99d16f521f9d24ea0a603ddb4d0968780
3
+ metadata.gz: ceffc61373ff30143743c0cca412f43abe64bbb6e012722c1ba2ab5035fe939b
4
+ data.tar.gz: ed70c819e47438f269c1d58d1291d4cde4eef22fc73049fd67c4b4d1b9c3da7a
5
5
  SHA512:
6
- metadata.gz: f843d4f4b653ab255f52d9c5188b3d9d72c0170e0f4af4522404e4d993b97f6629b0b61c665a9cc47f3fc89a22ca7f7beb9efd68dfdae186c5d1502b320004d7
7
- data.tar.gz: 70654e57d920e7f13a92394d57f7b5ddff2529ebe346507de201f47ce9aad77beef17992419dea84f1ba795b4de7d8443a65d3250b9a46298f8b1896d43dbeb4
6
+ metadata.gz: 94aeb7517bb00357f29bedf15a0b509fcefb28664e9c31e59f3a592e64c69da601a1e379cfeb473e7af590f69d1879c0dded102e55f159ecb8ef5dd8d8a84f58
7
+ data.tar.gz: e1eb0ec5ad04063965262a6be821027859a61c5fa42ec3b97da18d2e95e5193b95a8f96e4bf3a222bf866bd8e0c38579f7d50dd5380b305b3cc3badb66eceebf
data/README.md CHANGED
@@ -22,7 +22,7 @@ f("Hello", "world") # Boom
22
22
  It is intended to be an aid in development only and to be deactivated in
23
23
  production (TODO: Make it possible to deactivate)
24
24
 
25
- Constrain works with ruby-2 (and maybe ruby-3)
25
+ Constrain works with ruby-3
26
26
 
27
27
  ## Usage
28
28
 
@@ -35,15 +35,24 @@ require 'constrain'
35
35
  include Constrain
36
36
 
37
37
  def f(a, b, c)
38
- constrain a, Integer # An integer
39
- constrain b, [Symbol, String] => Integer # Hash with String or Symbol keys
40
- constrain c, [String], NilClass # Array of strings or nil
38
+ constrain a, Integer # An integer
39
+ constrain b, { [Symbol, String] => Integer } # Hash with String or Symbol keys
40
+ constrain c, [String], NilClass # Array of strings or nil
41
41
  ...
42
42
  end
43
43
  ```
44
44
 
45
- The alternative is to include the constrain Module in a common root class to
46
- have it available in all child classes
45
+ Constrain only defines the methods \#constrain and \#constrain? so it is
46
+ acceptible in most cases. An alternative is to include the constrain module
47
+ in a common root class to have it available in all child classes:
48
+
49
+ ```ruby
50
+ class BaseClass
51
+ include Constrain
52
+ ...
53
+ end
54
+ ```
55
+
47
56
 
48
57
  ## Methods
49
58
 
@@ -103,11 +112,11 @@ Simple values are compared to the expected result using the #=== operator. This
103
112
  means you can use regular expressions too:
104
113
 
105
114
  ```ruby
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.-]+$/
115
+ # Simple (!) email address regular expression (https://stackoverflow.com/a/719543)
116
+ EMAIL_ADDRESS_RE = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9.-]+$/
108
117
 
109
- def email(address)
110
- constrain address, EMAIL_RE
118
+ def send_email(address)
119
+ constrain address, EMAIL_ADDRESS_RE
111
120
  ...
112
121
  end
113
122
  ```
@@ -164,8 +173,8 @@ Hashes match if value is a hash and every key/value pair match one of the given
164
173
  key-class/value-class expressions:
165
174
 
166
175
  ```ruby
167
- constrain({"str" => 42}, String => Integer) # Success
168
- constrain({"str" => 42}, String => String) # Failure
176
+ constrain({"str" => 42}, { String => Integer }) # Success
177
+ constrain({"str" => 42}, { String => String }) # Failure
169
178
  ```
170
179
 
171
180
  Note that the parenthesis are needed because otherwise the Ruby parser would
@@ -177,15 +186,15 @@ element so that `[String, Symbol]` matches either a String or a Symbol value
177
186
  while `[String]` matches an array of String objects:
178
187
 
179
188
  ```ruby
180
- constrain({ sym: 42 }, [Symbol, String] => Integer) # Success
181
- constrain({ [sym] => 42 }, [Symbol, String] => Integer) # Failure
189
+ constrain({ sym: 42 }, { [Symbol, String] => Integer }) # Success
190
+ constrain({ [sym] => 42 }, { [Symbol, String] => Integer }) # Failure
182
191
  ```
183
192
 
184
193
  To specify an array of Symbol or String objects in hash keys or values, make
185
194
  sure the list expression is enclosed in an array:
186
195
 
187
196
  ```ruby
188
- constrain({ [sym] => 42 }, [[Symbol, String]] => Integer) # Success
197
+ constrain({ [sym] => 42 }, { [[Symbol, String]] => Integer }) # Success
189
198
  ```
190
199
 
191
200
  #### nil, true and false
@@ -245,6 +254,38 @@ include business logic that should be kept in the production code. Constrain is
245
254
  only thouhgt of as a tool to catch developer errors - not errors that stem from
246
255
  corrupted data
247
256
 
257
+ ## Other uses
258
+
259
+ Constrain can be used to type-check complex structures like YAML documents:
260
+
261
+ ```ruby
262
+ # A YAML value
263
+ value = {
264
+ "str" => "a",
265
+ "int" => 42,
266
+ "arr" => [1, 2],
267
+ "hash" => {
268
+ "key1" => "b",
269
+ "key2" => 42
270
+ }
271
+ }
272
+
273
+ # Type description
274
+ type = {
275
+ "str" => String,
276
+ "int" => Integer,
277
+ "arr" => [Integer],
278
+ "hash" => {
279
+ "key1" => [String, Integer],
280
+ "key2" => [String, Integer]
281
+ }
282
+ }
283
+
284
+ puts constrain?(value, type) ? "yes" : "no" # Outputs 'yes'
285
+ value["str"] = 42
286
+ puts constrain?(value, type) ? "yes" : "no" # Outputs 'no'
287
+ ```
288
+
248
289
  ## Installation
249
290
 
250
291
  Add this line to your application's Gemfile:
data/TODO CHANGED
@@ -1,14 +1,25 @@
1
1
 
2
+ o A constrain_class method that allows for inherited-from tests
3
+ o Allow single-argument boolean expressions: 'constrain !a.empty?'
4
+ o This seems to cause a stack error: 'constrain some-hash, Symbol'
5
+ o Allow range as value expressions. And REs!
2
6
  o Use | to create class-or expressions
3
- o Old: Class | Class syntax
7
+ o Class | Class syntax
8
+ o Will solve problem with [String, Integer] as a String/Integer vs. An array
9
+ of strings and integers
4
10
  o Use & to construct tuple expressions
5
- o Old: A tuple method: "Symbol => constrain.tuple(String, Integer)"
11
+ o Class & Class (doesn't look good - maybe Class + Class ?)
12
+ o Alt: A tuple method: "Symbol => constrain.tuple(String, Integer)"
6
13
  o Better error message for 'constrain EXPR'
7
14
  o Explain that 'constrain EXPR' can be used for 'constrain SomeClass < AnotherClass'
8
15
  o Match ranges and regular expressions
9
16
  o An array and hash method: "Symbol => constrain.array(Integer), String => constrain.hash(Symbol, Integer)"
17
+ Nope. Solved by | syntax
10
18
  o Constrained attributes: constrain_reader, constrain_writer, constrain_accessor:
11
- o Messages should include info about the unexpected element type in arrays (and maybe more): "Expected [#<PgGraph::Data::Record:public.pings[1] {id: 1, name: 'Ping A'}>, #<PgGraph::Data::Record:public.pings[2] {id: 2, name: 'Ping B'}>, nil] to match [PgGraph::Data::Record]
19
+ o Messages should include info about the unexpected element type in arrays (and
20
+ maybe more): "Expected [#<PgGraph::Data::Record:public.pings[1] {id: 1, name:
21
+ 'Ping A'}>, #<PgGraph::Data::Record:public.pings[2] {id: 2, name: 'Ping B'}>,
22
+ nil] to match [PgGraph::Data::Record]
12
23
 
13
24
  + constrain value, class-expr, "Error message"
14
25
  + Check against values: 'constrain arg, :one_value, :another_value, 1, 2, 3'
@@ -1,3 +1,3 @@
1
1
  module Constrain
2
- VERSION = "0.8.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/constrain.rb CHANGED
@@ -3,8 +3,12 @@ require "constrain/version"
3
3
  module Constrain
4
4
  # Raised if types doesn't match a class expression
5
5
  class MatchError < StandardError
6
- def initialize(value, exprs, message: nil, unwind: 0)
7
- super message || "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}"
6
+ def initialize(value, exprs, message: nil, unwind: 0, not_argument: false, not_value: nil)
7
+ if not_argument
8
+ super message || "Expected #{value.inspect} to not equal #{not_value.inspect}"
9
+ else
10
+ super message || "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}"
11
+ end
8
12
  end
9
13
  end
10
14
 
@@ -12,17 +16,6 @@ module Constrain
12
16
  base.extend ClassMethods
13
17
  end
14
18
 
15
- # See Constrain.constrain
16
- def constrain(value, *exprs)
17
- Constrain.do_constrain(value, *exprs)
18
- end
19
-
20
- # Like #constrain but returns true/false to indicate the result instead of
21
- # raising an exception
22
- def constrain?(value, *exprs)
23
- Constrain.do_constrain?(value, *exprs)
24
- end
25
-
26
19
  # :call-seq:
27
20
  # constrain(value, *class-expressions, unwind: 0)
28
21
  # constrain(value, *values, unwind: 0)
@@ -31,39 +24,38 @@ module Constrain
31
24
  # ArgumentError if the expression is invalid and a Constrain::MatchError if
32
25
  # the value doesn't match. The exception's backtrace skips :unwind number of
33
26
  # entries
34
- def self.constrain(value, *exprs)
35
- do_constrain(value, *exprs)
27
+ def self.constrain(value, *exprs, **opts)
28
+ do_constrain(value, *exprs, **opts)
36
29
  end
37
30
 
38
- # Return true if the value matches the class expression. Raises a
39
- # ArgumentError if the expression is invalid
40
- def self.constrain?(value, *exprs)
41
- do_constrain?(value, *exprs)
31
+ # See Constrain.constrain
32
+ def constrain(...) = Constrain.do_constrain(...)
33
+
34
+ # Like #constrain but returns true/false to indicate the result instead of
35
+ # raising an exception
36
+ def self.constrain?(value, *exprs, **opts)
37
+ do_constrain?(value, *exprs, **opts)
42
38
  end
43
39
 
40
+ # See Constrain.constrain?
41
+ def constrain?(...) = Constrain.do_constrain?(...)
42
+
44
43
  module ClassMethods
45
44
  # See Constrain.constrain
46
- def constrain(*args)
47
-
48
- Constrain.do_constrain(*args) end
45
+ def constrain(...) Constrain.do_constrain(...) end
49
46
 
50
47
  # See Constrain.constrain?
51
- def constrain?(*args) Constrain.do_constrain?(*args) end
48
+ def constrain?(...) Constrain.do_constrain?(...) end
52
49
  end
53
50
 
51
+ # :call-seq:
52
+ # do_constrain(value, *exprs, unwind: 0, message: nil, not: nil)
53
+ #
54
54
  # unwind is automatically incremented by one because ::do_constrain is always
55
55
  # called from one of the other constrain methods
56
56
  #
57
- def self.do_constrain(value, *exprs)
58
- if exprs.last.is_a?(Hash)
59
- unwind = (exprs.last.delete(:unwind) || 0) + 1
60
- message = exprs.last.delete(:message)
61
- !exprs.last.empty? or exprs.pop # Remove option hash if empty
62
- else
63
- unwind = 1
64
- message = nil
65
- end
66
-
57
+ def self.do_constrain(value, *exprs, unwind: 0, message: nil)
58
+ unwind += 1
67
59
  begin
68
60
  if exprs.empty?
69
61
  value or raise MatchError.new(value, [], message: message, unwind: unwind)
@@ -78,11 +70,13 @@ module Constrain
78
70
  value
79
71
  end
80
72
 
81
- def self.do_constrain?(value, *exprs)
73
+ def self.do_constrain?(...)
82
74
  begin
83
- !exprs.empty? or raise ArgumentError, "Empty constraint"
84
- exprs.any? { |expr| Constrain.do_constrain_value?(value, expr) }
75
+ do_constrain(...)
76
+ rescue MatchError
77
+ return false
85
78
  end
79
+ true
86
80
  end
87
81
 
88
82
  def self.do_constrain_value?(value, expr)
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.8.0
4
+ version: 0.10.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: 2022-09-17 00:00:00.000000000 Z
11
+ date: 2023-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simplecov