aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornatsuoto <[email protected]>2026-07-01 17:04:28 +0530
committernatsuoto <[email protected]>2026-07-01 17:04:28 +0530
commitdab26fa6193af03b5b132f6d7fe60b60d884ba86 (patch)
treeea8b0895b829a13b3ec0fca30509aa4e7a663c16
parent52daac402a6d47253f9e13d5a955c990c4f171a8 (diff)
downloadedify-dab26fa6193af03b5b132f6d7fe60b60d884ba86.tar.xz
edify-dab26fa6193af03b5b132f6d7fe60b60d884ba86.zip
feat: .fork() and .copy() explicit aliases for branch construction
-rw-r--r--edify/builder/core.py13
-rw-r--r--tests/builder/equality.test.py2
-rw-r--r--tests/builder/fork.test.py47
3 files changed, 61 insertions, 1 deletions
diff --git a/edify/builder/core.py b/edify/builder/core.py
index 1aa1ca3..333c7ad 100644
--- a/edify/builder/core.py
+++ b/edify/builder/core.py
@@ -33,6 +33,19 @@ class BuilderCore:
new_instance._state = new_state
return new_instance
+ def fork(self) -> Self:
+ """Return a fresh builder with the same immutable state.
+
+ Chain methods already return new instances so implicit forking works
+ (``root.digit()`` and ``root.word()`` share nothing after the split);
+ this method makes the intent explicit and discoverable via autocomplete.
+ """
+ return self._with_state(self._state)
+
+ def copy(self) -> Self:
+ """Alias for :meth:`fork` — return a fresh builder with the same immutable state."""
+ return self._with_state(self._state)
+
def __repr__(self) -> str:
"""Return ``<ClassName 'pattern-so-far'>`` for interactive display."""
rendered = _render_or_marker(self)
diff --git a/tests/builder/equality.test.py b/tests/builder/equality.test.py
index 6c6fce5..717d1ae 100644
--- a/tests/builder/equality.test.py
+++ b/tests/builder/equality.test.py
@@ -47,4 +47,4 @@ def test_hash_of_two_flags_that_differ_are_distinct():
a = RegexBuilder().ignore_case().digit()
b = RegexBuilder().digit()
assert a != b
- assert hash(a) != hash(b) \ No newline at end of file
+ assert hash(a) != hash(b)
diff --git a/tests/builder/fork.test.py b/tests/builder/fork.test.py
new file mode 100644
index 0000000..bcb8d1b
--- /dev/null
+++ b/tests/builder/fork.test.py
@@ -0,0 +1,47 @@
+"""Tests for the :meth:`BuilderCore.fork` and :meth:`BuilderCore.copy` aliases."""
+
+from edify import Pattern, RegexBuilder
+
+
+def test_fork_returns_a_regex_builder_with_the_same_state():
+ original = RegexBuilder().digit()
+ forked = original.fork()
+ assert forked == original
+ assert forked is not original
+
+
+def test_fork_returns_a_pattern_with_the_same_state():
+ original = Pattern().word()
+ forked = original.fork()
+ assert forked == original
+ assert forked is not original
+
+
+def test_copy_returns_a_regex_builder_with_the_same_state():
+ original = RegexBuilder().digit()
+ copied = original.copy()
+ assert copied == original
+ assert copied is not original
+
+
+def test_copy_returns_a_pattern_with_the_same_state():
+ original = Pattern().word()
+ copied = original.copy()
+ assert copied == original
+ assert copied is not original
+
+
+def test_fork_and_copy_produce_independent_branches():
+ root = RegexBuilder().digit()
+ branch_a = root.fork().word()
+ branch_b = root.copy().whitespace_char()
+ assert root.to_regex_string() == "\\d"
+ assert branch_a.to_regex_string() == "\\d\\w"
+ assert branch_b.to_regex_string() == "\\d\\s"
+
+
+def test_fork_and_copy_return_the_concrete_class_type():
+ assert isinstance(RegexBuilder().fork(), RegexBuilder)
+ assert isinstance(RegexBuilder().copy(), RegexBuilder)
+ assert isinstance(Pattern().fork(), Pattern)
+ assert isinstance(Pattern().copy(), Pattern)