gitignore.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. """
  2. This module provides :class:`.GitIgnoreSpec` which replicates
  3. *.gitignore* behavior.
  4. """
  5. from typing import (
  6. AnyStr,
  7. Callable, # Replaced by `collections.abc.Callable` in 3.9.
  8. Iterable, # Replaced by `collections.abc.Iterable` in 3.9.
  9. Optional, # Replaced by `X | None` in 3.10.
  10. Tuple, # Replaced by `tuple` in 3.9.
  11. Type, # Replaced by `type` in 3.9.
  12. TypeVar,
  13. Union, # Replaced by `X | Y` in 3.10.
  14. cast,
  15. overload)
  16. from .pathspec import (
  17. PathSpec)
  18. from .pattern import (
  19. Pattern)
  20. from .patterns.gitwildmatch import (
  21. GitWildMatchPattern,
  22. _DIR_MARK)
  23. from .util import (
  24. _is_iterable)
  25. Self = TypeVar("Self", bound="GitIgnoreSpec")
  26. """
  27. :class:`GitIgnoreSpec` self type hint to support Python v<3.11 using PEP
  28. 673 recommendation.
  29. """
  30. class GitIgnoreSpec(PathSpec):
  31. """
  32. The :class:`GitIgnoreSpec` class extends :class:`pathspec.pathspec.PathSpec` to
  33. replicate *.gitignore* behavior.
  34. """
  35. def __eq__(self, other: object) -> bool:
  36. """
  37. Tests the equality of this gitignore-spec with *other* (:class:`GitIgnoreSpec`)
  38. by comparing their :attr:`~pathspec.pattern.Pattern`
  39. attributes. A non-:class:`GitIgnoreSpec` will not compare equal.
  40. """
  41. if isinstance(other, GitIgnoreSpec):
  42. return super().__eq__(other)
  43. elif isinstance(other, PathSpec):
  44. return False
  45. else:
  46. return NotImplemented
  47. # Support reversed order of arguments from PathSpec.
  48. @overload
  49. @classmethod
  50. def from_lines(
  51. cls: Type[Self],
  52. pattern_factory: Union[str, Callable[[AnyStr], Pattern]],
  53. lines: Iterable[AnyStr],
  54. ) -> Self:
  55. ...
  56. @overload
  57. @classmethod
  58. def from_lines(
  59. cls: Type[Self],
  60. lines: Iterable[AnyStr],
  61. pattern_factory: Union[str, Callable[[AnyStr], Pattern], None] = None,
  62. ) -> Self:
  63. ...
  64. @classmethod
  65. def from_lines(
  66. cls: Type[Self],
  67. lines: Iterable[AnyStr],
  68. pattern_factory: Union[str, Callable[[AnyStr], Pattern], None] = None,
  69. ) -> Self:
  70. """
  71. Compiles the pattern lines.
  72. *lines* (:class:`~collections.abc.Iterable`) yields each uncompiled
  73. pattern (:class:`str`). This simply has to yield each line so it can
  74. be a :class:`io.TextIOBase` (e.g., from :func:`open` or
  75. :class:`io.StringIO`) or the result from :meth:`str.splitlines`.
  76. *pattern_factory* can be :data:`None`, the name of a registered
  77. pattern factory (:class:`str`), or a :class:`~collections.abc.Callable`
  78. used to compile patterns. The callable must accept an uncompiled
  79. pattern (:class:`str`) and return the compiled pattern
  80. (:class:`pathspec.pattern.Pattern`).
  81. Default is :data:`None` for :class:`.GitWildMatchPattern`).
  82. Returns the :class:`GitIgnoreSpec` instance.
  83. """
  84. if pattern_factory is None:
  85. pattern_factory = GitWildMatchPattern
  86. elif (isinstance(lines, (str, bytes)) or callable(lines)) and _is_iterable(pattern_factory):
  87. # Support reversed order of arguments from PathSpec.
  88. pattern_factory, lines = lines, pattern_factory
  89. self = super().from_lines(pattern_factory, lines)
  90. return cast(Self, self)
  91. @staticmethod
  92. def _match_file(
  93. patterns: Iterable[Tuple[int, GitWildMatchPattern]],
  94. file: str,
  95. ) -> Tuple[Optional[bool], Optional[int]]:
  96. """
  97. Check the file against the patterns.
  98. .. NOTE:: Subclasses of :class:`~pathspec.pathspec.PathSpec` may override
  99. this method as an instance method. It does not have to be a static
  100. method. The signature for this method is subject to change.
  101. *patterns* (:class:`~collections.abc.Iterable`) yields each indexed pattern
  102. (:class:`tuple`) which contains the pattern index (:class:`int`) and actual
  103. pattern (:class:`~pathspec.pattern.Pattern`).
  104. *file* (:class:`str`) is the normalized file path to be matched against
  105. *patterns*.
  106. Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
  107. or :data:`None`), and the index of the last matched pattern (:class:`int` or
  108. :data:`None`).
  109. """
  110. out_include: Optional[bool] = None
  111. out_index: Optional[int] = None
  112. out_priority = 0
  113. for index, pattern in patterns:
  114. if pattern.include is not None:
  115. match = pattern.match_file(file)
  116. if match is not None:
  117. # Pattern matched.
  118. # Check for directory marker.
  119. dir_mark = match.match.groupdict().get(_DIR_MARK)
  120. if dir_mark:
  121. # Pattern matched by a directory pattern.
  122. priority = 1
  123. else:
  124. # Pattern matched by a file pattern.
  125. priority = 2
  126. if pattern.include and dir_mark:
  127. out_include = pattern.include
  128. out_index = index
  129. out_priority = priority
  130. elif priority >= out_priority:
  131. out_include = pattern.include
  132. out_index = index
  133. out_priority = priority
  134. return out_include, out_index