constrain 0.1.1 → 0.2.2

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: 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: