Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/soupsieve/util.py: 100%
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
11RE_PATTERN_LINE_SPLIT = re.compile(r'(?:\r\n|(?!\r\n)[\n\r])|$')
13UC_A = ord('A')
14UC_Z = ord('Z')
17@lru_cache(maxsize=512)
18def lower(string: str) -> str:
19 """Lower."""
21 new_string = []
22 for c in string:
23 o = ord(c)
24 new_string.append(chr(o + 32) if UC_A <= o <= UC_Z else c)
25 return ''.join(new_string)
28class SelectorSyntaxError(Exception):
29 """Syntax error in a CSS selector."""
31 def __init__(self, msg: str, pattern: str | None = None, index: int | None = None) -> None:
32 """Initialize."""
34 self.line = None
35 self.col = None
36 self.context = None
38 if pattern is not None and index is not None:
39 # Format pattern to show line and column position
40 self.context, self.line, self.col = get_pattern_context(pattern, index)
41 msg = f'{msg}\n line {self.line}:\n{self.context}'
43 super().__init__(msg)
46def deprecated(message: str, stacklevel: int = 2) -> Callable[..., Any]: # pragma: no cover
47 """
48 Raise a `DeprecationWarning` when wrapped function/method is called.
50 Usage:
52 @deprecated("This method will be removed in version X; use Y instead.")
53 def some_method()"
54 pass
55 """
57 def _wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
58 @wraps(func)
59 def _deprecated_func(*args: Any, **kwargs: Any) -> Any:
60 warnings.warn(
61 f"'{func.__name__}' is deprecated. {message}",
62 category=DeprecationWarning,
63 stacklevel=stacklevel
64 )
65 return func(*args, **kwargs)
66 return _deprecated_func
67 return _wrapper
70def warn_deprecated(message: str, stacklevel: int = 2) -> None: # pragma: no cover
71 """Warn deprecated."""
73 warnings.warn(
74 message,
75 category=DeprecationWarning,
76 stacklevel=stacklevel
77 )
80def get_pattern_context(pattern: str, index: int) -> tuple[str, int, int]:
81 """Get the pattern context."""
83 last = 0
84 current_line = 1
85 col = 1
86 text = [] # type: list[str]
87 line = 1
88 offset = None # type: int | None
90 # Split pattern by newline and handle the text before the newline
91 for m in RE_PATTERN_LINE_SPLIT.finditer(pattern):
92 linetext = pattern[last:m.start(0)]
93 if not len(m.group(0)) and not len(text):
94 indent = ''
95 offset = -1
96 col = index - last + 1
97 elif last <= index < m.end(0):
98 indent = '--> '
99 offset = (-1 if index > m.start(0) else 0) + 3
100 col = index - last + 1
101 else:
102 indent = ' '
103 offset = None
104 if len(text):
105 # Regardless of whether we are presented with `\r\n`, `\r`, or `\n`,
106 # we will render the output with just `\n`. We will still log the column
107 # correctly though.
108 text.append('\n')
109 text.append(f'{indent}{linetext}')
110 if offset is not None:
111 text.append('\n')
112 text.append(' ' * (col + offset) + '^')
113 line = current_line
115 current_line += 1
116 last = m.end(0)
118 return ''.join(text), line, col