1""":module: watchdog.utils.patterns 2:synopsis: Common wildcard searching/filtering functionality for files. 3:author: [email protected] (Boris Staletic) 4:author: [email protected] (Yesudeep Mangalapilly) 5:author: [email protected] (Mickaël Schoentgen) 6""" 7 8from __future__ import annotations 9 10# Non-pure path objects are only allowed on their respective OS's. 11# Thus, these utilities require "pure" path objects that don't access the filesystem. 12# Since pathlib doesn't have a `case_sensitive` parameter, we have to approximate it 13# by converting input paths to `PureWindowsPath` and `PurePosixPath` where: 14# - `PureWindowsPath` is always case-insensitive. 15# - `PurePosixPath` is always case-sensitive. 16# Reference: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.match 17from pathlib import PurePosixPath, PureWindowsPath 18from typing import TYPE_CHECKING 19 20if TYPE_CHECKING: 21 from collections.abc import Iterator 22 23 24def _match_path( 25 raw_path: str, 26 included_patterns: set[str], 27 excluded_patterns: set[str], 28 *, 29 case_sensitive: bool, 30) -> bool: 31 """Internal function same as :func:`match_path` but does not check arguments.""" 32 path: PurePosixPath | PureWindowsPath 33 if case_sensitive: 34 path = PurePosixPath(raw_path) 35 else: 36 included_patterns = {pattern.lower() for pattern in included_patterns} 37 excluded_patterns = {pattern.lower() for pattern in excluded_patterns} 38 path = PureWindowsPath(raw_path) 39 40 common_patterns = included_patterns & excluded_patterns 41 if common_patterns: 42 error = f"conflicting patterns `{common_patterns}` included and excluded" 43 raise ValueError(error) 44 45 return any(path.match(p) for p in included_patterns) and not any(path.match(p) for p in excluded_patterns) 46 47 48def filter_paths( 49 paths: list[str], 50 *, 51 included_patterns: list[str] | None = None, 52 excluded_patterns: list[str] | None = None, 53 case_sensitive: bool = True, 54) -> Iterator[str]: 55 """Filters from a set of paths based on acceptable patterns and 56 ignorable patterns. 57 :param paths: 58 A list of path names that will be filtered based on matching and 59 ignored patterns. 60 :param included_patterns: 61 Allow filenames matching wildcard patterns specified in this list. 62 If no pattern list is specified, ["*"] is used as the default pattern, 63 which matches all files. 64 :param excluded_patterns: 65 Ignores filenames matching wildcard patterns specified in this list. 66 If no pattern list is specified, no files are ignored. 67 :param case_sensitive: 68 ``True`` if matching should be case-sensitive; ``False`` otherwise. 69 :returns: 70 A list of pathnames that matched the allowable patterns and passed 71 through the ignored patterns. 72 """ 73 included = set(["*"] if included_patterns is None else included_patterns) 74 excluded = set([] if excluded_patterns is None else excluded_patterns) 75 76 for path in paths: 77 if _match_path(path, included, excluded, case_sensitive=case_sensitive): 78 yield path 79 80 81def match_any_paths( 82 paths: list[str], 83 *, 84 included_patterns: list[str] | None = None, 85 excluded_patterns: list[str] | None = None, 86 case_sensitive: bool = True, 87) -> bool: 88 """Matches from a set of paths based on acceptable patterns and 89 ignorable patterns. 90 See ``filter_paths()`` for signature details. 91 """ 92 return any( 93 filter_paths( 94 paths, 95 included_patterns=included_patterns, 96 excluded_patterns=excluded_patterns, 97 case_sensitive=case_sensitive, 98 ), 99 ) 100