1"""
2This module provides private utility functions for backends.
3
4WARNING: The *pathspec._backends* package is not part of the public API. Its
5contents and structure are likely to change.
6"""
7
8import operator
9import re
10from collections.abc import (
11 Iterable)
12from functools import (
13 reduce)
14from typing import (
15 Union, # Replaced by `X | Y` in 3.10.
16 TypeVar,
17 overload)
18
19from pathspec.pattern import (
20 Pattern,
21 RegexPattern)
22
23_END_ANCHOR_BYTES = re.compile(rb'\\.', re.DOTALL)
24"""
25Regular expression to match an escaped character.
26"""
27
28_END_ANCHOR_STR = re.compile(r'\\.', re.DOTALL)
29"""
30Regular expression to match an escaped character.
31"""
32
33_ASCII_FLAGS = int(re.ASCII)
34"""
35The compile flags that both engines satisfy by default because their character
36classes and word boundaries are ASCII-only.
37
38`re.ASCII` cannot be relied upon this way when it is combined with
39`re.IGNORECASE`: the engines fold the case of Unicode characters that Python
40would leave alone.
41"""
42
43_NOOP_FLAGS = int(re.UNICODE | re.DEBUG)
44r"""
45The compile flags that need no translation.
46
47- `re.UNICODE` is implied for str patterns, and both engines match `\w`, `\d`,
48 `\s`, and `\b` as ASCII regardless.
49
50- `re.DEBUG` only affects how Python compiles the expression.
51"""
52
53_TRANSLATABLE_FLAGS = [
54 (int(re.IGNORECASE), 'i'),
55 (int(re.MULTILINE), 'm'),
56 (int(re.DOTALL), 's'),
57]
58"""
59The compile flags that both RE2 and Hyperscan understand as global inline flags
60at the start of an expression (`(?ims)`).
61
62RE2 rejects `(?x)` (it has no verbose mode), and both engines reject `(?a)`,
63`(?u)`, `(?U)`, and `(?L)`. Note that RE2 reads `(?U)` as *non-greedy*, which
64is not what Python's `re.UNICODE` means.
65"""
66
67_INCOMPATIBLE_FLAGS = ~(
68 _ASCII_FLAGS
69 | _NOOP_FLAGS
70 | reduce(operator.or_, [flag for flag, _letter in _TRANSLATABLE_FLAGS])
71)
72"""
73The compile flags that the Hyperscan and RE2 backends do not support.
74"""
75
76TPattern = TypeVar("TPattern", bound=Pattern)
77
78
79def enumerate_patterns(
80 patterns: Iterable[TPattern],
81 filter: bool,
82 reverse: bool,
83) -> list[tuple[int, TPattern]]:
84 """
85 Enumerate the patterns.
86
87 *patterns* (:class:`Iterable` of :class:`.Pattern`) contains the patterns.
88
89 *filter* (:class:`bool`) is whether to remove no-op patterns (:data:`True`),
90 or keep them (:data:`False`).
91
92 *reverse* (:class:`bool`) is whether to reverse the pattern order
93 (:data:`True`), or keep the order (:data:`True`).
94
95 Returns the enumerated patterns (:class:`list` of :class:`tuple`).
96 """
97 out_patterns = [
98 (__i, __pat)
99 for __i, __pat in enumerate(patterns)
100 if not filter or __pat.include is not None
101 ]
102 if reverse:
103 out_patterns.reverse()
104
105 return out_patterns
106
107
108def get_regex_flags(regex: Union[re.Pattern, str, bytes, None]) -> int:
109 """
110 Get the compile flags of a pattern's regular expression.
111
112 *regex* (:class:`re.Pattern`, :class:`bytes`, :class:`str`, or
113 :data:`None`) is the regular expression of a :class:`.RegexPattern`.
114
115 Returns the flags (:class:`int`) of a regular expression compiled by
116 Python's *re* module, or zero (:class:`int`) for anything else (a native
117 backend only ever sees the expression of a foreign pattern).
118 """
119 if isinstance(regex, re.Pattern):
120 return int(regex.flags)
121
122 return 0
123
124
125def has_unsupported_flags(patterns: Iterable[Pattern]) -> bool:
126 """
127 Check for active patterns with compile flags a native backend can't keep.
128
129 *patterns* (:class:`Iterable` of :class:`.Pattern`) contains the patterns.
130
131 Returns the answer (:class:`bool`).
132 """
133 for pattern in patterns:
134 if pattern.include is None or not isinstance(pattern, RegexPattern):
135 continue
136
137 regex = pattern.regex
138 if not isinstance(regex, re.Pattern):
139 # The pattern was not compiled by Python's *re* module. Its flags are
140 # unknown, and only the simple backend can use it faithfully.
141 continue
142
143 if not supports_regex_flags(regex.flags):
144 return True
145
146 return False
147
148
149@overload
150def translate_end_anchor(regex: str) -> str:
151 ...
152
153
154@overload
155def translate_end_anchor(regex: bytes) -> bytes:
156 ...
157
158
159@overload
160def translate_end_anchor(regex: Union[str, bytes]) -> Union[str, bytes]:
161 ...
162
163
164def translate_end_anchor(regex: Union[str, bytes]) -> Union[str, bytes]:
165 """
166 Translate Python's strict end anchor to the RE2 and Hyperscan spelling.
167
168 *regex* (:class:`bytes` or :class:`str`) is the regular expression.
169
170 Returns the translated regular expression (:class:`bytes` or :class:`str`).
171 """
172 # Escaped backslashes are consumed as pairs, keeping literal `\Z` names
173 # intact.
174 if isinstance(regex, bytes):
175 return _END_ANCHOR_BYTES.sub(_sub_end_anchor_bytes, regex)
176 else:
177 return _END_ANCHOR_STR.sub(_sub_end_anchor_str, regex)
178
179
180def _sub_end_anchor_bytes(match: re.Match) -> bytes:
181 """
182 Replaces Python's strict end anchor to the RE2 and Hyperscan spelling.
183
184 *match* (:class:`re.Match`) is the match object.
185
186 Returns the string replacement (:class:`bytes`).
187 """
188 val = match[0]
189 if val == rb'\Z':
190 return rb'\z'
191 else:
192 return val
193
194
195def _sub_end_anchor_str(match: re.Match) -> str:
196 """
197 Replaces Python's strict end anchor to the RE2 and Hyperscan spelling.
198
199 *match* (:class:`re.Match`) is the match object.
200
201 Returns the string replacement (:class:`str`).
202 """
203 val = match[0]
204 if val == r'\Z':
205 return r'\z'
206 else:
207 return val
208
209
210def supports_regex_flags(flags: int) -> bool:
211 """
212 Check whether a native backend can preserve the compile *flags*.
213
214 *flags* (:class:`int`) is the flags of a compiled :class:`re.Pattern`.
215
216 Returns the answer (:class:`bool`).
217 """
218 if flags & _INCOMPATIBLE_FLAGS:
219 return False
220
221 if flags & re.ASCII and flags & re.IGNORECASE:
222 return False
223
224 return True
225
226
227@overload
228def translate_regex_flags(regex: str, flags: int) -> str:
229 ...
230
231
232@overload
233def translate_regex_flags(regex: bytes, flags: int) -> bytes:
234 ...
235
236
237@overload
238def translate_regex_flags(regex: Union[str, bytes], flags: int) -> Union[str, bytes]:
239 ...
240
241
242def translate_regex_flags(
243 regex: Union[str, bytes],
244 flags: int,
245) -> Union[str, bytes]:
246 """
247 Prepend the global inline flags that a native backend understands.
248
249 *regex* (:class:`bytes` or :class:`str`) is the regular expression.
250
251 *flags* (:class:`int`) is the flags of the compiled :class:`re.Pattern`
252 that *regex* came from. Only flags accepted by
253 :func:`supports_regex_flags` may be translated.
254
255 Returns the regular expression (:class:`bytes` or :class:`str`).
256 """
257 letters = ''.join(
258 letter
259 for flag, letter in _TRANSLATABLE_FLAGS
260 if flags & flag
261 )
262 if not letters:
263 return regex
264
265 prefix = f'(?{letters})'
266 if isinstance(regex, bytes):
267 return prefix.encode('ascii') + regex
268
269 return prefix + regex