aboutsummaryrefslogtreecommitdiff
path: root/tests/builder
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-07-01 17:50:29 +0530
committerGitHub <[email protected]>2026-07-01 17:50:29 +0530
commitc757ea1fea447df3ed69e74b407ca9710a9f85e0 (patch)
treea710f73b1a8b7b0117f41a18c9accd01127b033e /tests/builder
parentad4eb75ddcadf263391bba8eed837cce3b860cf9 (diff)
parente0d5e70a0d1a35a1e9311aa8200fb5c5f2ca0854 (diff)
downloadedify-c757ea1fea447df3ed69e74b407ca9710a9f85e0.tar.xz
edify-c757ea1fea447df3ed69e74b407ca9710a9f85e0.zip
feat!: Regex result wrapper on .to_regex(), plus flag kwargs at the terminal (#270)
Reshapes what `.to_regex()` returns and takes, so pattern compilation becomes a first-class edify value. ## `Regex` result wrapper class `edify.Regex` is a composition wrapper over `re.Pattern` (which is a CPython C type and cannot be subclassed). It exposes: - `.source` — the emitted regex string. - `.compiled` — the underlying `re.Pattern` for callers that need identity checks, `isinstance` checks, or direct interop with libraries typed on `re.Pattern`. - Eight direct delegates — `.match()`, `.search()`, `.fullmatch()`, `.findall()`, `.finditer()`, `.sub()`, `.subn()`, `.split()` — mirroring the `re.Pattern` query surface. - Value equality on `(source, flags)` plus `__hash__`. ## `to_regex()` now returns `Regex` The terminal returns `edify.Regex` instead of raw `re.Pattern`. The existing eight query methods continue to work via delegation. `.match()` / `.search()` / … on builders (via `MatcherMixin`) still work because they go through the same delegation. **Breaking:** `isinstance(x, re.Pattern)` and `re.Pattern`-typed annotations on values returned from `.to_regex()` break. Callers that need the raw pattern read `.compiled`. ## Flag kwargs on `.to_regex(...)` `.to_regex()` now accepts six keyword-only flag arguments — `ascii_only`, `debug`, `ignore_case`, `multiline`, `dotall`, `verbose` — that OR-merge into the flag snapshot the builder already carries. Passing `ignore_case=True` at the terminal is equivalent to calling `.ignore_case()` mid-chain. Kwargs never turn a chain-set flag off; the terminal is the natural home for pattern-global settings. ## Example ```python from edify import RegexBuilder email = RegexBuilder().start_of_input().one_or_more().letter().end_of_input() regex = email.to_regex(ignore_case=True) regex.source # ^[a-zA-Z]+$ regex.match("Hello") # <re.Match ...> regex.compiled # <re.Pattern object; flags=re.IGNORECASE> ``` Closes #133 Closes #134 Closes #128
Diffstat (limited to 'tests/builder')
-rw-r--r--tests/builder/builder.test.py2
-rw-r--r--tests/builder/flags.test.py72
-rw-r--r--tests/builder/use.test.py2
3 files changed, 74 insertions, 2 deletions
diff --git a/tests/builder/builder.test.py b/tests/builder/builder.test.py
index ddd1997..82aa1cf 100644
--- a/tests/builder/builder.test.py
+++ b/tests/builder/builder.test.py
@@ -47,7 +47,7 @@ def regex_equality(regex, rb_expression):
def regex_compilation(regex, rb_expression, f=0):
rb_expression_c = rb_expression.to_regex()
- assert re.compile(regex, flags=f) == rb_expression_c
+ assert re.compile(regex, flags=f) == rb_expression_c.compiled
def test_empty_regex():
diff --git a/tests/builder/flags.test.py b/tests/builder/flags.test.py
new file mode 100644
index 0000000..d35d2ff
--- /dev/null
+++ b/tests/builder/flags.test.py
@@ -0,0 +1,72 @@
+"""Tests for the flag keyword arguments on :meth:`TerminalsMixin.to_regex`."""
+
+import re
+import sys
+
+import pytest
+
+from edify import Pattern, RegexBuilder
+
+_ON_PYPY = hasattr(sys, "pypy_version_info")
+
+
+def test_no_kwargs_uses_only_the_chain_flag_snapshot():
+ compiled = RegexBuilder().string("hi").to_regex()
+ assert compiled.compiled.flags & re.I == 0
+
+
+def test_ignore_case_kwarg_enables_the_ignore_case_flag():
+ compiled = RegexBuilder().string("hi").to_regex(ignore_case=True)
+ assert compiled.compiled.flags & re.I == re.I
+
+
+def test_multiline_kwarg_enables_the_multiline_flag():
+ compiled = RegexBuilder().string("hi").to_regex(multiline=True)
+ assert compiled.compiled.flags & re.M == re.M
+
+
+def test_dotall_kwarg_enables_the_dotall_flag():
+ compiled = RegexBuilder().string("hi").to_regex(dotall=True)
+ assert compiled.compiled.flags & re.S == re.S
+
+
+def test_ascii_only_kwarg_enables_the_ascii_flag():
+ compiled = RegexBuilder().string("hi").to_regex(ascii_only=True)
+ assert compiled.compiled.flags & re.A == re.A
+
+
+def test_verbose_kwarg_enables_the_verbose_flag():
+ compiled = RegexBuilder().string("hi").to_regex(verbose=True)
+ assert compiled.compiled.flags & re.X == re.X
+
+
+ _ON_PYPY,
+ reason="PyPy's re.DEBUG opcode disassembler has an upstream IndexError bug",
+)
+def test_debug_kwarg_compiles_without_error():
+ compiled = RegexBuilder().string("hi").to_regex(debug=True)
+ assert compiled.source == "hi"
+
+
+def test_kwargs_or_merge_with_chain_flags():
+ compiled = RegexBuilder().ignore_case().string("hi").to_regex(multiline=True)
+ assert compiled.compiled.flags & re.I == re.I
+ assert compiled.compiled.flags & re.M == re.M
+
+
+def test_kwargs_never_turn_off_a_chain_flag():
+ compiled = RegexBuilder().ignore_case().string("hi").to_regex(ignore_case=False)
+ assert compiled.compiled.flags & re.I == re.I
+
+
+def test_kwargs_work_on_pattern_too():
+ compiled = Pattern().string("hi").to_regex(ignore_case=True)
+ assert compiled.compiled.flags & re.I == re.I
+
+
+def test_multiple_kwargs_combine():
+ compiled = RegexBuilder().string("hi").to_regex(ignore_case=True, multiline=True, dotall=True)
+ assert compiled.compiled.flags & re.I == re.I
+ assert compiled.compiled.flags & re.M == re.M
+ assert compiled.compiled.flags & re.S == re.S
diff --git a/tests/builder/use.test.py b/tests/builder/use.test.py
index 10d7855..0253786 100644
--- a/tests/builder/use.test.py
+++ b/tests/builder/use.test.py
@@ -21,7 +21,7 @@ def test_use_drops_the_pattern_flag_snapshot_by_default():
case_insensitive_pattern = Pattern().ignore_case().string("hello")
expression = RegexBuilder().use(case_insensitive_pattern)
compiled = expression.to_regex()
- assert compiled.flags & 2 == 0
+ assert compiled.compiled.flags & 2 == 0
def test_use_accepts_another_regex_builder_as_the_source():