<gh_stars>1-10
# -*- coding: utf-8 -*

"""
Introduction
------------

*Expects* can be `extended` by defining `new matchers`.
The :mod:`matchers` module contains the bases for building
custom matchers.

Tutorial
--------

The easiest way to define a new matcher is to extend the
:class:`Matcher` class and override the :func:`Matcher._match`
method.

For example, to define a matcher to check if a `request` object contains
a given header takes <10 lines of code::

    from expects.matchers import Matcher

    class have_header(Matcher):
        def __init__(self, expected):
            self._expected = expected

        def _match(self, request):
            if self._expected in request.headers:
                return True, ['header found']
            return True, ['header not found']

An then you only need to import the new defined matcher and write
your expectation::

    from expects import expect
    from my_custom_matchers import have_header

    expect(my_request).to(have_header('Content-Type'))

Advanced
--------

For more complex matchers you can override the :class:`Matcher`
methods in order to achieve the needed behavior.

"""


class Matcher(object):
    """The :class:`Matcher` class is the base class for all `Expects`
    matchers.

    It defines a set of methods to ease writting new matchers.

    """

    def _match(self, subject):
        """This method will be called when the matcher is used in an
        expectation. It should be overwritten to implement the matcher
        logic. If not raises :class:`NotImplementedError`.

        Receives the expectation `subject` as the unique positional
        argument and should return a :keyword:`tuple` with the
        :keyword:`bool` result of the matcher and a :keyword:`list` of
        reasons for this result.

        If the matcher matches the subject then the boolean result
        should be :keyword:`True`. The reasons should be a list with
        0 or more strings.

        :param subject: The target value of the expectation.
        :rtype: tuple (bool, [str])

        """

        raise NotImplementedError()

    def _match_negated(self, subject):
        """Like :func:`_match` but will be called when used in a
        negated expectation. It can be used to implement a custom
        logic for negated expectations.

        By default returns the result of negating ``self._match(subject)``.

        :param subject: The target value of the expectation.
        :rtype: tuple (bool, [str])

        """

        result, reason = self._match(subject)
        return not result, reason

    def _failure_message(self, subject, reasons):
        """This method will be called from an expectation `only` when
        the expectation is going to fail. It should return a string
        with the failure message.

        By default returns a failure message with the following format::

            expected: {subject} to {description}
                 but: {reasons}

        With the passed `subject` repr, this matcher repr as `description`
        and the passed `reasons` from the matcher result.

        :param subject: The target value of the expectation.
        :param reasons: A list of reasons that caused this matcher to fail.
        :type subject: a string
        :type reasons: list of strings
        :rtype: a string

        """

        message = '\nexpected: {subject!r} to {matcher!r}'.format(
            subject=subject, matcher=self)

        if reasons:
            message += '\n     but: {0}'.format('\n          '.join(reasons))

        return message

    def _failure_message_negated(self, subject, reasons):
        """Like the :func:`_failure_message` method but will be called
        when a negated expectation is going to fail. It should return a
        string with the failure message for the negated expectation.

        By default returns a failure message with the following format::

            expected: {subject} to {description}
                 but: {reasons}

        :param subject: The target value of the expectation.
        :param reasons: A list of reasons that caused this matcher to fail.
        :type subject: a string
        :type reasons: list of strings
        :rtype: a string

        """

        message = '\nexpected: {subject!r} not to {matcher!r}'.format(
            subject=subject, matcher=self)

        if reasons:
            message += '\n     but: {0}'.format('\n          '.join(reasons))

        return message

    def __repr__(self):
        """Returns a string with the description of the matcher.

        By default returns a string with the following format::

            '{name} {expected}'

        Where `name` is based on the matcher class name and `expected`
        is the value passed to the constructor.

        :rtype: a string

        """

        if hasattr(self, '_expected'):
            return '{name} {expected!r}'.format(name=self._name,
                                                expected=self._expected)
        return self._name

    @property
    def _name(self):
        return type(self).__name__.replace('_', ' ').strip()

    def __and__(self, other):
        return _And(self, other)

    def __or__(self, other):
        return _Or(self, other)


def default_matcher(value):
    if not isinstance(value, Matcher):
        return equal_matcher(value)
    return value


