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

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 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. 

17try: 

18 from typing import Self as RegexPatternSelf # Added in 3.11. 

19except ImportError: 

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

21 

22from ._typing import ( 

23 AnyStr, # Removed in 3.18. 

24 deprecated, # Added in 3.13. 

25 override) # Added in 3.12. 

26 

27 

28class Pattern(object): 

29 """ 

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

31 """ 

32 

33 # Make the class dict-less. 

34 __slots__ = ( 

35 'include', 

36 ) 

37 

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

39 """ 

40 Initializes the :class:`Pattern` instance. 

41 

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

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

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

45 """ 

46 

47 self.include = include 

48 """ 

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

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

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

52 """ 

53 

54 @deprecated(( 

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

56 "similar results." 

57 )) 

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

59 """ 

60 .. version-deprecated:: 0.10.0 

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

62 method with a loop for similar results. 

63 

64 Matches this pattern against the specified files. 

65 

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

67 file relative to the root directory. 

68 

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

70 path (:class:`str`). 

71 """ 

72 for file in files: 

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

74 yield file 

75 

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

77 """ 

78 Matches this pattern against the specified file. 

79 

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

81 

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

83 """ 

84 raise NotImplementedError(( 

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

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

87 

88 

89class RegexPattern(Pattern): 

90 """ 

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

92 regular expressions. 

93 """ 

94 

95 # Keep the class dict-less. 

96 __slots__ = ( 

97 'pattern', 

98 'regex', 

99 ) 

100 

101 def __init__( 

102 self, 

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

104 include: Optional[bool] = None, 

105 ) -> None: 

106 """ 

107 Initializes the :class:`RegexPattern` instance. 

108 

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

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

111 

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

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

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

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

116 

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

118 """ 

119 

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

121 assert include is None, ( 

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

123 ) 

124 regex, include = self.pattern_to_regex(pattern) 

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

126 # returned for a null-operation. 

127 if include is not None: 

128 regex = re.compile(regex) 

129 

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

131 # Assume pattern is a precompiled regular expression. 

132 # - NOTE: Used specified *include*. 

133 regex = pattern 

134 

135 elif pattern is None: 

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

137 # null-operation. 

138 assert include is None, ( 

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

140 ) 

141 regex = None 

142 

143 else: 

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

145 

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

147 

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

149 """ 

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

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

152 """ 

153 

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

155 """ 

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

157 expression for the pattern. 

158 """ 

159 

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

161 """ 

162 Performa a shallow copy of the pattern. 

163 

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

165 """ 

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

167 other.pattern = self.pattern 

168 return other 

169 

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

171 """ 

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

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

174 attributes. 

175 """ 

176 if isinstance(other, RegexPattern): 

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

178 else: 

179 return NotImplemented 

180 

181 @override 

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

183 """ 

184 Matches this pattern against the specified file. 

185 

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

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

188 

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

190 otherwise, :data:`None`. 

191 """ 

192 if self.include is not None: 

193 match = self.regex.search(file) 

194 if match is not None: 

195 return RegexMatchResult(match) 

196 

197 return None 

198 

199 @classmethod 

200 def pattern_to_regex( 

201 cls, 

202 pattern: AnyStr, 

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

204 """ 

205 Convert the pattern into an uncompiled regular expression. 

206 

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

208 regular expression. 

209 

210 Returns a :class:`tuple` containing: 

211 

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

213 uncompiled regular expression . 

214 

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

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

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

218 

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

220 :data:`True`. 

221 """ 

222 return pattern, True 

223 

224 

225@dataclass() 

226class RegexMatchResult(object): 

227 """ 

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

229 the matched regular expression. 

230 """ 

231 

232 # Keep the class dict-less. 

233 __slots__ = ( 

234 'match', 

235 ) 

236 

237 match: re.Match 

238 """ 

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

240 """