Source code for pymap.parsing.specials.options


from __future__ import annotations

import re
from collections.abc import Iterable, Mapping

from . import AString, SequenceSet
from .. import Params, Parseable
from ..exceptions import NotParseable
from ..primitives import Number, List
from ...bytes import BytesFormat

__all__ = ['ExtensionOption', 'ExtensionOptions']


[docs] class ExtensionOption(Parseable[bytes]): """Represents a single command option, which may or may not have an associated value. See Also: `RFC 4466 2.1. <https://tools.ietf.org/html/rfc4466#section-2.1>`_ Args: option: The name of the option. arg: The option argument, if any. """ _opt_pattern = re.compile(br'[a-zA-Z_.-][a-zA-Z0-9_.:-]*') def __init__(self, option: bytes, arg: List) -> None: super().__init__() self.option = option self.arg = arg self._raw_arg: bytes | None = None @property def value(self) -> bytes: return self.option def __bytes__(self) -> bytes: if self.arg.value: return BytesFormat(b'%b %b') % (self.option, self.raw_arg) else: return self.option @property def raw_arg(self) -> bytes: if self._raw_arg is None: if not self.arg: self._raw_arg = b'' elif len(self.arg) == 1: arg_0 = self.arg.value[0] if isinstance(arg_0, (Number, SequenceSet)): self._raw_arg = bytes(arg_0) else: self._raw_arg = bytes(self.arg) else: self._raw_arg = bytes(self.arg) return self._raw_arg @classmethod def _parse_arg(cls, buf: memoryview, params: Params) \ -> tuple[List, memoryview]: try: num, buf = Number.parse(buf, params) except NotParseable: pass else: arg = List([num]) return arg, buf try: seq_set, buf = SequenceSet.parse(buf, params) except NotParseable: pass else: arg = List([seq_set]) return arg, buf try: params_copy = params.copy(expected=[AString, List]) return List.parse(buf, params_copy) except NotParseable: pass return List([]), buf
[docs] @classmethod def parse(cls, buf: memoryview, params: Params) \ -> tuple[ExtensionOption, memoryview]: start = cls._whitespace_length(buf) match = cls._opt_pattern.match(buf, start) if not match: raise NotParseable(buf[start:]) option = match.group(0).upper() buf = buf[match.end(0):] arg, buf = cls._parse_arg(buf, params) return cls(option, arg), buf
[docs] class ExtensionOptions(Parseable[Mapping[bytes, List]]): """Represents a set of command options, which may or may not have an associated argument. Command options are always optional, so the parsing will not fail, it will just return an empty object. See Also: `RFC 4466 2.1. <https://tools.ietf.org/html/rfc4466#section-2.1>`_ Args: options: The mapping of options to argument. """ _opt_pattern = re.compile(br'[a-zA-Z_.-][a-zA-Z0-9_.:-]*') _empty: ExtensionOptions | None = None def __init__(self, options: Iterable[ExtensionOption]) -> None: super().__init__() self.options: Mapping[bytes, List] = \ {opt.option: opt.arg for opt in options} self._raw: bytes | None = None
[docs] @classmethod def empty(cls) -> ExtensionOptions: """Return an empty set of command options.""" if cls._empty is None: cls._empty = ExtensionOptions({}) return cls._empty
@property def value(self) -> Mapping[bytes, List]: return self.options def has(self, option: bytes) -> bool: return option in self.options def get(self, option: bytes) -> List | None: return self.options.get(option, None) def __bool__(self) -> bool: return bool(self.options) def __len__(self) -> int: return len(self.options) def __bytes__(self) -> bytes: if self._raw is None: parts = [ExtensionOption(option, arg) for option, arg in sorted(self.options.items())] self._raw = b'(' + BytesFormat(b' ').join(parts) + b')' return self._raw @classmethod def _parse_paren(cls, buf: memoryview, paren: bytes) -> memoryview: start = cls._whitespace_length(buf) if buf[start:start + 1] != paren: raise NotParseable(buf) return buf[start + 1:] @classmethod def _parse(cls, buf: memoryview, params: Params) \ -> tuple[ExtensionOptions, memoryview]: buf = cls._parse_paren(buf, b'(') result: list[ExtensionOption] = [] while True: try: option, buf = ExtensionOption.parse(buf, params) except NotParseable: break else: result.append(option) buf = cls._parse_paren(buf, b')') return cls(result), buf
[docs] @classmethod def parse(cls, buf: memoryview, params: Params) \ -> tuple[ExtensionOptions, memoryview]: try: return cls._parse(buf, params) except NotParseable: return cls.empty(), buf