lib/constrain.rb in constrain-0.8.0 vs lib/constrain.rb in constrain-0.9.0

- old
+ new

@@ -1,89 +1,91 @@ require "constrain/version" module Constrain # Raised if types doesn't match a class expression class MatchError < StandardError - def initialize(value, exprs, message: nil, unwind: 0) - super message || "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}" + def initialize(value, exprs, message: nil, unwind: 0, not_argument: false, not_value: nil) + if not_argument + super message || "Expected #{value.inspect} to not equal #{not_value.inspect}" + else + super message || "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}" + end end end def self.included base base.extend ClassMethods end - # See Constrain.constrain - def constrain(value, *exprs) - Constrain.do_constrain(value, *exprs) - end - - # Like #constrain but returns true/false to indicate the result instead of - # raising an exception - def constrain?(value, *exprs) - Constrain.do_constrain?(value, *exprs) - end - # :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 - def self.constrain(value, *exprs) - do_constrain(value, *exprs) + def self.constrain(value, *exprs, **opts) + do_constrain(value, *exprs, **opts) end - # Return true if the value matches the class expression. Raises a - # ArgumentError if the expression is invalid - def self.constrain?(value, *exprs) - do_constrain?(value, *exprs) + # See Constrain.constrain + def constrain(...) = Constrain.do_constrain(...) + + # Like #constrain but returns true/false to indicate the result instead of + # raising an exception + def self.constrain?(value, *exprs, **opts) + do_constrain?(value, *exprs, **opts) end + # See Constrain.constrain? + def constrain?(...) = Constrain.do_constrain?(...) + module ClassMethods # See Constrain.constrain - def constrain(*args) + def constrain(...) Constrain.do_constrain(...) end - Constrain.do_constrain(*args) end - # See Constrain.constrain? - def constrain?(*args) Constrain.do_constrain?(*args) end + def constrain?(...) Constrain.do_constrain?(...) end end + # :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 # - def self.do_constrain(value, *exprs) - if exprs.last.is_a?(Hash) - unwind = (exprs.last.delete(:unwind) || 0) + 1 - message = exprs.last.delete(:message) - !exprs.last.empty? or exprs.pop # Remove option hash if empty - else - unwind = 1 - message = nil - end + def self.do_constrain(value, *exprs, unwind: 0, message: nil, **opts) + opts.keys.empty? || opts.keys == [:not] or raise ArgumentError + unwind += 1 + not_argument = opts.key?(:not) + not_value = opts[:not] 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 + !not_argument || value != not_value or + raise MatchError.new( + value, exprs, message: message, unwind: unwind, not_argument: true, not_value: not_value) + rescue ArgumentError, Constrain::MatchError => ex ex.set_backtrace(caller[1 + unwind..-1]) raise end value end - def self.do_constrain?(value, *exprs) + def self.do_constrain?(...) begin - !exprs.empty? or raise ArgumentError, "Empty constraint" - exprs.any? { |expr| Constrain.do_constrain_value?(value, expr) } + do_constrain(...) + rescue MatchError + return false end + true end def self.do_constrain_value?(value, expr) case expr when Class, Module