aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
author夏音 / natsuoto.exe <[email protected]>2026-07-01 16:21:15 +0530
committerGitHub <[email protected]>2026-07-01 16:21:15 +0530
commite859ce24e5ffc95653023669f9edf4d3a0344a8e (patch)
treeea2d0971292c1a70737f1f068ec27d308974b88f /tests
parent42509f3a1d0c93c5fd43b2e2bdc42201d24a643e (diff)
parent1860f7396e997c785d2299c421852909a0ee2070 (diff)
downloadedify-e859ce24e5ffc95653023669f9edf4d3a0344a8e.tar.xz
edify-e859ce24e5ffc95653023669f9edf4d3a0344a8e.zip
feat: varargs .one_of() and .any_of(*literals) chain-level shorthand (#266)
Adds a varargs shorthand for literal alternation on both fluent surfaces (`RegexBuilder` and `Pattern`): - **`.any_of(*literals: str)`** — dual-mode overload. Zero args keeps the existing frame-opener behavior (`.any_of().string("cat").string("dog").end()`). One or more string args skips the frame dance and appends the alternation directly. - **`.one_of(*literals: str)`** — new method, always the varargs form. Requires at least one literal (raises `MustBeAtLeastOneLiteralError` on zero). Literals go through the same escape + `CharElement`/`StringElement` branching that `.char()` / `.string()` use, so the compile-path fusion into `[...]` for single-character sets still applies: ```python RegexBuilder().any_of("+", "-").to_regex_string() # [\+\-] RegexBuilder().one_of("cat", "dog", "fish").to_regex_string() # (?:cat|dog|fish) ``` The pre-existing top-level factory `any_of(*operands: Pattern)` from #263 stays untouched — it takes `Pattern` operands, not literal strings, and serves the compositional (humre-style) API rather than the chain-level shorthand this PR delivers. Closes #131
Diffstat (limited to 'tests')
-rw-r--r--tests/builder/varargs.test.py72
-rw-r--r--tests/errors/errors.test.py6
2 files changed, 78 insertions, 0 deletions
diff --git a/tests/builder/varargs.test.py b/tests/builder/varargs.test.py
new file mode 100644
index 0000000..6a30636
--- /dev/null
+++ b/tests/builder/varargs.test.py
@@ -0,0 +1,72 @@
+"""Tests for the varargs shorthand on :meth:`GroupsMixin.any_of` and :meth:`GroupsMixin.one_of`."""
+
+import pytest
+
+from edify import Pattern, RegexBuilder
+from edify.errors.input import (
+ MustBeAStringError,
+ MustBeAtLeastOneLiteralError,
+ MustBeOneCharacterError,
+)
+
+
+def test_any_of_no_args_still_opens_a_frame():
+ expr = RegexBuilder().any_of().string("cat").string("dog").end()
+ assert expr.to_regex_string() == "(?:cat|dog)"
+
+
+def test_any_of_varargs_fuses_single_character_literals_into_a_char_class():
+ expr = RegexBuilder().any_of("+", "-")
+ assert expr.to_regex_string() == "[\\+\\-]"
+
+
+def test_any_of_varargs_uses_alternation_for_multi_character_literals():
+ expr = RegexBuilder().any_of("http", "https")
+ assert expr.to_regex_string() == "(?:http|https)"
+
+
+def test_any_of_varargs_returns_a_builder_of_the_same_type():
+ expr = RegexBuilder().any_of("a", "b")
+ assert isinstance(expr, RegexBuilder)
+
+
+def test_any_of_varargs_leaves_the_original_builder_untouched():
+ original = RegexBuilder().digit()
+ _ = original.any_of("a", "b")
+ assert original.to_regex_string() == "\\d"
+
+
+def test_any_of_varargs_on_pattern_returns_a_pattern():
+ pattern = Pattern().any_of("cat", "dog")
+ assert isinstance(pattern, Pattern)
+ assert pattern.to_regex_string() == "(?:cat|dog)"
+
+
+def test_one_of_fuses_single_character_literals_into_a_char_class():
+ expr = RegexBuilder().one_of("+", "-")
+ assert expr.to_regex_string() == "[\\+\\-]"
+
+
+def test_one_of_uses_alternation_for_multi_character_literals():
+ expr = RegexBuilder().one_of("cat", "dog", "fish")
+ assert expr.to_regex_string() == "(?:cat|dog|fish)"
+
+
+def test_one_of_accepts_a_single_literal_and_wraps_it():
+ expr = RegexBuilder().one_of("cat")
+ assert expr.to_regex_string() == "(?:cat)"
+
+
+def test_one_of_zero_args_raises_must_be_at_least_one_literal():
+ with pytest.raises(MustBeAtLeastOneLiteralError):
+ RegexBuilder().one_of()
+
+
+def test_any_of_varargs_rejects_empty_string_literal():
+ with pytest.raises(MustBeOneCharacterError):
+ RegexBuilder().any_of("cat", "")
+
+
+def test_one_of_rejects_non_string_literal():
+ with pytest.raises(MustBeAStringError):
+ RegexBuilder().one_of("cat", 42)
diff --git a/tests/errors/errors.test.py b/tests/errors/errors.test.py
index 0f39228..6472469 100644
--- a/tests/errors/errors.test.py
+++ b/tests/errors/errors.test.py
@@ -6,6 +6,7 @@ from edify.errors.anchors import (
from edify.errors.captures import InvalidTotalCaptureGroupsIndexError
from edify.errors.input import (
MustBeAStringError,
+ MustBeAtLeastOneLiteralError,
MustBeAtLeastTwoOperandsError,
MustBeInstanceError,
MustBeIntegerGreaterThanZeroError,
@@ -113,6 +114,11 @@ def test_must_be_at_least_two_operands():
assert "any_of requires at least two operands" in str(error)
+def test_must_be_at_least_one_literal():
+ error = MustBeAtLeastOneLiteralError("one_of")
+ assert "one_of requires at least one literal" in str(error)
+
+
def test_name_not_valid():
error = NameNotValidError("bad name")
assert "Name bad name is not valid" in str(error)