Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/soupsieve/util.py: 31%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Utility."""
2from __future__ import annotations
3from functools import wraps, lru_cache
4import warnings
5import re
6from typing import Callable, Any
8DEBUG = 0x00001
9NOCACHE = 0x00002
10NOSTRICT = 0x00004
12RE_PATTERN_LINE_SPLIT = re.compile(r'(?:\r\n|(?!\r\n)[\n\r])|$')
14ASCII_LOWER = str.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
17def ascii_lower(string: str) -> str:
18 """
19 Lower ASCII letters only, without caching.
21 Use this for document text, such as attribute values, which can be large and should not be held by a cache.
22 """
24 return string.lower() if string.isascii() else string.translate(ASCII_LOWER)
27@lru_cache(maxsize=512)
28def lower(string: str) -> str:
29 """Lower ASCII letters only."""
31 return ascii_lower(string)
34class SelectorSyntaxError(Exception):
35 """Syntax error in a CSS selector."""
37 def __init__(self, msg: str, pattern: str | None = None, index: int | None = None) -> None:
38 """Initialize."""
40 self.line = None
41 self.col = None
42 self.context = None
44 if pattern is not None and index is not None:
45 # Format pattern to show line and column position
46 self.context, self.line, self.col = get_pattern_context(pattern, index)
47 msg = f'{msg}\n line {self.line}:\n{self.context}'
49 super().__init__(msg)
52def deprecated(message: str, stacklevel: int = 2) -> Callable[..., Any]: # pragma: no cover
53 """
54 Raise a `DeprecationWarning` when wrapped function/method is called.
56 Usage:
58 @deprecated("This method will be removed in version X; use Y instead.")
59 def some_method()"
60 pass
61 """
63 def _wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
64 @wraps(func)
65 def _deprecated_func(*args: Any, **kwargs: Any) -> Any:
66 warnings.warn(
67 f"'{func.__name__}' is deprecated. {message}",
68 category=DeprecationWarning,
69 stacklevel=stacklevel
70 )
71 return func(*args, **kwargs)
72 return _deprecated_func
73 return _wrapper
76def warn_deprecated(message: str, stacklevel: int = 2) -> None: # pragma: no cover
77 """Warn deprecated."""
79 warnings.warn(
80 message,
81 category=DeprecationWarning,
82 stacklevel=stacklevel
83 )
86def get_pattern_context(pattern: str, index: int) -> tuple[str, int, int]:
87 """Get the pattern context."""
89 last = 0
90 current_line = 1
91 col = 1
92 text: list[str] = []
93 line = 1
94 offset: int | None = None
96 # Split pattern by newline and handle the text before the newline
97 for m in RE_PATTERN_LINE_SPLIT.finditer(pattern):
98 linetext = pattern[last:m.start(0)]
99 if not len(m.group(0)) and not len(text):
100 indent = ''
101 offset = -1
102 col = index - last + 1
103 elif last <= index < m.end(0):
104 indent = '--> '
105 offset = (-1 if index > m.start(0) else 0) + 3
106 col = index - last + 1
107 else:
108 indent = ' '
109 offset = None
110 if len(text):
111 # Regardless of whether we are presented with `\r\n`, `\r`, or `\n`,
112 # we will render the output with just `\n`. We will still log the column
113 # correctly though.
114 text.append('\n')
115 text.append(f'{indent}{linetext}')
116 if offset is not None:
117 text.append('\n')
118 text.append(' ' * (col + offset) + '^')
119 line = current_line
121 current_line += 1
122 last = m.end(0)
124 return ''.join(text), line, col