constrain 0.1.1 → 0.2.2

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: ef3357b49faba9f9d3bfe5dbfb3d27b70d1493456b4dea29afb96c811b4eff8b
4
- data.tar.gz: 590bb604f5566f76d4473f1f9191ef2ca21bfdcf9a904b92a0b6fa0388263c12
3
+ metadata.gz: 21ca1b88c0ffb91b40a2a2335ead91e2570108f8b67eaa9ea7fa3ebe00ada4c4
4
+ data.tar.gz: '083da1149466231ca8de0dc216cb00c46c540ebc1b7558a0d26422c33b5af9c4'
5
5
  SHA512:
6
- metadata.gz: ec0d44e4cb0a7b4c3c8291bc10d821000769d36fe30c44aed85c30b3993076796169f7efa394cd47b0694744b47e6354e7413fb1c8b3a814d3a42d91cdeff04d
7
- data.tar.gz: 9b60ecec9cb4edd2dc507b10835df150094b9fc3a939735be6935a93df4d5f049bbf4fe963eebb7ebf07a4faf21fa13cf3908a4ea44ac93178f97ef9b29d2612
6
+ metadata.gz: 7b40d638495bf36bd661fd1f8ab7845ec177a3f5cb2f73ddfd400976a9b02f9f8b2f16b2b0301b287cd419c43a2e961e895f96b6e9fd7a46545c96c71a954a28
7
+ data.tar.gz: 7be37bc44024db00f04d1355cc4bf340ca8728b2ff3014e27bda54f551bab85843f383f61890a8eaf63c65a14907061b3ce49f676732e1b0ad774b0e0e727541
data/README.md CHANGED
@@ -58,38 +58,49 @@ end
58
58
  ```
59
59
 
60
60
  The alternative is to include the constrain Module in a common root class to
61
- have it available in all child class
61
+ have it available in all child classes
62
62
 
63
- The #constrain method has the following signature
63
+ ## Methods
64
64
 
65
- ```ruby
66
- constrain(value, *class-expressions, message = nil)
67
- ```
65
+ #### constrain(value, \*class-expressions, message = nil, unwind: 0)
68
66
 
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
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
74
73
 
75
- Constrain also defines a #check class method with the signature
74
+ \#constrain is typically used to type-check parameters in methods where you
75
+ want an exception if the parameters doesn't match the expected, but because it
76
+ returns the value if successful it can be used to check the validity of
77
+ variables in expressions too, eg. `return constrain(result_of_complex_computation, Integer)`
78
+ to check the return value of a method
76
79
 
77
- ```ruby
78
- Constrain.check(value, *class-expression) -> true or false
79
- ```
80
+ #### Constrain.constrain(value, \*class-expressions, message = nil, unwind: 0)
81
+
82
+ Class method version of #constrain. It is automatically added to classes that
83
+ include Constrain
84
+
85
+
86
+ #### Constrain.constrain?(value, \*class-expression) -> true or false
80
87
 
81
88
  It matches value against the class expressions like #constrain but returns true
82
- or false as result
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
83
93
 
84
94
  ## Class Expressions
85
95
 
86
- Constrain#constrain and Constrain::check use class expressions composed of
87
- Class objects, Proc objects, or arrays and hashes of class expressions. Class
88
- objects match if the value is an instance of the class:
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:
89
99
 
90
100
  ```ruby
91
- constrain 42, Integer # Success
92
- constrain 42, String # Failure
101
+ constrain 42, Integer # Success
102
+ constrain 42, Comparable # Success
103
+ constrain nil, Comparable # Failure
93
104
  ```
94
105
 
95
106
  More than one class expression is allowed. It matches if at least one of the expressions match:
@@ -134,10 +145,15 @@ constrain 0, Integer # Success
134
145
  constrain 0, lambda { |value| value > 1 } # Failure
135
146
  ```
136
147
 
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
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
141
157
 
142
158
  #### Arrays
143
159
 
data/constrain.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  Constrain works with ruby-2 (and maybe ruby-3)
28
28
  }
29
- spec.homepage = "http://www.nowhere.com/"
29
+ spec.homepage = "https://github.com/clrgit/constrain/"
30
30
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
31
31
 
32
32
  spec.metadata["homepage_uri"] = spec.homepage
data/lib/constrain.rb CHANGED
@@ -1,4 +1,18 @@
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
2
16
 
3
17
  module Constrain
4
18
  # Raised on any error
@@ -6,42 +20,88 @@ module Constrain
6
20
 
7
21
  # Raised if types doesn't match a class expression
8
22
  class TypeError < Error
9
- def initialize(value, exprs, msg = nil)
23
+ def initialize(value, exprs, msg = nil, unwind: 0)
10
24
  super msg || "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}"
11
25
  end
12
26
  end
13
27
 
