Module: Constrain

Defined in:
lib/constrain.rb,
lib/constrain/version.rb

Defined Under Namespace

Modules: ClassMethods Classes: MatchError

Constant Summary collapse

VERSION =
"0.10.0"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.constrain(value, *exprs, **opts) ⇒ Object

:call-seq:

constrain(value, *class-expressions, unwind: 0)
constrain(value, *values, unwind: 0)

Check that value matches one of the class expressions. Raises a ArgumentError if the expression is invalid and a Constrain::MatchError if the value doesn’t match. The exception’s backtrace skips :unwind number of entries



27
28
29
# File 'lib/constrain.rb', line 27

def self.constrain(value, *exprs, **opts)
  do_constrain(value, *exprs, **opts)
end

.constrain?(value, *exprs, **opts) ⇒ Boolean

Like #constrain but returns true/false to indicate the result instead of raising an exception

Returns:

  • (Boolean)


36
37
38
# File 'lib/constrain.rb', line 36

def self.constrain?(value, *exprs, **opts)
  do_constrain?(value, *exprs, **opts)
end

.do_constrain(value, *exprs, unwind: 0, message: nil) ⇒ Object

:call-seq:

do_constrain(value, *exprs, unwind: 0, message: nil, not: nil)

unwind is automatically incremented by one because ::do_constrain is always called from one of the other constrain methods



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/constrain.rb', line 57

def self.do_constrain(value, *exprs, unwind: 0, message: nil)
  unwind += 1
  begin
    if exprs.empty?
      value or raise MatchError.new(value, [], message: message, unwind: unwind)
    else
      exprs.any? { |expr| Constrain.do_constrain_value?(value, expr) } or 
          raise MatchError.new(value, exprs, message: message, unwind: unwind)
    end
  rescue ArgumentError, Constrain::MatchError => ex
    ex.set_backtrace(caller[1 + unwind..-1])
    raise
  end
  value
end

.do_constrain?Boolean

Returns:

  • (Boolean)


73
74
75
76
77
78
79
80
# File 'lib/constrain.rb', line 73

def self.do_constrain?(...)
  begin
    do_constrain(...)
  rescue MatchError
    return false
  end
  true
end

.do_constrain_value?(value, expr) ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/constrain.rb', line 82

def self.do_constrain_value?(value, expr)
  case expr
    when Class, Module
      expr === value
    when Array
      !expr.empty? or raise ArgumentError, "Empty array in constraint"
      value.is_a?(Array) && value.all? { |elem| expr.any? { |e| Constrain.constrain?(elem, e) } }
    when Hash
      value.is_a?(Hash) && value.all? { |key, value|
        expr.any? { |key_expr, value_expr|
          [[key, key_expr], [value, value_expr]].all? { |value, expr|
            if expr.is_a?(Array) && (expr.size > 1 || expr.first.is_a?(Array))
              expr.any? { |e| Constrain.do_constrain?(value, e) }
            else
              Constrain.constrain?(value, expr)
            end
          }
        }
      }
    when Proc
      expr.call(value)
  else
    expr === value
  end
end

.fmt_expr(expr) ⇒ Object

Render a class expression as a String. Same as expr.inspect except that Proc objects are rendered as “Proc@<sourcefile>>:<linenumber>”



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/constrain.rb', line 117

def self.fmt_expr(expr)
  case expr
    when Class, Module; expr.to_s
    when Regexp; expr.to_s
    when Array; "[" + expr.map { |expr| fmt_expr(expr) }.join(", ") + "]"
    when Hash; "{" + expr.map { |k,v| "#{fmt_expr(k)} => #{fmt_expr(v)}" }.join(", ") + "}"
    when Proc; "Proc@#{expr.source_location.first}:#{expr.source_location.last}"
  else
    expr.inspect
  end
end

.fmt_exprs(exprs) ⇒ Object

Render a class expression as a String. Same as exprs.map(&:inspect).join(", ") except that Proc objects are rendered as “Proc@<sourcefile>:<linenumber>”



111
112
113
# File 'lib/constrain.rb', line 111

def self.fmt_exprs(exprs)
  exprs.map { |expr| fmt_expr(expr) }.join(", ")
end

.included(base) ⇒ Object



15
16
17
# File 'lib/constrain.rb', line 15

def self.included base
  base.extend ClassMethods
end

Instance Method Details

#constrainObject

See Constrain.constrain



32
# File 'lib/constrain.rb', line 32

def constrain(...) = Constrain.do_constrain(...)

#constrain?Boolean

See Constrain.constrain?

Returns:

  • (Boolean)


41
# File 'lib/constrain.rb', line 41

def constrain?(...) = Constrain.do_constrain?(...)