Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pathspec/pattern.py: 68%

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

66 statements  

1""" 

2This module provides the base definition for patterns. 

3""" 

4from __future__ import annotations 

5 

6import re 

7from collections.abc import ( 

8 Iterable, 

9 Iterator) 

10from dataclasses import ( 

11 dataclass) 

12from typing import ( 

13 Any, 

14 Optional, # Replaced by `X | None` in 3.10. 

15 TypeVar, 

16 Union) # Replaced by `X | Y` in 3.10. 

17 

18from ._typing import ( 

19 AnyStr, # Removed in 3.18. 

20 deprecated, # Added in 3.13. 

21 override) # Added in 3.12. 

22 

23RegexPatternSelf = TypeVar("RegexPatternSelf", bound='RegexPattern') 

24""" 

25:class:`.RegexPattern` self type hint to support Python v<3.11 using PEP 673 

26recommendation. 

27""" 

28 

29class Pattern(object): 

30 """ 

31 The :class:`Pattern` class is the abstract definition of a pattern. 

32 """ 

33 

34 # Make the class dict-less. 

35 __slots__ = ( 

36 'include', 

37 ) 

38 

39 def __init__(self, include: Optional[bool]) -> None: 

40 """ 

41 Initializes the :class:`Pattern` instance. 

42 

43 *include* (:class:`bool` or :data:`None`) is whether the matched files 

44 should be included (:data:`True`), excluded (:data:`False`), or is a 

45 null-operation (:data:`None`). 

46 """ 

47 

48 self.include = include 

49 """ 

50 *include* (:class:`bool` or :data:`None`) is whether the matched files 

51 should be included (:data:`True`), excluded (:data:`False`), or is a 

52 null-operation (:data:`None`). 

53 """ 

54 

55 @deprecated(( 

56 "Pattern.match() is deprecated. Use Pattern.match_file() with a loop for " 

57 "similar results." 

58 )) 

59 def match(self, files: Iterable[str]) -> Iterator[str]: 

60 """ 

61 .. version-deprecated:: 0.10.0 

62 This method is no longer used. Use the :meth:`self.match_file <.Pattern.match_file>` 

63 method with a loop for similar results. 

64 

65 Matches this pattern against the specified files. 

66 

67 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains each 

68 file relative to the root directory. 

69 

70 Returns an :class:`~collections.abc.Iterable` yielding each matched file 

71 path (:class:`str`). 

72 """ 

73 for file in files: 

74 if self.match_file(file) is not None: 

75 yield file 

76 

77 def match_file(self, file: str) -> Optional[Any]: 

78 """ 

79 Matches this pattern against the specified file. 

80 

81 *file* (:class:`str`) is the normalized file path to match against. 

82 

83 Returns the match result if *file* matched; otherwise, :data:`None`. 

84 """ 

85 raise NotImplementedError(( 

86 "{cls.__module__}.{cls.__qualname__} must override match_file()." 

87 ).format(cls=self.__class__)) 

88 

89 

90class RegexPattern(Pattern): 

91 """ 

92 The :class:`RegexPattern` class is an implementation of a pattern using 

93 regular expressions. 

94 """ 

95 

96 # Keep the class dict-less. 

97 __slots__ = ( 

98 'pattern', 

99 'regex', 

100 ) 

101 

102 def __init__( 

103 self, 

104 pattern: Union[AnyStr, re.Pattern, None], 

105 include: Optional[bool] = None, 

106 **kw, 

107 ) -> None: 

108 """ 

109 Initializes the :class:`RegexPattern` instance. 

110 

111 *pattern* (:class:`str`, :class:`bytes`, :class:`re.Pattern`, or 

112 :data:`None`) is the pattern to compile into a regular expression. 

113 

114 *include* (:class:`bool` or :data:`None`) must be :data:`None` unless 

115 *pattern* is a precompiled regular expression (:class:`re.Pattern`) in which 

116 case it is whether matched files should be included (:data:`True`), excluded 

117 (:data:`False`), or is a null operation (:data:`None`). 

118 

119 .. note:: Subclasses do not need to support the *include* parameter. 

120 

121 `**kw* contains any additional keyword arguments to pass to 

122 :meth:`self.pattern_to_regex <.RegexPattern.pattern_to_regex>`. 

123 """ 

124 regex: Optional[re.Pattern] = None 

125 if isinstance(pattern, (str, bytes)): 

126 assert include is None, ( 

127 f"{include=!r} must be null when {pattern=!r} is a string." 

128 ) 

129 raw_regex, include = self.pattern_to_regex(pattern, **kw) 

130 # NOTE: Make sure to allow a null regular expression to be 

131 # returned for a null-operation. 

