123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- """Utility."""
- from functools import wraps, lru_cache
- import warnings
- import re
- from typing import Callable, Any, Optional, Tuple, List
- DEBUG = 0x00001
- RE_PATTERN_LINE_SPLIT = re.compile(r'(?:\r\n|(?!\r\n)[\n\r])|$')
- UC_A = ord('A')
- UC_Z = ord('Z')
- @lru_cache(maxsize=512)
- def lower(string: str) -> str:
- """Lower."""
- new_string = []
- for c in string:
- o = ord(c)
- new_string.append(chr(o + 32) if UC_A <= o <= UC_Z else c)
- return ''.join(new_string)
- class SelectorSyntaxError(Exception):
- """Syntax error in a CSS selector."""
- def __init__(self, msg: str, pattern: Optional[str] = None, index: Optional[int] = None) -> None:
- """Initialize."""
- self.line = None
- self.col = None
- self.context = None
- if pattern is not None and index is not None:
- # Format pattern to show line and column position
- self.context, self.line, self.col = get_pattern_context(pattern, index)
- msg = '{}\n line {}:\n{}'.format(msg, self.line, self.context)
- super().__init__(msg)
- def deprecated(message: str, stacklevel: int = 2) -> Callable[..., Any]: # pragma: no cover
- """
- Raise a `DeprecationWarning` when wrapped function/method is called.
- Usage:
- @deprecated("This method will be removed in version X; use Y instead.")
- def some_method()"
- pass
- """
- def _wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
- @wraps(func)
- def _deprecated_func(*args: Any, **kwargs: Any) -> Any:
- warnings.warn(
- f"'{func.__name__}' is deprecated. {message}",
- category=DeprecationWarning,
- stacklevel=stacklevel
- )
- return func(*args, **kwargs)
- return _deprecated_func
- return _wrapper
- def warn_deprecated(message: str, stacklevel: int = 2) -> None: # pragma: no cover
- """Warn deprecated."""
- warnings.warn(
- message,
- category=DeprecationWarning,
- stacklevel=stacklevel
- )
- def get_pattern_context(pattern: str, index: int) -> Tuple[str, int, int]:
- """Get the pattern context."""
- last = 0
- current_line = 1
- col = 1
- text = [] # type: List[str]
- line = 1
- offset = None # type: Optional[int]
- # Split pattern by newline and handle the text before the newline
- for m in RE_PATTERN_LINE_SPLIT.finditer(pattern):
- linetext = pattern[last:m.start(0)]
- if not len(m.group(0)) and not len(text):
- indent = ''
- offset = -1
- col = index - last + 1
- elif last <= index < m.end(0):
- indent = '--> '
- offset = (-1 if index > m.start(0) else 0) + 3
- col = index - last + 1
- else:
- indent = ' '
- offset = None
- if len(text):
- # Regardless of whether we are presented with `\r\n`, `\r`, or `\n`,
- # we will render the output with just `\n`. We will still log the column
- # correctly though.
- text.append('\n')
- text.append('{}{}'.format(indent, linetext))
- if offset is not None:
- text.append('\n')
- text.append(' ' * (col + offset) + '^')
- line = current_line
- current_line += 1
- last = m.end(0)
- return ''.join(text), line, col
|