diff options
| author | 夏音 / natsuoto.exe <[email protected]> | 2026-07-01 16:13:54 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-07-01 16:13:54 +0530 |
| commit | 42509f3a1d0c93c5fd43b2e2bdc42201d24a643e (patch) | |
| tree | 5f1544aed63e707d5fb2aeeb87b6e7e0dc98f5cd /tests | |
| parent | a757473f3551426948069a1a25ef6657a572d759 (diff) | |
| parent | 341cf4fe45bb56677f0b42e287738999fb8a1ac5 (diff) | |
| download | edify-42509f3a1d0c93c5fd43b2e2bdc42201d24a643e.tar.xz edify-42509f3a1d0c93c5fd43b2e2bdc42201d24a643e.zip | |
feat: .at_most(n) quantifier closes the symmetry gap with at_least/exactly/between (#265)
Adds `.at_most(n)` on both `RegexBuilder`/`Pattern` and as an
`at_most(n, operand)` factory. Closes the symmetry gap in the quantifier
lineup — we had `exactly(n)`, `at_least(n)`, and `between(lo, hi)`, but
no `{0,n}` shorthand.
## What is new
- `AtMostElement(times, child)` in the elements union, rendered as
`{0,times}` by the quantifier suffix dispatch.
- `.at_most(count)` chain method on `QuantifiersMixin` — validates
`count > 0` (matches the `at_least` contract).
- `at_most(count, operand)` free-function factory in the humre-style
API.
- Re-exported through `edify.pattern` and top-level `edify`.
## Example
```python
from edify import DIGIT, at_most, Pattern
at_most(3, DIGIT).to_regex_string() # \d{0,3}
Pattern().at_most(3).digit().to_regex_string() # \d{0,3}
```
Closes #130
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/builder/builder.test.py | 6 | ||||
| -rw-r--r-- | tests/builder/validation.test.py | 5 | ||||
| -rw-r--r-- | tests/pattern/factories/quantifiers.test.py | 14 |
3 files changed, 25 insertions, 0 deletions
diff --git a/tests/builder/builder.test.py b/tests/builder/builder.test.py index 062e1ae..33aa17d 100644 --- a/tests/builder/builder.test.py +++ b/tests/builder/builder.test.py @@ -348,6 +348,12 @@ def test_at_least(): regex_compilation("\\w{3,}", expr) +def test_at_most(): + expr = RegexBuilder().at_most(3).word() + regex_equality("\\w{0,3}", expr) + regex_compilation("\\w{0,3}", expr) + + def test_between(): expr = RegexBuilder().between(3, 5).word() regex_equality("\\w{3,5}", expr) diff --git a/tests/builder/validation.test.py b/tests/builder/validation.test.py index e4898ad..53128c5 100644 --- a/tests/builder/validation.test.py +++ b/tests/builder/validation.test.py @@ -109,6 +109,11 @@ def test_at_least_non_positive_raises(): RegexBuilder().at_least(-1).digit() +def test_at_most_non_positive_raises(): + with pytest.raises(MustBePositiveIntegerError): + RegexBuilder().at_most(0).digit() + + def test_between_negative_lower_raises(): with pytest.raises(MustBeIntegerGreaterThanZeroError): RegexBuilder().between(-1, 5).digit() diff --git a/tests/pattern/factories/quantifiers.test.py b/tests/pattern/factories/quantifiers.test.py index 8a6f34f..a0f558a 100644 --- a/tests/pattern/factories/quantifiers.test.py +++ b/tests/pattern/factories/quantifiers.test.py @@ -7,6 +7,7 @@ from edify import ( WORD, Pattern, at_least, + at_most, between, between_lazy, char, @@ -62,6 +63,14 @@ def test_at_least_produces_open_ended_quantifier(): assert at_least(2, DIGIT).to_regex_string() == "\\d{2,}" +def test_at_most_produces_zero_lower_bound_quantifier(): + assert at_most(3, DIGIT).to_regex_string() == "\\d{0,3}" + + +def test_at_most_groups_a_multi_element_operand(): + assert at_most(3, DIGIT + WORD).to_regex_string() == "(?:\\d\\w){0,3}" + + def test_between_produces_greedy_bounded_quantifier(): assert between(2, 4, DIGIT).to_regex_string() == "\\d{2,4}" @@ -84,6 +93,11 @@ def test_at_least_rejects_negative_count(): at_least(-1, DIGIT) +def test_at_most_rejects_zero_count(): + with pytest.raises(MustBePositiveIntegerError): + at_most(0, DIGIT) + + def test_between_rejects_negative_lower_bound(): with pytest.raises(MustBeIntegerGreaterThanZeroError): between(-1, 5, DIGIT) |
