pathspec.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # encoding: utf-8
  2. """
  3. This module provides an object oriented interface for pattern matching
  4. of files.
  5. """
  6. from . import util
  7. from .compat import Collection, iterkeys, izip_longest, string_types, unicode
  8. class PathSpec(object):
  9. """
  10. The :class:`PathSpec` class is a wrapper around a list of compiled
  11. :class:`.Pattern` instances.
  12. """
  13. def __init__(self, patterns):
  14. """
  15. Initializes the :class:`PathSpec` instance.
  16. *patterns* (:class:`~collections.abc.Collection` or :class:`~collections.abc.Iterable`)
  17. yields each compiled pattern (:class:`.Pattern`).
  18. """
  19. self.patterns = patterns if isinstance(patterns, Collection) else list(patterns)
  20. """
  21. *patterns* (:class:`~collections.abc.Collection` of :class:`.Pattern`)
  22. contains the compiled patterns.
  23. """
  24. def __eq__(self, other):
  25. """
  26. Tests the equality of this path-spec with *other* (:class:`PathSpec`)
  27. by comparing their :attr:`~PathSpec.patterns` attributes.
  28. """
  29. if isinstance(other, PathSpec):
  30. paired_patterns = izip_longest(self.patterns, other.patterns)
  31. return all(a == b for a, b in paired_patterns)
  32. else:
  33. return NotImplemented
  34. def __len__(self):
  35. """
  36. Returns the number of compiled patterns this path-spec contains
  37. (:class:`int`).
  38. """
  39. return len(self.patterns)
  40. def __add__(self, other):
  41. """
  42. Combines the :attr:`Pathspec.patterns` patterns from two
  43. :class:`PathSpec` instances.
  44. """
  45. if isinstance(other, PathSpec):
  46. return PathSpec(self.patterns + other.patterns)
  47. else:
  48. return NotImplemented
  49. def __iadd__(self, other):
  50. """
  51. Adds the :attr:`Pathspec.patterns` patterns from one :class:`PathSpec`
  52. instance to this instance.
  53. """
  54. if isinstance(other, PathSpec):
  55. self.patterns += other.patterns
  56. return self
  57. else:
  58. return NotImplemented
  59. @classmethod
  60. def from_lines(cls, pattern_factory, lines):
  61. """
  62. Compiles the pattern lines.
  63. *pattern_factory* can be either the name of a registered pattern
  64. factory (:class:`str`), or a :class:`~collections.abc.Callable` used
  65. to compile patterns. It must accept an uncompiled pattern (:class:`str`)
  66. and return the compiled pattern (:class:`.Pattern`).
  67. *lines* (:class:`~collections.abc.Iterable`) yields each uncompiled
  68. pattern (:class:`str`). This simply has to yield each line so it can
  69. be a :class:`file` (e.g., from :func:`open` or :class:`io.StringIO`)
  70. or the result from :meth:`str.splitlines`.
  71. Returns the :class:`PathSpec` instance.
  72. """
  73. if isinstance(pattern_factory, string_types):
  74. pattern_factory = util.lookup_pattern(pattern_factory)
  75. if not callable(pattern_factory):
  76. raise TypeError("pattern_factory:{!r} is not callable.".format(pattern_factory))
  77. if not util._is_iterable(lines):
  78. raise TypeError("lines:{!r} is not an iterable.".format(lines))
  79. lines = [pattern_factory(line) for line in lines if line]
  80. return cls(lines)
  81. def match_file(self, file, separators=None):
  82. """
  83. Matches the file to this path-spec.
  84. *file* (:class:`str` or :class:`~pathlib.PurePath`) is the file path
  85. to be matched against :attr:`self.patterns <PathSpec.patterns>`.
  86. *separators* (:class:`~collections.abc.Collection` of :class:`str`)
  87. optionally contains the path separators to normalize. See
  88. :func:`~pathspec.util.normalize_file` for more information.
  89. Returns :data:`True` if *file* matched; otherwise, :data:`False`.
  90. """
  91. norm_file = util.normalize_file(file, separators=separators)
  92. return util.match_file(self.patterns, norm_file)
  93. def match_entries(self, entries, separators=None):
  94. """
  95. Matches the entries to this path-spec.
  96. *entries* (:class:`~collections.abc.Iterable` of :class:`~util.TreeEntry`)
  97. contains the entries to be matched against :attr:`self.patterns <PathSpec.patterns>`.
  98. *separators* (:class:`~collections.abc.Collection` of :class:`str`;
  99. or :data:`None`) optionally contains the path separators to
  100. normalize. See :func:`~pathspec.util.normalize_file` for more
  101. information.
  102. Returns the matched entries (:class:`~collections.abc.Iterable` of
  103. :class:`~util.TreeEntry`).
  104. """
  105. if not util._is_iterable(entries):
  106. raise TypeError("entries:{!r} is not an iterable.".format(entries))
  107. entry_map = util._normalize_entries(entries, separators=separators)
  108. match_paths = util.match_files(self.patterns, iterkeys(entry_map))
  109. for path in match_paths:
  110. yield entry_map[path]
  111. def match_files(self, files, separators=None):
  112. """
  113. Matches the files to this path-spec.
  114. *files* (:class:`~collections.abc.Iterable` of :class:`str; or
  115. :class:`pathlib.PurePath`) contains the file paths to be matched
  116. against :attr:`self.patterns <PathSpec.patterns>`.
  117. *separators* (:class:`~collections.abc.Collection` of :class:`str`;
  118. or :data:`None`) optionally contains the path separators to
  119. normalize. See :func:`~pathspec.util.normalize_file` for more
  120. information.
  121. Returns the matched files (:class:`~collections.abc.Iterable` of
  122. :class:`str`).
  123. """
  124. if not util._is_iterable(files):
  125. raise TypeError("files:{!r} is not an iterable.".format(files))
  126. file_map = util.normalize_files(files, separators=separators)
  127. matched_files = util.match_files(self.patterns, iterkeys(file_map))
  128. for path in matched_files:
  129. yield file_map[path]
  130. def match_tree_entries(self, root, on_error=None, follow_links=None):
  131. """
  132. Walks the specified root path for all files and matches them to this
  133. path-spec.
  134. *root* (:class:`str`; or :class:`pathlib.PurePath`) is the root
  135. directory to search.
  136. *on_error* (:class:`~collections.abc.Callable` or :data:`None`)
  137. optionally is the error handler for file-system exceptions. See
  138. :func:`~pathspec.util.iter_tree_entries` for more information.
  139. *follow_links* (:class:`bool` or :data:`None`) optionally is whether
  140. to walk symbolic links that resolve to directories. See
  141. :func:`~pathspec.util.iter_tree_files` for more information.
  142. Returns the matched files (:class:`~collections.abc.Iterable` of
  143. :class:`str`).
  144. """
  145. entries = util.iter_tree_entries(root, on_error=on_error, follow_links=follow_links)
  146. return self.match_entries(entries)
  147. def match_tree_files(self, root, on_error=None, follow_links=None):
  148. """
  149. Walks the specified root path for all files and matches them to this
  150. path-spec.
  151. *root* (:class:`str`; or :class:`pathlib.PurePath`) is the root
  152. directory to search for files.
  153. *on_error* (:class:`~collections.abc.Callable` or :data:`None`)
  154. optionally is the error handler for file-system exceptions. See
  155. :func:`~pathspec.util.iter_tree_files` for more information.
  156. *follow_links* (:class:`bool` or :data:`None`) optionally is whether
  157. to walk symbolic links that resolve to directories. See
  158. :func:`~pathspec.util.iter_tree_files` for more information.
  159. Returns the matched files (:class:`~collections.abc.Iterable` of
  160. :class:`str`).
  161. """
  162. files = util.iter_tree_files(root, on_error=on_error, follow_links=follow_links)
  163. return self.match_files(files)
  164. # Alias `match_tree_files()` as `match_tree()`.
  165. match_tree = match_tree_files