"""CSS selector structure items.""" import copyreg from .pretty import pretty from typing import Any, Type, Tuple, Union, Dict, Iterator, Hashable, Optional, Pattern, Iterable, Mapping __all__ = ( 'Selector', 'SelectorNull', 'SelectorTag', 'SelectorAttribute', 'SelectorContains', 'SelectorNth', 'SelectorLang', 'SelectorList', 'Namespaces', 'CustomSelectors' ) SEL_EMPTY = 0x1 SEL_ROOT = 0x2 SEL_DEFAULT = 0x4 SEL_INDETERMINATE = 0x8 SEL_SCOPE = 0x10 SEL_DIR_LTR = 0x20 SEL_DIR_RTL = 0x40 SEL_IN_RANGE = 0x80 SEL_OUT_OF_RANGE = 0x100 SEL_DEFINED = 0x200 SEL_PLACEHOLDER_SHOWN = 0x400 class Immutable: """Immutable.""" __slots__: Tuple[str, ...] = ('_hash',) _hash: int def __init__(self, **kwargs: Any) -> None: """Initialize.""" temp = [] for k, v in kwargs.items(): temp.append(type(v)) temp.append(v) super(Immutable, self).__setattr__(k, v) super(Immutable, self).__setattr__('_hash', hash(tuple(temp))) @classmethod def __base__(cls) -> "Type[Immutable]": """Get base class.""" return cls def __eq__(self, other: Any) -> bool: """Equal.""" return ( isinstance(other, self.__base__()) and all([getattr(other, key) == getattr(self, key) for key in self.__slots__ if key != '_hash']) ) def __ne__(self, other: Any) -> bool: """Equal.""" return ( not isinstance(other, self.__base__()) or any([getattr(other, key) != getattr(self, key) for key in self.__slots__ if key != '_hash']) ) def __hash__(self) -> int: """Hash.""" return self._hash def __setattr__(self, name: str, value: Any) -> None: """Prevent mutability.""" raise AttributeError("'{}' is immutable".format(self.__class__.__name__)) def __repr__(self) -> str: # pragma: no cover """Representation.""" return "{}({})".format( self.__class__.__name__, ', '.join(["{}={!r}".format(k, getattr(self, k)) for k in self.__slots__[:-1]]) ) __str__ = __repr__ def pretty(self) -> None: # pragma: no cover """Pretty print.""" print(pretty(self)) class ImmutableDict(Mapping[Any, Any]): """Hashable, immutable dictionary.""" def __init__( self, arg: Union[Dict[Any, Any], Iterable[Tuple[Any, Any]]] ) -> None: """Initialize.""" self._validate(arg) self._d = dict(arg) self._hash = hash(tuple([(type(x), x, type(y), y) for x, y in sorted(self._d.items())])) def _validate(self, arg: Union[Dict[Any, Any], Iterable[Tuple[Any, Any]]]) -> None: """Validate arguments.""" if isinstance(arg, dict): if not all([isinstance(v, Hashable) for v in arg.values()]): raise TypeError('{} values must be hashable'.format(self.__class__.__name__)) elif not all([isinstance(k, Hashable) and isinstance(v, Hashable) for k, v in arg]): raise TypeError('{} values must be hashable'.format(self.__class__.__name__)) def __iter__(self) -> Iterator[Any]: """Iterator.""" return iter(self._d) def __len__(self) -> int: """Length.""" return len(self._d) def __getitem__(self, key: Any) -> Any: """Get item: `namespace['key']`.""" return self._d[key] def __hash__(self) -> int: """Hash.""" return self._hash def __repr__(self) -> str: # pragma: no cover """Representation.""" return "{!r}".format(self._d) __str__ = __repr__ class Namespaces(ImmutableDict): """Namespaces.""" def __init__(self, arg: Union[Dict[str, str], Iterable[Tuple[str, str]]]) -> None: """Initialize.""" super().__init__(arg) def _validate(self, arg: Union[Dict[str, str], Iterable[Tuple[str, str]]]) -> None: """Validate arguments.""" if isinstance(arg, dict): if not all([isinstance(v, str) for v in arg.values()]): raise TypeError('{} values must be hashable'.format(self.__class__.__name__)) elif not all([isinstance(k, str) and isinstance(v, str) for k, v in arg]): raise TypeError('{} keys and values must be Unicode strings'.format(self.__class__.__name__)) class CustomSelectors(ImmutableDict): """Custom selectors.""" def __init__(self, arg: Union[Dict[str, str], Iterable[Tuple[str, str]]]) -> None: """Initialize.""" super().__init__(arg) def _validate(self, arg: Union[Dict[str, str], Iterable[Tuple[str, str]]]) -> None: """Validate arguments.""" if isinstance(arg, dict): if not all([isinstance(v, str) for v in arg.values()]): raise TypeError('{} values must be hashable'.format(self.__class__.__name__)) elif not all([isinstance(k, str) and isinstance(v, str) for k, v in arg]): raise TypeError('{} keys and values must be Unicode strings'.format(self.__class__.__name__)) class Selector(Immutable): """Selector.""" __slots__ = ( 'tag', 'ids', 'classes', 'attributes', 'nth', 'selectors', 'relation', 'rel_type', 'contains', 'lang', 'flags', '_hash' ) tag: Optional['SelectorTag'] ids: Tuple[str, ...] classes: Tuple[str, ...] attributes: Tuple['SelectorAttribute', ...] nth: Tuple['SelectorNth', ...] selectors: Tuple['SelectorList', ...] relation: 'SelectorList' rel_type: Optional[str] contains: Tuple['SelectorContains', ...] lang: Tuple['SelectorLang', ...] flags: int def __init__( self, tag: Optional['SelectorTag'], ids: Tuple[str, ...], classes: Tuple[str, ...], attributes: Tuple['SelectorAttribute', ...], nth: Tuple['SelectorNth', ...], selectors: Tuple['SelectorList', ...], relation: 'SelectorList', rel_type: Optional[str], contains: Tuple['SelectorContains', ...], lang: Tuple['SelectorLang', ...], flags: int ): """Initialize.""" super().__init__( tag=tag, ids=ids, classes=classes, attributes=attributes, nth=nth, selectors=selectors, relation=relation, rel_type=rel_type, contains=contains, lang=lang, flags=flags ) class SelectorNull(Immutable): """Null Selector.""" def __init__(self) -> None: """Initialize.""" super().__init__() class SelectorTag(Immutable): """Selector tag.""" __slots__ = ("name", "prefix", "_hash") name: str prefix: Optional[str] def __init__(self, name: str, prefix: Optional[str]) -> None: """Initialize.""" super().__init__(name=name, prefix=prefix) class SelectorAttribute(Immutable): """Selector attribute rule.""" __slots__ = ("attribute", "prefix", "pattern", "xml_type_pattern", "_hash") attribute: str prefix: str pattern: Optional[Pattern[str]] xml_type_pattern: Optional[Pattern[str]] def __init__( self, attribute: str, prefix: str, pattern: Optional[Pattern[str]], xml_type_pattern: Optional[Pattern[str]] ) -> None: """Initialize.""" super().__init__( attribute=attribute, prefix=prefix, pattern=pattern, xml_type_pattern=xml_type_pattern ) class SelectorContains(Immutable): """Selector contains rule.""" __slots__ = ("text", "own", "_hash") text: Tuple[str, ...] own: bool def __init__(self, text: Iterable[str], own: bool) -> None: """Initialize.""" super().__init__(text=tuple(text), own=own) class SelectorNth(Immutable): """Selector nth type.""" __slots__ = ("a", "n", "b", "of_type", "last", "selectors", "_hash") a: int n: bool b: int of_type: bool last: bool selectors: 'SelectorList' def __init__(self, a: int, n: bool, b: int, of_type: bool, last: bool, selectors: 'SelectorList') -> None: """Initialize.""" super().__init__( a=a, n=n, b=b, of_type=of_type, last=last, selectors=selectors ) class SelectorLang(Immutable): """Selector language rules.""" __slots__ = ("languages", "_hash",) languages: Tuple[str, ...] def __init__(self, languages: Iterable[str]): """Initialize.""" super().__init__(languages=tuple(languages)) def __iter__(self) -> Iterator[str]: """Iterator.""" return iter(self.languages) def __len__(self) -> int: # pragma: no cover """Length.""" return len(self.languages) def __getitem__(self, index: int) -> str: # pragma: no cover """Get item.""" return self.languages[index] class SelectorList(Immutable): """Selector list.""" __slots__ = ("selectors", "is_not", "is_html", "_hash") selectors: Tuple[Union['Selector', 'SelectorNull'], ...] is_not: bool is_html: bool def __init__( self, selectors: Optional[Iterable[Union['Selector', 'SelectorNull']]] = None, is_not: bool = False, is_html: bool = False ) -> None: """Initialize.""" super().__init__( selectors=tuple(selectors) if selectors is not None else tuple(), is_not=is_not, is_html=is_html ) def __iter__(self) -> Iterator[Union['Selector', 'SelectorNull']]: """Iterator.""" return iter(self.selectors) def __len__(self) -> int: """Length.""" return len(self.selectors) def __getitem__(self, index: int) -> Union['Selector', 'SelectorNull']: """Get item.""" return self.selectors[index] def _pickle(p: Any) -> Any: return p.__base__(), tuple([getattr(p, s) for s in p.__slots__[:-1]]) def pickle_register(obj: Any) -> None: """Allow object to be pickled.""" copyreg.pickle(obj, _pickle) pickle_register(Selector) pickle_register(SelectorNull) pickle_register(SelectorTag) pickle_register(SelectorAttribute) pickle_register(SelectorContains) pickle_register(SelectorNth) pickle_register(SelectorLang) pickle_register(SelectorList)