aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--edify/builder/mixins/matcher.py10
-rw-r--r--tests/builder/matcher.test.py33
2 files changed, 41 insertions, 2 deletions
diff --git a/edify/builder/mixins/matcher.py b/edify/builder/mixins/matcher.py
index 653ea31..b4106af 100644
--- a/edify/builder/mixins/matcher.py
+++ b/edify/builder/mixins/matcher.py
@@ -5,7 +5,9 @@ to the returned :class:`re.Pattern`. Adding this mixin to a class lets users
write ``pattern.match("10001")`` instead of ``pattern.to_regex().match("10001")``.
The signatures mirror :class:`re.Pattern` exactly so IDE autocomplete and
-type inference stay unchanged.
+type inference stay unchanged. :meth:`test` is the one non-:mod:`re` method:
+a boolean shortcut that returns ``True`` when the pattern matches anywhere
+in the input, ``False`` otherwise.
"""
from __future__ import annotations
@@ -18,7 +20,11 @@ from edify.builder.types.protocol import BuilderProtocol
class MatcherMixin(BuilderProtocol):
- """Provides eight :class:`re.Pattern` proxy methods on any fluent surface."""
+ """Provides nine :class:`re.Pattern` proxy methods plus :meth:`test` on any fluent surface."""
+
+ def test(self, string: str, pos: int = 0, endpos: int = sys.maxsize) -> bool:
+ """Return ``True`` when the pattern matches anywhere in ``string``, else ``False``."""
+ return self.to_regex().search(string, pos, endpos) is not None
def match(self, string: str, pos: int = 0, endpos: int = sys.maxsize) -> re.Match[str] | None:
"""Delegate to :meth:`re.Pattern.match`."""
diff --git a/tests/builder/matcher.test.py b/tests/builder/matcher.test.py
index e200d90..05a763a 100644
--- a/tests/builder/matcher.test.py
+++ b/tests/builder/matcher.test.py
@@ -80,3 +80,36 @@ def test_pattern_and_builder_yield_identical_results_for_same_regex():
def test_pattern_match_falls_back_when_only_pattern_class_used():
fluent = Pattern().start_of_input().exactly(5).digit().end_of_input()
assert fluent.match("10001") is not None
+
+
+def test_test_returns_true_when_pattern_matches_anywhere():
+ lower = one_or_more(range_of("a", "z"))
+ assert lower.test("HELLO world") is True
+
+
+def test_test_returns_false_when_pattern_does_not_match():
+ lower = one_or_more(range_of("a", "z"))
+ assert lower.test("12345") is False
+
+
+def test_test_respects_anchors():
+ zip_code = START + exactly(5, DIGIT) + END
+ assert zip_code.test("10001") is True
+ assert zip_code.test("foo 10001 bar") is False
+
+
+def test_test_returns_a_bool_not_a_match_object():
+ lower = one_or_more(range_of("a", "z"))
+ assert isinstance(lower.test("hello"), bool)
+
+
+def test_test_respects_the_pos_argument():
+ lower = one_or_more(range_of("a", "z"))
+ assert lower.test("!hello", pos=1) is True
+ assert lower.test("!hello", pos=6) is False
+
+
+def test_test_on_regex_builder_works():
+ lower = RegexBuilder().one_or_more().range("a", "z")
+ assert lower.test("hello") is True
+ assert lower.test("12345") is False