55
66from __future__ import annotations
77
8+ import abc
89import hashlib
910import ntpath
1011import os
1617
1718from coverage import env
1819from coverage .exceptions import ConfigError
19- from coverage .misc import human_sorted , isolate_module , join_regex
20- from coverage .types import TMatcher
20+ from coverage .misc import human_sorted , isolate_module , join_regex , plural
2121
2222os = isolate_module (os )
2323
@@ -215,26 +215,59 @@ def prep_patterns(patterns: Iterable[str]) -> list[str]:
215215 return prepped
216216
217217
218- class TreeMatcher (TMatcher ):
219- """A matcher for files in a tree.
218+ DebugFn = Callable [[str ], None ] | None
220219
221- Construct with a list of paths, either files or directories. Paths match
222- with the `match` method if they are one of the files, or if they are
223- somewhere in a subtree rooted at one of the directories.
224220
225- """
221+ class Matcher (abc .ABC ):
222+ """Common behavior for matchers."""
226223
227- def __init__ (self , paths : Iterable [str ], name : str = "unknown" ) -> None :
228- self .original_paths : list [str ] = human_sorted (paths )
229- self .paths = [os .path .normcase (p ) for p in paths ]
224+ def __init__ (self , strs : list [str ], name : str , caption : str , debug : DebugFn ) -> None :
225+ self .strs = strs
230226 self .name = name
227+ if debug :
228+ debug (f"{ caption } matching { self } " )
229+ for inf in self .info ():
230+ debug (f" { inf } " )
231+
232+ def __str__ (self ) -> str :
233+ n = len (self .strs )
234+ return f"{ self .__class__ .__name__ } { self .name !r} { n } { plural (n , 'item' )} "
231235
232236 def __repr__ (self ) -> str :
233- return f"<TreeMatcher { self .name } { self .original_paths !r} >"
237+ return f"<{ self .__class__ .__name__ } { self .name } { self .strs !r} >"
238+
239+ @abc .abstractmethod
240+ def match (self , s : str ) -> bool :
241+ """Does this string match?"""
234242
235243 def info (self ) -> list [str ]:
236244 """A list of strings for displaying when dumping state."""
237- return self .original_paths
245+ return self .strs
246+
247+
248+ class TreeMatcher (Matcher ):
249+ """A matcher for files in a tree.
250+
251+ Construct with a list of paths, either files or directories. Paths match
252+ with the `match` method if they are one of the files, or if they are
253+ somewhere in a subtree rooted at one of the directories.
254+ """
255+
256+ def __init__ (
257+ self ,
258+ paths : Iterable [str ],
259+ name : str = "unknown" ,
260+ caption : str = "" ,
261+ debug : DebugFn = None ,
262+ ) -> None :
263+ self .original_paths = human_sorted (paths )
264+ super ().__init__ (self .original_paths , name = name , caption = caption , debug = debug )
265+ self .paths = []
266+ for p in paths :
267+ ap = os .path .normcase (p )
268+ if ap != p and debug :
269+ debug (f" Normalized { p !r} to { ap !r} " )
270+ self .paths .append (ap )
238271
239272 def match (self , fpath : str ) -> bool : # pylint: disable=arguments-renamed
240273 """Does `fpath` indicate a file in one of our trees?"""
@@ -250,19 +283,18 @@ def match(self, fpath: str) -> bool: # pylint: disable=arguments-renamed
250283 return False
251284
252285
253- class ModuleMatcher (TMatcher ):
286+ class ModuleMatcher (Matcher ):
254287 """A matcher for modules in a tree."""
255288
256- def __init__ (self , module_names : Iterable [str ], name : str = "unknown" ) -> None :
289+ def __init__ (
290+ self ,
291+ module_names : Iterable [str ],
292+ name : str = "unknown" ,
293+ caption : str = "" ,
294+ debug : DebugFn = None ,
295+ ) -> None :
257296 self .modules = list (module_names )
258- self .name = name
259-
260- def __repr__ (self ) -> str :
261- return f"<ModuleMatcher { self .name } { self .modules !r} >"
262-
263- def info (self ) -> list [str ]:
264- """A list of strings for displaying when dumping state."""
265- return self .modules
297+ super ().__init__ (self .modules , name = name , caption = caption , debug = debug )
266298
267299 def match (self , module_name : str ) -> bool : # pylint: disable=arguments-renamed
268300 """Does `module_name` indicate a module in one of our packages?"""
@@ -280,20 +312,19 @@ def match(self, module_name: str) -> bool: # pylint: disable=arguments-renamed
280312 return False
281313
282314
283- class GlobMatcher (TMatcher ):
315+ class GlobMatcher (Matcher ):
284316 """A matcher for files by file name pattern."""
285317
286- def __init__ (self , pats : Iterable [str ], name : str = "unknown" ) -> None :
318+ def __init__ (
319+ self ,
320+ pats : Iterable [str ],
321+ name : str = "unknown" ,
322+ caption : str = "" ,
323+ debug : DebugFn = None ,
324+ ) -> None :
287325 self .pats = list (pats )
326+ super ().__init__ (self .pats , name = name , caption = caption , debug = debug )
288327 self .re = globs_to_regex (self .pats , case_insensitive = env .WINDOWS )
289- self .name = name
290-
291- def __repr__ (self ) -> str :
292- return f"<GlobMatcher { self .name } { self .pats !r} >"
293-
294- def info (self ) -> list [str ]:
295- """A list of strings for displaying when dumping state."""
296- return self .pats
297328
298329 def match (self , fpath : str ) -> bool : # pylint: disable=arguments-renamed
299330 """Does `fpath` match one of our file name patterns?"""
0 commit comments