Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pathspec/_backends/re2/pathspec.py: 34%

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

65 statements  

1""" 

2This module provides the :module:`re2` backend for :class:`~pathspec.pathspec.PathSpec`. 

3 

4WARNING: The *pathspec._backends.re2* package is not part of the public API. Its 

5contents and structure are likely to change. 

6""" 

7from __future__ import annotations 

8 

9from collections.abc import ( 

10 Sequence) 

11from typing import ( 

12 Callable, # Replaced by `collections.abc.Callable` in 3.9.2. 

13 Optional) # Replaced by `X | None` in 3.10. 

14 

15try: 

16 import re2 

17except ModuleNotFoundError: 

18 re2 = None # type: ignore[assignment] 

19 

20from pathspec.backend import ( 

21 _Backend) 

22from pathspec.pattern import ( 

23 RegexPattern) 

24from pathspec._typing import ( 

25 override) # Added in 3.12. 

26 

27from .._utils import ( 

28 enumerate_patterns, 

29 get_regex_flags, 

30 has_unsupported_flags, 

31 translate_end_anchor, 

32 translate_regex_flags) 

33 

34from .base import ( 

35 re2_error) 

36from ._base import ( 

37 RE2_OPTIONS, 

38 Re2RegexDat, 

39 Re2RegexDebug) 

40 

41 

42class Re2PsBackend(_Backend): 

43 """ 

44 The :class:`Re2PsBackend` class is the :module:`re2` implementation used by 

45 :class:`~pathspec.pathspec.PathSpec` for matching files. 

46 """ 

47 

48 def __init__( 

49 self, 

50 patterns: Sequence[RegexPattern], 

51 *, 

52 _debug_regex: Optional[bool] = None, 

53 _test_sort: Optional[Callable[[list], None]] = None, 

54 ) -> None: 

55 """ 

56 Initialize the :class:`Re2PsBackend` instance. 

57 

58 *patterns* (:class:`Sequence` of :class:`.RegexPattern`) contains the 

59 compiled patterns. 

60 """ 

61 if re2_error is not None: 

62 raise re2_error 

63 

64 if patterns and not isinstance(patterns[0], RegexPattern): 

65 raise TypeError(f"{patterns[0]=!r} must be a RegexPattern.") 

66 

67 if has_unsupported_flags(patterns): 

68 raise ValueError(( 

69 "The re2 backend cannot preserve the regex compile flags. " 

70 "Use backend='simple'." 

71 )) 

72 

73 use_patterns = dict(enumerate_patterns( 

74 patterns, filter=True, reverse=False, 

75 )) 

76 regex_set = self._make_set() 

77 

78 self._debug_regex = bool(_debug_regex) 

79 """ 

80 *_debug_regex* (:class:`bool`) is whether to include additional debugging 

81 information for the regular expressions. 

82 """ 

83 

84 self._patterns: dict[int, RegexPattern] = use_patterns 

85 """ 

86 *_patterns* (:class:`dict`) maps pattern index (:class:`int`) to pattern 

87 (:class:`RegexPattern`). 

88 """ 

89 

90 self._regex_data: list[Re2RegexDat] = self._init_set( 

91 debug=self._debug_regex, 

92 patterns=use_patterns, 

93 regex_set=regex_set, 

94 sort_indices=_test_sort, 

95 ) 

96 """ 

97 *_regex_data* (:class:`list`) maps regex index (:class:`int`) to regex data 

98 (:class:`Re2RegexDat`). 

99 """ 

100 

101 self._set: re2.Set = regex_set # type: ignore 

102 """ 

103 *_set* (:class:`re2.Set`) is the re2 regex set. 

104 """ 

105 

106 @staticmethod 

107 def _init_set( 

108 debug: bool, 

109 patterns: dict[int, RegexPattern], 

110 regex_set: re2.Set, # type: ignore 

111 sort_indices: Optional[Callable[[list[int]], None]], 

112 ) -> list[Re2RegexDat]: 

113 """ 

114 Create the re2 regex set. 

115 

116 *debug* (:class:`bool`) is whether to include additional debugging 

117 information for the regular expressions. 

118 

119 *patterns* (:class:`dict`) maps pattern index (:class:`int`) to pattern 

120 (:class:`.RegexPattern`). 

121 

122 *regex_set* (:class:`re2.Set`) is the regex set. 

123 

124 *sort_indices* (:class:`callable` or :data:`None`) is a function used to 

125 sort the patterns by index. This is used during testing to ensure the order 

126 of patterns is not accidentally relied on. 

127 

128 Returns a :class:`list` indexed by regex id (:class:`int`) to its data 

129 (:class:`Re2RegexDat`). 

130 """ 

131 # Sort patterns. 

132 indices = list(patterns.keys()) 

133 if sort_indices is not None: 

134 sort_indices(indices) 

135 

136 # Prepare patterns. 

137 regex_data: list[Re2RegexDat] = [] 

138 for pattern_index in indices: 

139 pattern = patterns[pattern_index] 

140 pattern_include = pattern.include 

141 if pattern_include is None: 

142 continue 

143 

144 assert pattern.regex is not None, pattern 

145 assert isinstance(pattern, RegexPattern), pattern 

146 regex = translate_regex_flags( 

147 translate_end_anchor(pattern.regex.pattern), 

148 get_regex_flags(pattern.regex), 

149 ) 

150 

151 if debug: 

152 regex_data.append(Re2RegexDebug( 

153 include=pattern_include, 

154 index=pattern_index, 

155 is_dir_pattern=False, 

156 regex=regex, 

157 )) 

158 else: 

159 regex_data.append(Re2RegexDat( 

160 include=pattern_include, 

161 index=pattern_index, 

162 is_dir_pattern=False, 

163 )) 

164 

165 regex_set.Add(regex) 

166 

167 # Compile patterns. 

168 regex_set.Compile() 

169 return regex_data 

170 

171 @staticmethod 

172 def _make_set() -> re2.Set: # type: ignore 

173 """ 

174 Create the re2 regex set. 

175 

176 Returns the set (:class:`re2.Set`). 

177 """ 

178 assert re2 is not None, (re2, re2_error) 

179 return re2.Set.SearchSet(RE2_OPTIONS) 

180 

181 @override 

182 def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]: 

183 """ 

184 Check the file against the patterns. 

185 

186 *file* (:class:`str`) is the normalized file path to check. 

187 

188 Returns a :class:`tuple` containing whether to include *file* (:class:`bool` 

189 or :data:`None`), and the index of the last matched pattern (:class:`int` or 

190 :data:`None`). 

191 """ 

192 # Find best match. 

193 # - WARNING: According to the documentation on `RE2::Set::Match()`, there is 

194 # no guarantee matches will be produced in order! Later expressions have 

195 # higher priority. 

196 match_ids: Optional[list[int]] = self._set.Match(file) # type: ignore[assignment] 

197 if not match_ids: 

198 return (None, None) 

199 

200 regex_data = self._regex_data 

201 pattern_index = max(regex_data[__id].index for __id in match_ids) 

202 pattern = self._patterns[pattern_index] 

203 return (pattern.include, pattern_index)