1# encoding: utf-8 2""" 3This module provides an object oriented interface for pattern matching 4of files. 5""" 6 7try: 8 from typing import ( 9 Any, 10 AnyStr, 11 Callable, 12 Iterable, 13 Iterator, 14 Optional, 15 Text, 16 Union) 17except ImportError: 18 pass 19 20try: 21 # Python 3.6+ type hints. 22 from os import PathLike 23 from typing import Collection 24except ImportError: 25 pass 26 27from . import util 28from .compat import ( 29 CollectionType, 30 iterkeys, 31 izip_longest, 32 string_types) 33from .pattern import Pattern 34from .util import TreeEntry 35 36 37class PathSpec(object): 38 """ 39 The :class:`PathSpec` class is a wrapper around a list of compiled 40 :class:`.Pattern` instances. 41 """ 42 43 def __init__(self, patterns): 44 # type: (Iterable[Pattern]) -> None 45 """ 46 Initializes the :class:`PathSpec` instance. 47 48 *patterns* (:class:`~collections.abc.Collection` or :class:`~collections.abc.Iterable`) 49 yields each compiled pattern (:class:`.Pattern`). 50 """ 51 52 self.patterns = patterns if isinstance(patterns, CollectionType) else list(patterns) 53 """ 54 *patterns* (:class:`~collections.abc.Collection` of :class:`.Pattern`) 55 contains the compiled patterns. 56 """ 57 58 def __eq__(self, other): 59 # type: (PathSpec) -> bool 60 """ 61 Tests the equality of this path-spec with *other* (:class:`PathSpec`) 62 by comparing their :attr:`~PathSpec.patterns` attributes. 63 """ 64 if isinstance(other, PathSpec): 65 paired_patterns = izip_longest(self.patterns, other.patterns) 66 return all(a == b for a, b in paired_patterns) 67 else: 68 return NotImplemented 69 70 def __len__(self): 71 """ 72 Returns the number of compiled patterns this path-spec contains 73 (:class:`int`). 74 """ 75 return len(self.patterns) 76 77 def __add__(self, other): 78 # type: (PathSpec) -> PathSpec 79 """ 80 Combines the :attr:`Pathspec.patterns` patterns from two 81 :class:`PathSpec` instances. 82 """ 83 if isinstance(other, PathSpec): 84 return PathSpec(self.patterns + other.patterns) 85 else: 86 return NotImplemented 87 88 def __iadd__(self, other): 89 # type: (PathSpec) -> PathSpec 90 """ 91 Adds the :attr:`Pathspec.patterns` patterns from one :class:`PathSpec` 92 instance to this instance. 93 """ 94 if isinstance(other, PathSpec): 95 self.patterns += other.patterns 96 return self 97 else: 98 return NotImplemented 99 100 @classmethod 101 def from_lines(cls, pattern_factory, lines): 102 # type: (Union[Text, Callable[[AnyStr], Pattern]], Iterable[AnyStr]) -> PathSpec 103 """ 104 Compiles the pattern lines. 105 106 *pattern_factory* can be either the name of a registered pattern 107 factory (:class:`str`), or a :class:`~collections.abc.Callable` used 108 to compile patterns. It must accept an uncompiled pattern (:class:`str`) 109 and return the compiled pattern (:class:`.Pattern`). 110 111 *lines* (:class:`~collections.abc.Iterable`) yields each uncompiled 112 pattern (:class:`str`). This simply has to yield each line so it can 113 be a :class:`file` (e.g., from :func:`open` or :class:`io.StringIO`) 114 or the result from :meth:`str.splitlines`. 115 116 Returns the :class:`PathSpec` instance. 117 """ 118 if isinstance(pattern_factory, string_types): 119 pattern_factory = util.lookup_pattern(pattern_factory) 120 if not callable(pattern_factory): 121 raise TypeError("pattern_factory:{!r} is not callable.".format(pattern_factory)) 122 123 if not util._is_iterable(lines): 124 raise TypeError("lines:{!r} is not an iterable.".format(lines)) 125 126 patterns = [pattern_factory(line) for line in lines if line] 127 return cls(patterns) 128 129 def match_file(self, file, separators=None): 130 # type: (Union[Text, PathLike], Optional[Collection[Text]]) -> bool 131 """ 132 Matches the file to this path-spec. 133 134 *file* (:class:`str` or :class:`~pathlib.PurePath`) is the file path 135 to be matched against :attr:`self.patterns <PathSpec.patterns>`. 136 137 *separators* (:class:`~collections.abc.Collection` of :class:`str`) 138 optionally contains the path separators to normalize. See 139 :func:`~pathspec.util.normalize_file` for more information. 140 141 Returns :data:`True` if *file* matched; otherwise, :data:`False`. 142 """ 143 norm_file = util.normalize_file(file, separators=separators) 144 return util.match_file(self.patterns, norm_file) 145 146 def match_entries(self, entries, separators=None): 147 # type: (Iterable[TreeEntry], Optional[Collection[Text]]) -> Iterator[TreeEntry] 148 """ 149 Matches the entries to this path-spec. 150 151 *entries* (:class:`~collections.abc.Iterable` of :class:`~util.TreeEntry`) 152 contains the entries to be matched against :attr:`self.patterns <PathSpec.patterns>`. 153 154 *separators* (:class:`~collections.abc.Collection` of :class:`str`; 155 or :data:`None`) optionally contains the path separators to 156 normalize. See :func:`~pathspec.util.normalize_file` for more 157 information. 158 159 Returns the matched entries (:class:`~collections.abc.Iterator` of 160 :class:`~util.TreeEntry`). 161 """ 162 if not util._is_iterable(entries): 163 raise TypeError("entries:{!r} is not an iterable.".format(entries)) 164 165 entry_map = util._normalize_entries(entries, separators=separators) 166 match_paths = util.match_files(self.patterns, iterkeys(entry_map)) 167 for path in match_paths: 168 yield entry_map[path] 169 170 def match_files(self, files, separators=None): 171 # type: (Iterable[Union[Text, PathLike]], Optional[Collection[Text]]) -> Iterator[Union[Text, PathLike]] 172 """ 173 Matches the files to this path-spec. 174 175 *files* (:class:`~collections.abc.Iterable` of :class:`str; or 176 :class:`pathlib.PurePath`) contains the file paths to be matched 177 against :attr:`self.patterns <PathSpec.patterns>`. 178 179 *separators* (:class:`~collections.abc.Collection` of :class:`str`; 180 or :data:`None`) optionally contains the path separators to 181 normalize. See :func:`~pathspec.util.normalize_file` for more 182 information. 183 184 Returns the matched files (:class:`~collections.abc.Iterator` of 185 :class:`str` or :class:`pathlib.PurePath`). 186 """ 187 if not util._is_iterable(files): 188 raise TypeError("files:{!r} is not an iterable.".format(files)) 189 190 file_map = util.normalize_files(files, separators=separators) 191 matched_files = util.match_files(self.patterns, iterkeys(file_map)) 192 for norm_file in matched_files: 193 for orig_file in file_map[norm_file]: 194 yield orig_file 195 196 def match_tree_entries(self, root, on_error=None, follow_links=None): 197 # type: (Text, Optional[Callable], Optional[bool]) -> Iterator[TreeEntry] 198 """ 199 Walks the specified root path for all files and matches them to this 200 path-spec. 201 202 *root* (:class:`str`; or :class:`pathlib.PurePath`) is the root 203 directory to search. 204 205 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) 206 optionally is the error handler for file-system exceptions. See 207 :func:`~pathspec.util.iter_tree_entries` for more information. 208 209 *follow_links* (:class:`bool` or :data:`None`) optionally is whether 210 to walk symbolic links that resolve to directories. See 211 :func:`~pathspec.util.iter_tree_files` for more information. 212 213 Returns the matched files (:class:`~collections.abc.Iterator` of 214 :class:`.TreeEntry`). 215 """ 216 entries = util.iter_tree_entries(root, on_error=on_error, follow_links=follow_links) 217 return self.match_entries(entries) 218 219 def match_tree_files(self, root, on_error=None, follow_links=None): 220 # type: (Text, Optional[Callable], Optional[bool]) -> Iterator[Text] 221 """ 222 Walks the specified root path for all files and matches them to this 223 path-spec. 224 225 *root* (:class:`str`; or :class:`pathlib.PurePath`) is the root 226 directory to search for files. 227 228 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) 229 optionally is the error handler for file-system exceptions. See 230 :func:`~pathspec.util.iter_tree_files` for more information. 231 232 *follow_links* (:class:`bool` or :data:`None`) optionally is whether 233 to walk symbolic links that resolve to directories. See 234 :func:`~pathspec.util.iter_tree_files` for more information. 235 236 Returns the matched files (:class:`~collections.abc.Iterable` of 237 :class:`str`). 238 """ 239 files = util.iter_tree_files(root, on_error=on_error, follow_links=follow_links) 240 return self.match_files(files) 241 242 # Alias `match_tree_files()` as `match_tree()`. 243 match_tree = match_tree_files 244