constrain 0.8.0 → 0.10.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: 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