class _And(Matcher):
    def __init__(self, op1, op2):
        self.op1 = op1
        self.op2 = op2

    def _match(self, subject):
        result1, _ = self.op1._match(subject)
        result2, _ = self.op2._match(subject)

        return result1 and result2, []

    def __repr__(self):
        return '{0} and {1}'.format(repr(self.op1).replace(' and ', ', '),
                                    repr(self.op2))


class _Or(Matcher):
    def __init__(self, op1, op2):
        self.op1 = op1
        self.op2 = op2

    def _match(self, subject):
        result1, _ = self.op1._match(subject)
        result2, _ = self.op2._match(subject)

        return result1 or result2, []

    def __repr__(self):
        return '{0} or {1}'.format(repr(self.op1).replace(' or ', ', '),
                                   repr(self.op2))

from .built_in import equal as equal_matcher

# -*- coding: utf-8 -*

"""
Introduction
------------

*Expects* can be `extended` by defining `new matchers`.
The :mod:`matchers` module contains the bases for building
custom matchers.

Tutorial
--------

The easiest way to define a new matcher is to extend the
:class:`Matcher` class and override the :func:`Matcher._match`
method.

For example, to define a matcher to check if a `request` object contains
a given header takes <10 lines of code::

    from expects.matchers import Matcher

    class have_header(Matcher):
        def __init__(self, expected):
            self._expected = expected

        def _match(self, request):
            if self._expected in request.headers:
                return True, ['header found']
            return True, ['header not found']

An then you only need to import the new defined matcher and write
your expectation::

    from expects import expect
    from my_custom_matchers import have_header

    expect(my_request).to(have_header('Content-Type'))

Advanced
--------

For more complex matchers you can override the :class:`Matcher`
methods in order to achieve the needed behavior.

"""


class Matcher(object):
    """The :class:`Matcher` class is the base class for all `Expects`
    matchers.

    It defines a set of methods to ease writting new matchers.

    """

    def _match(self, subject):
        """This method will be called when the matcher is used in an
        expectation. It should be overwritten to implement the matcher
        logic. If not raises :class:`NotImplementedError`.

        Receives the expectation `subject` as the unique positional
        argument and should return a :keyword:`tuple` with the
        :keyword:`bool` result of the matcher and a :keyword:`list` of
        reasons for this result.

        If the matcher matches the subject then the boolean result
        should be :keyword:`True`. The reasons should be a list with
        0 or more strings.

        :param subject: The target value of the expectation.
        :rtype: tuple (bool, [str])

        """

        raise NotImplementedError()

    def _match_negated(self, subject):
        """Like :func:`_match` but will be called when used in a
        negated expectation. It can be used to implement a custom
        logic for negated expectations.

        By default returns the result of negating ``self._match(subject)``.

        :param subject: The target value of the expectation.
        :rtype: tuple (bool, [str])

        """

        result, reason = self._match(subject)
        return not result, reason

    def _failure_message(self, subject, reasons):
        """This method will be called from an expectation `only` when
        the expectation is going to fail. It should return a string
        with the failure message.

        By default returns a failure message with the following format::

            expected: {subject} to {description}
                 but: {reasons}

        With the passed `subject` repr, this matcher repr as `description`
        and the passed `reasons` from the matcher result.

        :param subject: The target value of the expectation.
        :param reasons: A list of reasons that caused this matcher to fail.
        :type subject: a string
        :type reasons: list of strings
        :rtype: a string

        """

        message = '\nexpected: {subject!r} to {matcher!r}'.format(
            subject=subject, matcher=self)

        if reasons:
            message += '\n     but: {0}'.format('\n          '.join(reasons))

        return message

    def _failure_message_negated(self, subject, reasons):
        """Like the :func:`_failure_message` method but will be called
        when a negated expectation is going to fail. It should return a
        string with the failure message for the negated expectation.

        By default returns a failure message with the following format::

            expected: {subject} to {description}
                 but: {reasons}

        :param subject: The target value of the expectation.
        :param reasons: A list of reasons that caused this matcher to fail.
        :type subject: a string
        :type reasons: list of strings
        :rtype: a string

        """

        message = '\nexpected: {subject!r} not to {matcher!r}'.format(
            subject=subject, matcher=self)

        if reasons:
            message += '\n     but: {0}'.format('\n          '.join(reasons))

        return message

    def __repr__(self):
        """Returns a string with the description of the matcher.

        By default returns a string with the following format::

            '{name} {expected}'

        Where `name` is based on the matcher class name and `expected`
        is the value passed to the constructor.

        :rtype: a string

        """

        if hasattr(self, '_expected'):
            return '{name} {expected!r}'.format(name=self._name,
                                                expected=self._expected)
        return self._name

    @property
    def _name(self):
        return type(self).__name__.replace('_', ' ').strip()

    def __and__(self, other):
        return _And(self, other)

    def __or__(self, other):
        return _Or(self, other)


