# Source code for headerparser.parser

    from   warnings  import warn
    from   six       import itervalues, string_types
    from   .         import errors
    from   .normdict import NormalizedDict
    from   .scanner  import scan, scan_string, scan_stanzas, scan_stanzas_string, \
                            scan_next_stanza, scan_next_stanza_string
    from   .types    import lower, unfold

    class HeaderParser(object):
        """
        A parser for RFC 822-style header sections.  Define the fields the parser
        should recognize with the `add_field()` method, configure handling of
        unrecognized fields with `add_additional()`, and then parse input with
        `parse()` or another `!parse_*()` method.

        :param callable normalizer: By default, the parser will consider two field
            names to be equal iff their lowercased forms are equal.  This can be
            overridden by setting ``normalizer`` to a custom callable that takes a
            field name and returns a "normalized" name for use in equality testing.
            The normalizer will also be used when looking up keys in the
            `NormalizedDict` instances returned by the parser's `!parse_*()`
            methods.

        :param bool body: whether the parser should allow or forbid a body after
            the header section; `True` means a body is required, `False` means a
            body is prohibited, and `None` (the default) means a body is optional

        :param kwargs: :ref:`scanner options <scan_opts>`
        """

        def __init__(self, normalizer=None, body=None, **kwargs):
            #: The ``normalizer`` argument passed to the constructor, or `lower` if
            #: no normalizer was supplied
            self._normalizer = normalizer or lower
            #: The ``body`` argument passed to the constructor
            self._body = body
            #: Scanner options
            self._scan_opts = kwargs
            #: A mapping from normalized field names to `NamedField` instances
            self._fielddefs = dict()
            #: The set of all normalized ``dest`` values for all named fields
            #: defined so far
            self._dests = set()
            #: If additional fields are enabled, this is the `FieldDef` instance
            #: used to process them; otherwise, it is `None`.
            self._additional = None
            #: Whether any fields with custom ``dest`` values have been defined,
            #: thereby precluding `add_additional()`
            self._custom_dests = False

        def __eq__(self, other):
            if type(self) is type(other):
                return vars(self) == vars(other)
            else:
                return NotImplemented

        def __ne__(self, other):
            return not (self == other)

        def add_field(self, name, *altnames, **kwargs):
            """
            Define a header field for the parser to parse.  During parsing, if a
            field is encountered whose name (*modulo* normalization) equals either
            ``name`` or one of the ``altnames``, the field's value will be
            processed according to the options in ``**kwargs``.  (If no options are
            specified, the value will just be stored in the result dictionary.)

            .. versionadded:: 0.2.0
                ``action`` argument added

            :param string name: the primary name for the field, used in error
                messages and as the default value of ``dest``

            :param strings altnames: field name synonyms

            :param dest: The key in the result dictionary in which the field's
                value(s) will be stored; defaults to ``name``.  When additional
                headers are enabled (see `add_additional`), ``dest`` must equal
                (after normalization) one of the field's names.

            :param bool required: If `True` (default `False`), the ``parse_*``
                methods will raise a `~headerparser.errors.MissingFieldError` if
                the field is not present in the input

            :param default: The value to associate with the field if it is not
                present in the input.  If no default value is specified, the field
                will be omitted from the result dictionary if it is not present in
                the input.  ``default`` cannot be set when the field is required.
                ``type``, ``unfold``, and ``action`` will not be applied to the
                default value, and the default value need not belong to
                ``choices``.

            :param bool multiple: If `True`, the header field will be allowed to
                occur more than once in the input, and all of the field's values
                will be stored in a list.  If `False` (the default), a
                `~headerparser.errors.DuplicateFieldError` will be raised if the
                field occurs more than once in the input.

            :param bool unfold: If `True` (default `False`), the field value will
                be "unfolded" (i.e., line breaks will be removed and whitespace
                around line breaks will be converted to a single space) before
                applying ``type``

            :param callable type: a callable to apply to the field value before
                storing it in the result dictionary

            :param iterable choices: A sequence of values which the field is
                allowed to have.  If ``choices`` is defined, all occurrences of the
                field in the input must have one of the given values (after
                applying ``type``) or else an
                `~headerparser.errors.InvalidChoiceError` is raised.

            :param callable action: A callable to invoke whenever the field is
                encountered in the input.  The callable will be passed the current
                dictionary of header fields, the field's ``name``, and the field's
                value (after processing with ``type`` and ``unfold`` and checking
                against ``choices``).  The callable replaces the default behavior
                of storing the field's values in the result dictionary, and so the
                callable must explicitly store the values if desired.  When
                ``action`` is defined for a field, ``dest`` cannot be.

            :return: `None`
            :raises ValueError:
                - if another field with the same name or ``dest`` was already
                  defined
                - if ``dest`` is not one of the field's names and `add_additional`
                  is enabled
                - if ``default`` is defined and ``required`` is true
                - if ``choices`` is an empty sequence
                - if both ``dest`` and ``action`` are defined
            :raises TypeError: if ``name`` or one of the ``altnames`` is not a
                string
            """

            if 'action' in kwargs and 'dest' in kwargs:
                raise ValueError('`action` and `dest` are mutually exclusive')
            kwargs.setdefault('dest', name)
            hd = NamedField(name=name, **kwargs)
            normed = set(map(self._normalizer, (name,) + altnames))
            # Error before modifying anything:
            redefs = [n for n in self._fielddefs if n in normed]
            if redefs:
                raise ValueError('field defined more than once: ' + repr(redefs[0]))
            if self._normalizer(hd.dest) in self._dests:
                raise ValueError('destination defined more than once: '
                                 + repr(hd.dest))
            if self._normalizer(hd.dest) not in normed:
                if self._additional is not None:
                    raise ValueError('add_additional and `dest` are mutually exclusive')
                self._custom_dests = True
            for n in normed:
                self._fielddefs[n] = hd
            self._dests.add(self._normalizer(hd.dest))

        def add_additional(self, enable=True, **kwargs):
            """
            Specify how the parser should handle fields in the input that were not
            previously registered with `add_field`.  By default, unknown fields
            will cause the ``parse_*`` methods to raise an
            `~headerparser.errors.UnknownFieldError`, but calling this method with
            ``enable=True`` (the default) will change the parser's behavior so that
            all unregistered fields are processed according to the options in
            ``**kwargs``.  (If no options are specified, the additional values will
            just be stored in the result dictionary.)

            If this method is called more than once, only the settings from the
            last call will be used.

            Note that additional field values are always stored in the result
            dictionary using their field name as the key, and two fields are
            considered the same (for the purposes of ``multiple``) iff their names
            are the same after normalization.  Customization of the dictionary key
            and field name can only be done through `add_field`.

            .. versionadded:: 0.2.0
                ``action`` argument added

            :param bool enable: whether the parser should accept input fields that
                were not registered with `add_field`; setting this to `False`
                disables additional fields and restores the parser's default
                behavior

            :param bool multiple: If `True`, each additional header field will be
                allowed to occur more than once in the input, and each field's
                values will be stored in a list.  If `False` (the default), a
                `~headerparser.errors.DuplicateFieldError` will be raised if an
                additional field occurs more than once in the input.

            :param bool unfold: If `True` (default `False`), additional field
                values will be "unfolded" (i.e., line breaks will be removed and
                whitespace around line breaks will be converted to a single space)
                before applying ``type``

            :param callable type: a callable to apply to additional field values
                before storing them in the result dictionary

            :param iterable choices: A sequence of values which additional fields
                are allowed to have.  If ``choices`` is defined, all additional
                field values in the input must have one of the given values (after
                applying ``type``) or else an
                `~headerparser.errors.InvalidChoiceError` is raised.

            :param callable action: A callable to invoke whenever the field is
                encountered in the input.  The callable will be passed the current
                dictionary of header fields, the field's name, and the field's
                value (after processing with ``type`` and ``unfold`` and checking
                against ``choices``).  The callable replaces the default behavior
                of storing the field's values in the result dictionary, and so the
                callable must explicitly store the values if desired.

            :return: `None`
            :raises ValueError:
                - if ``enable`` is true and a previous call to `add_field` used a
                  custom ``dest``
                - if ``choices`` is an empty sequence
            """
            if enable:
                if self._custom_dests:
                    raise ValueError('add_additional and `dest` are mutually exclusive')
                self._additional = FieldDef(**kwargs)
            else:
                self._additional = None

        def parse_stream(self, fields):
            """
            Process a sequence of ``(name, value)`` pairs as returned by `scan()`
            or `scan_string()` and return a dictionary of header fields (possibly
            with body attached).  This is a low-level method that you will usually
            not need to call.

            :param fields: a sequence of ``(name, value)`` pairs representing the
                input fields
            :type fields: iterable of pairs of strings
            :rtype: NormalizedDict
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ValueError: if the input contains more than one body pair
            """
            data = NormalizedDict(normalizer=self._normalizer)
            fields_seen = set()
            body_seen = False
            for k,v in fields:
                if k is None:
                    if body_seen:
                        raise ValueError('Body appears twice in input')
                    if self._body is not None and not self._body:
                        raise errors.BodyNotAllowedError()
                    data.body = v
                    body_seen = True
                else:
                    try:
                        hd = self._fielddefs[self._normalizer(k)]
                    except KeyError:
                        if self._additional is not None:
                            hd = self._additional
                        else:
                            raise errors.UnknownFieldError(k)
                    else:
                        fields_seen.add(hd.name)
                    hd.process(data, k, v)
            for hd in itervalues(self._fielddefs):
                if hd.name not in fields_seen:
                    if hd.required:
                        raise errors.MissingFieldError(hd.name)
                    elif hasattr(hd, 'default'):
                        data[hd.dest] = hd.default
            if self._body and not body_seen:
                raise errors.MissingBodyError()
            return data

        def parse(self, iterable):
            """
            .. versionadded:: 0.4.0

            Parse an RFC 822-style header field section (possibly followed by a
            message body) from the contents of the given filehandle or sequence of
            lines and return a dictionary of the header fields (possibly with body
            attached).  If ``iterable`` is an iterable of `str`, newlines will be
            appended to lines in multiline header fields where not already present
            but will not be inserted where missing inside the body.

            :param iterable: a text-file-like object or iterable of lines to parse
            :rtype: NormalizedDict
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if the header section is malformed
            """
            return self.parse_stream(scan(iterable, **self._scan_opts))

        def parse_file(self, fp):
            """
            Parse an RFC 822-style header field section (possibly followed by a
            message body) from the contents of the given filehandle and return a
            dictionary of the header fields (possibly with body attached)

            .. deprecated:: 0.4.0
                Use `parse()` instead.

            :param fp: the file to parse
            :type fp: file-like object
            :rtype: NormalizedDict
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if the header section is malformed
            """
            warn(
                'HeaderParser.parse_file() is deprecated.'
                '  Use the parse() method instead.',
                DeprecationWarning,
            )
            return self.parse_stream(scan(fp, **self._scan_opts))

        def parse_lines(self, iterable):
            """
            Parse an RFC 822-style header field section (possibly followed by a
            message body) from the given sequence of lines and return a dictionary
            of the header fields (possibly with body attached).  Newlines will be
            inserted where not already present in multiline header fields but will
            not be inserted inside the body.

            .. deprecated:: 0.4.0
                Use `parse()` instead.

            :param iterable: a sequence of lines comprising the text to parse
            :type iterable: iterable of strings
            :rtype: NormalizedDict
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if the header section is malformed
            """
            warn(
                'HeaderParser.parse_lines() is deprecated.'
                '  Use the parse() method instead.',
                DeprecationWarning,
            )
            return self.parse_stream(scan(iterable, **self._scan_opts))

        def parse_string(self, s):
            """
            Parse an RFC 822-style header field section (possibly followed by a
            message body) from the given string and return a dictionary of the
            header fields (possibly with body attached)

            :param string s: the text to parse
            :rtype: NormalizedDict
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if the header section is malformed
            """
            return self.parse_stream(scan_string(s, **self._scan_opts))

        def parse_stanzas(self, iterable):
            """
            .. versionadded:: 0.4.0

            Parse zero or more stanzas of RFC 822-style header fields from the
            given filehandle or sequence of lines and return a generator of
            dictionaries of header fields.

            All of the input is treated as header sections, not message bodies; as
            a result, calling this method when ``body`` is true will produce a
            `MissingBodyError`.

            :param iterable: a text-file-like object or iterable of lines to parse
            :rtype: generator of `NormalizedDict`
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if a header section is malformed
            """
            return self.parse_stanzas_stream(
                scan_stanzas(iterable, **self._scan_opts)
            )

        def parse_stanzas_string(self, s):
            """
            .. versionadded:: 0.4.0

            Parse zero or more stanzas of RFC 822-style header fields from the
            given string and return a generator of dictionaries of header fields.

            All of the input is treated as header sections, not message bodies; as
            a result, calling this method when ``body`` is true will produce a
            `MissingBodyError`.

            :param string s: the text to parse
            :rtype: generator of `NormalizedDict`
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if a header section is malformed
            """
            return self.parse_stanzas_stream(
                scan_stanzas_string(s, **self._scan_opts)
            )

        def parse_stanzas_stream(self, fields):
            """
            .. versionadded:: 0.4.0

            Parse an iterable of iterables of ``(name, value)`` pairs as returned
            by `scan_stanzas()` or `scan_stanzas_string()` and return a generator
            of dictionaries of header fields.  This is a low-level method that you
            will usually not need to call.

            :param fields: an iterable of iterables of pairs of strings
            :rtype: generator of `NormalizedDict`
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if a header section is malformed
            """
            for stanza in fields:
                yield self.parse_stream(stanza)

        def parse_next_stanza(self, iterator):
            """
            .. versionadded:: 0.4.0

            Parse a RFC 822-style header field section from the contents of the
            given filehandle or iterator of lines and return a dictionary of the
            header fields.  Input processing stops at the end of the header
            section, leaving the rest of the iterator unconsumed.  As a message
            body is not consumed, calling this method when ``body`` is true will
            produce a `MissingBodyError`.

            :param iterator: a text-file-like object or iterator of lines to parse
            :rtype: NormalizedDict
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if a header section is malformed
            """
            return self.parse_stream(scan_next_stanza(iterator, **self._scan_opts))

        def parse_next_stanza_string(self, s):
            """
            .. versionadded:: 0.4.0

            Parse a RFC 822-style header field section from the given string and
            return a pair of a dictionary of the header fields and the rest of the
            string.  As a message body is not consumed, calling this method when
            ``body`` is true will produce a `MissingBodyError`.

            :param string s: the text to parse
            :rtype: pair of `NormalizedDict` and a string
            :raises ParserError: if the input fields do not conform to the field
                definitions declared with `add_field` and `add_additional`
            :raises ScannerError: if a header section is malformed
            """
            fields, extra = scan_next_stanza_string(s, **self._scan_opts)
            return (self.parse_stream(fields), extra)

    class FieldDef(object):
        def __init__(self, type=None, multiple=False, unfold=False, choices=None,
                           action=None):
            self.type = type
            self.multiple = bool(multiple)
            self.unfold = bool(unfold)
            if choices is not None:
                choices = list(choices)
                if not choices:
                    raise ValueError('empty list supplied for choices')
            self.choices = choices
            self.action = action

        def __eq__(self, other):
            if type(self) is type(other):
                return vars(self) == vars(other)
            else:
                return NotImplemented

        def __ne__(self, other):
            return not (self == other)

        def _process(self, data, name, dest, value):
            if self.unfold:
                value = unfold(value)
            if self.type is not None:
                try:
                    value = self.type(value)
                except errors.FieldTypeError:
                    raise
                except Exception as e:
                    raise errors.FieldTypeError(name, value, e)
            if self.choices is not None and value not in self.choices:
                raise errors.InvalidChoiceError(name, value)
            if self.action is not None:
                self.action(data, name, value)
            elif self.multiple:
                data.setdefault(dest, []).append(value)
            elif dest in data:
                raise errors.DuplicateFieldError(name)
            else:
                data[dest] = value

        def process(self, data, name, value):
            self._process(data, name, name, value)

    class NamedField(FieldDef):
        def __init__(self, name, dest, required=False, **kwargs):
            if not isinstance(name, string_types):
                raise TypeError('field names must be strings')
            self.name = name
            self.dest = dest
            self.required = bool(required)
            if 'default' in kwargs:
                if self.required:
                    raise ValueError('required and default are mutually exclusive')
                self.default = kwargs.pop('default')
            super(NamedField, self).__init__(**kwargs)

        def process(self, data, _, value):
            self._process(data, self.name, self.dest, value)