diff options
| author | 夏音 / natsuoto.exe <[email protected]> | 2026-07-01 16:21:15 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-07-01 16:21:15 +0530 |
| commit | e859ce24e5ffc95653023669f9edf4d3a0344a8e (patch) | |
| tree | ea2d0971292c1a70737f1f068ec27d308974b88f /tests | |
| parent | 42509f3a1d0c93c5fd43b2e2bdc42201d24a643e (diff) | |
| parent | 1860f7396e997c785d2299c421852909a0ee2070 (diff) | |
| download | edify-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.py | 72 | ||||
| -rw-r--r-- | tests/errors/errors.test.py | 6 |
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) |