def default_matcher(value):
    if not isinstance(value, Matcher):
        return equal_matcher(value)
    return value


class _And(Matcher):
    def __init__(self, op1, op2):
        self.op1 = op1
        self.op2 = op2

    def _match(self, subject):
        result1, _ = self.op1._match(subject)
        result2, _ = self.op2._match(subject)

        return result1 and result2, []

    def __repr__(self):
        return '{0} and {1}'.format(repr(self.op1).replace(' and ', ', '),
                                    repr(self.op2))


class _Or(Matcher):
    def __init__(self, op1, op2):
        self.op1 = op1
        self.op2 = op2

    def _match(self, subject):
        result1, _ = self.op1._match(subject)
        result2, _ = self.op2._match(subject)

        return result1 or result2, []

    def __repr__(self):
        return '{0} or {1}'.format(repr(self.op1).replace(' or ', ', '),
                                   repr(self.op2))

from .built_in import equal as equal_matcher
# -*- coding: utf-8 -* Introduction ------------  *Expects* can be `extended` by defining `new matchers`. The :mod:`matchers` module contains the bases for building custom matchers.  Tutorial --------  The easiest way to define a new matcher is to extend the :class:`Matcher` class and override the :func:`Matcher._match` method.  For example, to define a matcher to check if a `request` object contains a given header takes <10 lines of code::      from expects.matchers import Matcher      class have_header(Matcher):         def __init__(self, expected):             self._expected = expected          def _match(self, request):             if self._expected in request.headers:                 return True, ['header found']             return True, ['header not found']  An then you only need to import the new defined matcher and write your expectation::      from expects import expect     from my_custom_matchers import have_header      expect(my_request).to(have_header('Content-Type'))  Advanced --------  For more complex matchers you can override the :class:`Matcher` methods in order to achieve the needed behavior. The :class:`Matcher` class is the base class for all `Expects`     matchers.      It defines a set of methods to ease writting new matchers. This method will be called when the matcher is used in an         expectation. It should be overwritten to implement the matcher         logic. If not raises :class:`NotImplementedError`.          Receives the expectation `subject` as the unique positional         argument and should return a :keyword:`tuple` with the         :keyword:`bool` result of the matcher and a :keyword:`list` of         reasons for this result.          If the matcher matches the subject then the boolean result         should be :keyword:`True`. The reasons should be a list with         0 or more strings.          :param subject: The target value of the expectation.         :rtype: tuple (bool, [str]) Like :func:`_match` but will be called when used in a         negated expectation. It can be used to implement a custom         logic for negated expectations.          By default returns the result of negating ``self._match(subject)``.          :param subject: The target value of the expectation.         :rtype: tuple (bool, [str]) This method will be called from an expectation `only` when         the expectation is going to fail. It should return a string         with the failure message.          By default returns a failure message with the following format::              expected: {subject} to {description}                  but: {reasons}          With the passed `subject` repr, this matcher repr as `description`         and the passed `reasons` from the matcher result.          :param subject: The target value of the expectation.         :param reasons: A list of reasons that caused this matcher to fail.         :type subject: a string         :type reasons: list of strings         :rtype: a string Like the :func:`_failure_message` method but will be called         when a negated expectation is going to fail. It should return a         string with the failure message for the negated expectation.          By default returns a failure message with the following format::              expected: {subject} to {description}                  but: {reasons}          :param subject: The target value of the expectation.         :param reasons: A list of reasons that caused this matcher to fail.         :type subject: a string         :type reasons: list of strings         :rtype: a string Returns a string with the description of the matcher.          By default returns a string with the following format::              '{name} {expected}'          Where `name` is based on the matcher class name and `expected`         is the value passed to the constructor.          :rtype: a string