diff mercurial/interfaces/matcher.py @ 52741:5c48fd4c0e68

typing: introduce a `types` module and a MatcherT alias This is a proposal to formalise the way we do typing and do more of it. The initial motivation to make progress is to help break the 100+ module cycle that is slowing pytype a lot.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 30 Jan 2025 18:22:01 +0100
parents
children aa948d9e3fee
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/interfaces/matcher.py	Thu Jan 30 18:22:01 2025 +0100
@@ -0,0 +1,142 @@
+# mercurial/interfaces/matcher - typing protocol for Matcher objects
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import annotations
+
+import abc
+
+from typing import (
+    Callable,
+    List,
+    Optional,
+    Protocol,
+    Set,
+    Union,
+)
+
+from ._basetypes import (
+    HgPathT,
+    UserMsgT,
+)
+
+
+class IMatcher(Protocol):
+    @abc.abstractmethod
+    def was_tampered_with_nonrec(self) -> bool:
+        ...
+
+    @abc.abstractmethod
+    def was_tampered_with(self) -> bool:
+        ...
+
+    @abc.abstractmethod
+    def __call__(self, fn: HgPathT) -> bool:
+        ...
+
+    # Callbacks related to how the matcher is used by dirstate.walk.
+    # Subscribers to these events must monkeypatch the matcher object.
+    @abc.abstractmethod
+    def bad(self, f: HgPathT, msg: Optional[UserMsgT]) -> None:
+        ...
+
+    # If traversedir is set, it will be called when a directory discovered
+    # by recursive traversal is visited.
+    traversedir: Optional[Callable[[HgPathT], None]] = None
+
+    @property
+    @abc.abstractmethod
+    def _files(self) -> List[HgPathT]:
+        ...
+
+    @abc.abstractmethod
+    def files(self) -> List[HgPathT]:
+        ...
+
+    @property
+    @abc.abstractmethod
+    def _fileset(self) -> Set[HgPathT]:
+        ...
+
+    @abc.abstractmethod
+    def exact(self, f: HgPathT) -> bool:
+        """Returns True if f is in .files()."""
+
+    @abc.abstractmethod
+    def matchfn(self, f: HgPathT) -> bool:
+        ...
+
+    @abc.abstractmethod
+    def visitdir(self, dir: HgPathT) -> Union[bool, bytes]:
+        """Decides whether a directory should be visited based on whether it
+        has potential matches in it or one of its subdirectories. This is
+        based on the match's primary, included, and excluded patterns.
+
+        Returns the string 'all' if the given directory and all subdirectories
+        should be visited. Otherwise returns True or False indicating whether
+        the given directory should be visited.
+        """
+
+    @abc.abstractmethod
+    def visitchildrenset(self, dir: HgPathT) -> Union[Set[HgPathT], bytes]:
+        """Decides whether a directory should be visited based on whether it
+        has potential matches in it or one of its subdirectories, and
+        potentially lists which subdirectories of that directory should be
+        visited. This is based on the match's primary, included, and excluded
+        patterns.
+
+        This function is very similar to 'visitdir', and the following mapping
+        can be applied:
+
+             visitdir | visitchildrenlist
+            ----------+-------------------
+             False    | set()
+             'all'    | 'all'
+             True     | 'this' OR non-empty set of subdirs -or files- to visit
+
+        Example:
+          Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
+          the following values (assuming the implementation of visitchildrenset
+          is capable of recognizing this; some implementations are not).
+
+          '' -> {'foo', 'qux'}
+          'baz' -> set()
+          'foo' -> {'bar'}
+          # Ideally this would be 'all', but since the prefix nature of matchers
+          # is applied to the entire matcher, we have to downgrade this to
+          # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
+          # in.
+          'foo/bar' -> 'this'
+          'qux' -> 'this'
+
+        Important:
+          Most matchers do not know if they're representing files or
+          directories. They see ['path:dir/f'] and don't know whether 'f' is a
+          file or a directory, so visitchildrenset('dir') for most matchers will
+          return {'f'}, but if the matcher knows it's a file (like exactmatcher
+          does), it may return 'this'. Do not rely on the return being a set
+          indicating that there are no files in this dir to investigate (or
+          equivalently that if there are files to investigate in 'dir' that it
+          will always return 'this').
+        """
+
+    @abc.abstractmethod
+    def always(self) -> bool:
+        """Matcher will match everything and .files() will be empty --
+        optimization might be possible."""
+
+    @abc.abstractmethod
+    def isexact(self) -> bool:
+        """Matcher will match exactly the list of files in .files() --
+        optimization might be possible."""
+
+    @abc.abstractmethod
+    def prefix(self) -> bool:
+        """Matcher will match the paths in .files() recursively --
+        optimization might be possible."""
+
+    @abc.abstractmethod
+    def anypats(self) -> bool:
+        """None of .always(), .isexact(), and .prefix() is true --
+        optimizations will be difficult."""