Mercurial > public > mercurial-scm > hg
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."""