132 if include is not None: 

133 assert raw_regex is not None, ( 

134 f"{raw_regex=!r} must be non-null when {include=!r} is not None." 

135 ) 

136 regex = re.compile(raw_regex) 

137 

138 elif pattern is not None and hasattr(pattern, 'match'): 

139 # Assume pattern is a precompiled regular expression. 

140 # - NOTE: Use specified *include*. 

141 regex = pattern 

142 

143 elif pattern is None: 

144 # NOTE: Make sure to allow a null pattern to be passed for a null 

145 # operation. 

146 assert include is None, ( 

147 f"{include=!r} must be null when {pattern=!r} is null." 

148 ) 

149 

150 else: 

151 raise TypeError(f"{pattern=!r} is not a string, re.Pattern, or None.") 

152 

153 super(RegexPattern, self).__init__(include) 

154 

155 self.pattern: Union[str, bytes, re.Pattern, None] = pattern 

156 """ 

157 *pattern* (:class:`str`, :class:`bytes`, :class:`re.Pattern`, or 

158 :data:`None`) is the uncompiled, input pattern. This is for reference. 

159 """ 

160 

161 self.regex: Optional[re.Pattern] = regex 

162 """ 

163 *regex* (:class:`re.Pattern` or :data:`None`) is the compiled regular 

164 expression for the pattern. 

165 """ 

166 

167 def __repr__(self) -> str: 

168 """ 

169 Returns a debug representation of this regex pattern. 

170 """ 

171 return f"{self.__class__.__name__}(pattern={self.pattern!r}, include={self.include!r})" 

172 

173 def __str__(self) -> str: 

174 """ 

175 Returns a string representation of this regex pattern. Equivalent to uncompiled pattern. 

176 

177 The string representation is the uncompiled pattern if it is not 

178 :data:`None`; otherwise, an empty string. 

179 """ 

180 return str(self.pattern or "") 

181 

182 def __copy__(self: RegexPatternSelf) -> RegexPatternSelf: 

183 """ 

184 Performa a shallow copy of the pattern. 

185 

186 Returns the copy (:class:`RegexPattern`). 

187 """ 

188 other = self.__class__(self.regex, self.include) 

189 other.pattern = self.pattern 

190 return other 

191 

192 def __eq__(self, other: object) -> bool: 

193 """ 

194 Tests the equality of this regex pattern with *other* (:class:`RegexPattern`) 

195 by comparing their :attr:`~Pattern.include` and :attr:`~RegexPattern.regex` 

196 attributes. 

197 """ 

198 if isinstance(other, RegexPattern): 

199 return self.include == other.include and self.regex == other.regex 

200 else: 

201 return NotImplemented 

202 

203 @override 

204 def match_file(self, file: AnyStr) -> Optional[RegexMatchResult]: 

205 """ 

206 Matches this pattern against the specified file. 

207 

208 *file* (:class:`str` or :class:`bytes`) is the file path relative to the 

209 root directory (e.g., "relative/path/to/file"). 

210 

211 Returns the match result (:class:`.RegexMatchResult`) if *file* matched; 

212 otherwise, :data:`None`. 

213 """ 

214 if ( 

215 (regex := self.regex) is not None 

216 and (match := regex.search(file)) is not None 

217 ): 

218 return RegexMatchResult(match) 

219 

220 return None 

221 

222 @classmethod 

223 def pattern_to_regex( 

224 cls, 

225 pattern: AnyStr, 

226 ) -> tuple[Optional[AnyStr], Optional[bool]]: 

227 """ 

228 Convert the pattern into an uncompiled regular expression. 

229 

230 *pattern* (:class:`str` or :class:`bytes`) is the pattern to convert into a 

231 regular expression. 

232 

233 Returns a :class:`tuple` containing: 

234 

235 - *pattern* (:class:`str`, :class:`bytes` or :data:`None`) is the 

236 uncompiled regular expression. 

237 

238 - *include* (:class:`bool` or :data:`None`) is whether matched files 

239 should be included (:data:`True`), excluded (:data:`False`), or is a 

240 null-operation (:data:`None`). 

241 

242 .. note:: The default implementation simply returns *pattern* and 

243 :data:`True`. 

244 """ 

245 return (pattern, True) 

246 

247 

248@dataclass() 

249class RegexMatchResult(object): 

250 """ 

251 The :class:`RegexMatchResult` data class is used to return information about 

252 the matched regular expression. 

253 """ 

254 

255 # Keep the class dict-less. 

256 __slots__ = ( 

257 'match', 

258 ) 

259 

260 match: re.Match 

261 """ 

262 *match* (:class:`re.Match`) is the regex match result. 

263 """