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

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

63 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 

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

122 assert include is None, ( 

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

124 ) 

125 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 regex = re.compile(regex) 

130 

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

132 # Assume pattern is a precompiled regular expression. 

133 # - NOTE: Used specified *include*. 

134 regex = pattern 

135 

136 elif pattern is None: 

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

138 # null-operation. 

139 assert include is None, ( 

140 f"include:{include!r} must be null when pattern:{pattern!r} is null." 

141 ) 

142 regex = None 

143 

144 else: 

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

146 

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

148 

149 self.pattern: Union[AnyStr, re.Pattern, None] = pattern 

150 """ 

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

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

153 """ 

154 

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

156 """ 

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

158 expression for the pattern. 

159 """ 

160 

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

162 """ 

163 Performa a shallow copy of the pattern. 

164 

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

166 """ 

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

168 other.pattern = self.pattern 

169 return other 

170 

171 def __eq__(self, other: RegexPattern) -> bool: 

172 """ 

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

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

175 attributes. 

176 """ 

177 if isinstance(other, RegexPattern): 

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

179 else: 

180 return NotImplemented 

181 

182 @override 

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

184 """ 

185 Matches this pattern against the specified file. 

186 

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

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

189 

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

191 otherwise, :data:`None`. 

192 """ 

193 if self.include is not None: 

194 match = self.regex.search(file) 

195 if match is not None: 

196 return RegexMatchResult(match) 

197 

198 return None 

199 

200 @classmethod 

201 def pattern_to_regex( 

202 cls, 

203 pattern: AnyStr, 

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

205 """ 

206 Convert the pattern into an uncompiled regular expression. 

207 

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

209 regular expression. 

210 

211 Returns a :class:`tuple` containing: 

212 

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

214 uncompiled regular expression . 

215 

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

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

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

219 

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

221 :data:`True`. 

222 """ 

223 return pattern, True 

224 

225 

226@dataclass() 

227class RegexMatchResult(object): 

228 """ 

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

230 the matched regular expression. 

231 """ 

232 

233 # Keep the class dict-less. 

234 __slots__ = ( 

235 'match', 

236 ) 

237 

238 match: re.Match 

239 """ 

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

241 """