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 ) -> None: 

107 """ 

108 Initializes the :class:`RegexPattern` instance. 

109 

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

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

112 

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

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

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

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

117 

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

119 """ 

120 regex: Optional[re.Pattern] = None 

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

122 assert include is None, ( 

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

124 ) 

125 raw_regex, include = self.pattern_to_regex(pattern) 

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

127 # returned for a null-operation. 

128 if include is not None: 

129 assert raw_regex is not None, ( 

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

131 ) 

132 regex = re.compile(raw_regex) 

133 

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

135 # Assume pattern is a precompiled regular expression. 

136 # - NOTE: Use specified *include*. 

137 regex = pattern 

138 

139 elif pattern is None: 

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

141 # operation. 

142 assert include is None, ( 

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

144 ) 

145 

146 else: 

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

148 

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

150 

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

152 """ 

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

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

155 """ 

156 

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

158 """ 

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

160 expression for the pattern. 

161 """ 

162 

163 def __repr__(self) -> str: 

164 """ 

165 Returns a debug representation of this regex pattern. 

166 """ 

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

168 

169 def __str__(self) -> str: 

170 """ 

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

172 

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

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

175 """ 

176 return str(self.pattern or "") 

177 

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

179 """ 

180 Performa a shallow copy of the pattern. 

181 

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

183 """ 

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

185 other.pattern = self.pattern 

186 return other 

187 

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

189 """ 

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

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

192 attributes. 

193 """ 

194 if isinstance(other, RegexPattern): 

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

196 else: 

197 return NotImplemented 

198 

199 @override 

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

201 """ 

202 Matches this pattern against the specified file. 

203 

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

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

206 

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

208 otherwise, :data:`None`. 

209 """ 

210 if ( 

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

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

213 ): 

214 return RegexMatchResult(match) 

215 

216 return None 

217 

218 @classmethod 

219 def pattern_to_regex( 

220 cls, 

221 pattern: AnyStr, 

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

223 """ 

224 Convert the pattern into an uncompiled regular expression. 

225 

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

227 regular expression. 

228 

229 Returns a :class:`tuple` containing: 

230 

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

232 uncompiled regular expression . 

233 

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

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

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

237 

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

239 :data:`True`. 

240 """ 

241 return (pattern, True) 

242 

243 

244@dataclass() 

245class RegexMatchResult(object): 

246 """ 

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

248 the matched regular expression. 

249 """ 

250 

251 # Keep the class dict-less. 

252 __slots__ = ( 

253 'match', 

254 ) 

255 

256 match: re.Match 

257 """ 

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

259 """