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

57 statements  

1"""Utility.""" 

2from __future__ import annotations 

3from functools import wraps, lru_cache 

4import warnings 

5import re 

6from typing import Callable, Any 

7 

8DEBUG = 0x00001 

9NOCACHE = 0x00002 

10NOSTRICT = 0x00004 

11 

12RE_PATTERN_LINE_SPLIT = re.compile(r'(?:\r\n|(?!\r\n)[\n\r])|$') 

13 

14UC_A = ord('A') 

15UC_Z = ord('Z') 

16 

17 

18@lru_cache(maxsize=512) 

19def lower(string: str) -> str: 

20 """Lower.""" 

21 

22 new_string = [] 

23 for c in string: 

24 o = ord(c) 

25 new_string.append(chr(o + 32) if UC_A <= o <= UC_Z else c) 

26 return ''.join(new_string) 

27 

28 

29class SelectorSyntaxError(Exception): 

30 """Syntax error in a CSS selector.""" 

31 

32 def __init__(self, msg: str, pattern: str | None = None, index: int | None = None) -> None: 

33 """Initialize.""" 

34 

35 self.line = None 

36 self.col = None 

37 self.context = None 

38 

39 if pattern is not None and index is not None: 

40 # Format pattern to show line and column position 

41 self.context, self.line, self.col = get_pattern_context(pattern, index) 

42 msg = f'{msg}\n line {self.line}:\n{self.context}' 

43 

44 super().__init__(msg) 

45 

46 

47def deprecated(message: str, stacklevel: int = 2) -> Callable[..., Any]: # pragma: no cover 

48 """ 

49 Raise a `DeprecationWarning` when wrapped function/method is called. 

50 

51 Usage: 

52 

53 @deprecated("This method will be removed in version X; use Y instead.") 

54 def some_method()" 

55 pass 

56 """ 

57 

58 def _wrapper(func: Callable[..., Any]) -> Callable[..., Any]: 

59 @wraps(func) 

60 def _deprecated_func(*args: Any, **kwargs: Any) -> Any: 

61 warnings.warn( 

62 f"'{func.__name__}' is deprecated. {message}", 

63 category=DeprecationWarning, 

64 stacklevel=stacklevel 

65 ) 

66 return func(*args, **kwargs) 

67 return _deprecated_func 

68 return _wrapper 

69 

70 

71def warn_deprecated(message: str, stacklevel: int = 2) -> None: # pragma: no cover 

72 """Warn deprecated.""" 

73 

74 warnings.warn( 

75 message, 

76 category=DeprecationWarning, 

77 stacklevel=stacklevel 

78 ) 

79 

80 

81def get_pattern_context(pattern: str, index: int) -> tuple[str, int, int]: 

82 """Get the pattern context.""" 

83 

84 last = 0 

85 current_line = 1 

86 col = 1 

87 text: list[str] = [] 

88 line = 1 

89 offset: int | None = None 

90 

91 # Split pattern by newline and handle the text before the newline 

92 for m in RE_PATTERN_LINE_SPLIT.finditer(pattern): 

93 linetext = pattern[last:m.start(0)] 

94 if not len(m.group(0)) and not len(text): 

95 indent = '' 

96 offset = -1 

97 col = index - last + 1 

98 elif last <= index < m.end(0): 

99 indent = '--> ' 

100 offset = (-1 if index > m.start(0) else 0) + 3 

101 col = index - last + 1 

102 else: 

103 indent = ' ' 

104 offset = None 

105 if len(text): 

106 # Regardless of whether we are presented with `\r\n`, `\r`, or `\n`, 

107 # we will render the output with just `\n`. We will still log the column 

108 # correctly though. 

109 text.append('\n') 

110 text.append(f'{indent}{linetext}') 

111 if offset is not None: 

112 text.append('\n') 

113 text.append(' ' * (col + offset) + '^') 

114 line = current_line 

115 

116 current_line += 1 

117 last = m.end(0) 

118 

119 return ''.join(text), line, col