28
+ def self.included base
29
+ base.extend ClassMethods
30
+ end
31
+
32
+ # See Constrain.constrain
33
+ def constrain(value, *exprs)
34
+ Constrain.do_constrain(value, *exprs)
35
+ end
36
+
37
+ def constrain?(value, expr)
38
+ Constrain.do_constrain?(value, expr)
39
+ end
40
+
41
+ # :call-seq:
42
+ # constrain(value, *class-expressions, unwind: 0)
43
+ #
14
44
  # Check that value matches one of the class expressions. Raises a
15
45
  # Constrain::Error if the expression is invalid and a Constrain::TypeError if
16
- # the value doesn't match
17
- def constrain(value, *exprs)
46
+ # the value doesn't match. The exception's backtrace skips :unwind number of
47
+ # entries
48
+ def self.constrain(value, *exprs)
49
+ do_constrain(value, *exprs)
50
+ end
51
+
52
+ # Return true if the value matches the class expression. Raises a
53
+ # Constrain::Error if the expression is invalid
54
+ #
55
+ # TODO: Allow *exprs
56
+ def self.constrain?(value, expr)
57
+ do_constrain?(value, expr)
58
+ end
59
+
60
+ module ClassMethods
61
+ # See Constrain.constrain
62
+ def constrain(*args) Constrain.do_constrain(*args) end
63
+
64
+ # See Constrain.constrain?
65
+ def constrain?(*args) Constrain.do_constrain?(*args) end
66
+ end
67
+
68
+ # unwind is automatically incremented by one because ::do_constrain is always
69
+ # called from one of the other constrain methods
70
+ #
71
+ def self.do_constrain(value, *exprs)
72
+ if exprs.last.is_a?(Hash)
73
+ unwind = (exprs.last.delete(:unwind) || 0) + 1
74
+ !exprs.last.empty? or exprs.pop
75
+ else
76
+ unwind = 1
77
+ end
18
78
  msg = exprs.pop if exprs.last.is_a?(String)
19
79
  begin
20
80
  !exprs.empty? or raise Error, "Empty class expression"
21
- exprs.any? { |expr| Constrain.check(value, expr) } or raise TypeError.new(value, exprs, msg)
81
+ exprs.any? { |expr| Constrain.do_constrain?(value, expr) } or
82
+ raise TypeError.new(value, exprs, msg, unwind: unwind)
22
83
  rescue Error => ex
23
- ex.set_backtrace(caller[1..-1])
84
+ ex.set_backtrace(caller[1 + unwind..-1])
24
85
  raise
25
86
  end
87
+ value
26
88
  end
27
89
 
28
- # Return true if the value matches the class expression. Raises a
29
- # Constrain::Error if the expression is invalid
30
- def self.check(value, expr)
90
+ def self.do_constrain?(value, expr)
31
91
  case expr
32
- when Class
92
+ when Class, Module
33
93
  value.is_a?(expr)
34
94
  when Array
35
95
  !expr.empty? or raise Error, "Empty array"
36
- value.is_a?(Array) && value.all? { |elem| expr.any? { |e| check(elem, e) } }
96
+ value.is_a?(Array) && value.all? { |elem| expr.any? { |e| Constrain.constrain?(elem, e) } }
37
97
  when Hash
38
98
  value.is_a?(Hash) && value.all? { |key, value|
39
99
  expr.any? { |key_expr, value_expr|
40
100
  [[key, key_expr], [value, value_expr]].all? { |value, expr|
41
101
  if expr.is_a?(Array) && (expr.size > 1 || expr.first.is_a?(Array))
42
- expr.any? { |e| check(value, e) }
102
+ expr.any? { |e| Constrain.do_constrain?(value, e) }
43
103
  else
44
- check(value, expr)
104
+ Constrain.constrain?(value, expr)
45
105
  end
46
106
  }
47
107
  }
@@ -65,7 +125,7 @@ module Constrain
65
125
  #
66
126
  def self.fmt_expr(expr)
67
127
  case expr
68
- when Class; expr.to_s
128
+ when Class, Module; expr.to_s
69
129
  when Array; "[" + expr.map { |expr| fmt_expr(expr) }.join(", ") + "]"
70
130
  when Hash; "{" + expr.map { |k,v| "#{fmt_expr(k)} => #{fmt_expr(v)}" }.join(", ") + "}"
71
131
  when Proc; "Proc@#{expr.source_location.first}:#{expr.source_location.last}"
@@ -1,3 +1,3 @@
1
1
  module Constrain
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.2"
3
3
  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.1.1
4
+ version: 0.2.2
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-16 00:00:00.000000000 Z
11
+ date: 2021-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simplecov
@@ -52,10 +52,10 @@ files:
52
52
  - constrain.gemspec
53
53
  - lib/constrain.rb
54
54
  - lib/constrain/version.rb
55
- homepage: http://www.nowhere.com/
55
+ homepage: https://github.com/clrgit/constrain/
56
56
  licenses: []
57
57
  metadata:
58
- homepage_uri: http://www.nowhere.com/
58
+ homepage_uri: https://github.com/clrgit/constrain/
59
59
  post_install_message:
60
60
  rdoc_options: []
61
61
  require_paths: