Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

file 93 lines (79 sloc) 3.004 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
module RSpec
  module Matchers
    class OperatorMatcher
      class << self
        def registry
          @registry ||= {}
        end

        def register(klass, operator, matcher)
          registry[klass] ||= {}
          registry[klass][operator] = matcher
        end

        def unregister(klass, operator)
          registry[klass] && registry[klass].delete(operator)
        end

        def get(klass, operator)
          klass.ancestors.each { |ancestor|
            matcher = registry[ancestor] && registry[ancestor][operator]
            return matcher if matcher
          }

          nil
        end
      end

      def initialize(actual)
        @actual = actual
      end

      def self.use_custom_matcher_or_delegate(operator)
        define_method(operator) do |expected|
          if matcher = OperatorMatcher.get(@actual.class, operator)
            @actual.send(::RSpec::Matchers.last_should, matcher.new(expected))
          else
            eval_match(@actual, operator, expected)
          end
        end

        negative_operator = operator.sub(/^=/, '!')
        if negative_operator != operator && respond_to?(negative_operator)
          define_method(negative_operator) do |expected|
            opposite_should = ::RSpec::Matchers.last_should == :should ? :should_not : :should
            raise "RSpec does not support `#{::RSpec::Matchers.last_should} #{negative_operator} expected`. " +
              "Use `#{opposite_should} #{operator} expected` instead."
          end
        end
      end

      ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
        use_custom_matcher_or_delegate operator
      end

      def fail_with_message(message)
        RSpec::Expectations.fail_with(message, @expected, @actual)
      end

      def description
        "#{@operator} #{@expected.inspect}"
      end

      private

      def eval_match(actual, operator, expected)
        ::RSpec::Matchers.last_matcher = self
        @operator, @expected = operator, expected
        __delegate_operator(actual, operator, expected)
      end
    end

    module BuiltIn
      class PositiveOperatorMatcher < OperatorMatcher
        def __delegate_operator(actual, operator, expected)
          if actual.__send__(operator, expected)
            true
          elsif ['==','===', '=~'].include?(operator)
            fail_with_message("expected: #{expected.inspect}\n got: #{actual.inspect} (using #{operator})")
          else
            fail_with_message("expected: #{operator} #{expected.inspect}\n got: #{operator.gsub(/./, ' ')} #{actual.inspect}")
          end
        end
      end

      class NegativeOperatorMatcher < OperatorMatcher
        def __delegate_operator(actual, operator, expected)
          return false unless actual.__send__(operator, expected)
          return fail_with_message("expected not: #{operator} #{expected.inspect}\n got: #{operator.gsub(/./, ' ')} #{actual.inspect}")
        end
      end
    end
  end
end
Something went wrong with that request. Please try again.