""" Utilities and compatibility abstraction. Licensed under MIT Copyright (c) 2015 - 2020 Isaac Muse """ from __future__ import annotations import warnings import sys from typing import Any, Callable, AnyStr PY311 = (3, 11) <= sys.version_info PY312 = (3, 12) <= sys.version_info FMT_FIELD = 0 FMT_INDEX = 1 FMT_ATTR = 2 FMT_CONV = 3 FMT_SPEC = 4 class StringIter: """Preprocess replace tokens.""" def __init__(self, text: str) -> None: """Initialize.""" self._string = text self._index = 0 def __iter__(self) -> StringIter: """Iterate.""" return self def __next__(self) -> str: """Python 3 iterator compatible next.""" return self.iternext() @property def index(self) -> int: """Get Index.""" return self._index def rewind(self, count: int) -> None: """Rewind index.""" if count > self._index: # pragma: no cover raise ValueError("Can't rewind past beginning!") self._index -= count def iternext(self) -> str: """Iterate through characters of the string.""" try: char = self._string[self._index] self._index += 1 except IndexError as e: raise StopIteration from e return char def _to_bstr(obj: Any) -> bytes: """Convert to byte string.""" if isinstance(obj, str): return obj.encode('ascii', 'backslashreplace') elif not isinstance(obj, bytes): return str(obj).encode('ascii', 'backslashreplace') return obj def _to_str(obj: Any) -> str: """Convert to string.""" if not isinstance(obj, str): return str(obj) return obj def format_captures( captures: list[AnyStr], formatting: tuple[tuple[int, Any]], converter: Callable[[Any], AnyStr], default: AnyStr ) -> AnyStr: """Perform a string format on a set of captures.""" capture = captures # type: Any for i, fmt in enumerate(formatting, 0): if i == 0: continue fmt_type, value = fmt if fmt_type == FMT_ATTR: # Attribute capture = getattr(capture, value) elif fmt_type == FMT_INDEX: # Index if value is not None: capture = capture[value] else: capture = default if not capture else capture[0] elif fmt_type == FMT_CONV: # Conversion if value == 'a': capture = ascii(capture) elif value == 'r': capture = repr(capture) elif value == 's': # If the object is not string or byte string already capture = str(capture) elif fmt_type == FMT_SPEC: # Integers and floats don't have an explicit 's' format type. if value[3] and value[3] == 's': if isinstance(capture, int): # pragma: no cover raise ValueError("Unknown format code 's' for object of type 'int'") if isinstance(capture, float): # pragma: no cover raise ValueError("Unknown format code 's' for object of type 'float'") # Ensure object is a byte string capture = converter(capture) spec_type = value[1] if spec_type == '^': capture = capture.center(value[2], value[0]) elif spec_type == ">": capture = capture.rjust(value[2], value[0]) else: capture = capture.ljust(value[2], value[0]) # Make sure the final object is a byte string return converter(capture) class Immutable: """Immutable.""" __slots__: tuple[Any, ...] = () def __init__(self, **kwargs: Any) -> None: """Initialize.""" for k, v in kwargs.items(): super().__setattr__(k, v) def __setattr__(self, name: str, value: Any) -> None: """Prevent mutability.""" raise AttributeError('Class is immutable!') def warn_deprecated(message: str, stacklevel: int = 2) -> None: # pragma: no cover """Warn deprecated.""" warnings.warn( message, category=DeprecationWarning, stacklevel=stacklevel )