aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--edify/builder/core.py25
-rw-r--r--tests/builder/repr.test.py31
2 files changed, 53 insertions, 3 deletions
diff --git a/edify/builder/core.py b/edify/builder/core.py
index f6a1bd9..455be98 100644
--- a/edify/builder/core.py
+++ b/edify/builder/core.py
@@ -2,9 +2,9 @@
Both fluent surfaces carry the same :class:`BuilderState` and use the same
clone-and-replace pattern for chain methods. :class:`BuilderCore` provides
-the state attribute, the constructor, and the ``_with_state`` helper that
-mixins call to produce new instances; concrete classes compose it with
-their mixin set.
+the state attribute, the constructor, the ``_with_state`` helper that
+mixins call to produce new instances, and the interactive ``__repr__``
+that shows the pattern-so-far.
"""
from __future__ import annotations
@@ -12,6 +12,9 @@ from __future__ import annotations
from typing import Self
from edify.builder.types.state import BuilderState
+from edify.errors.structure import CannotCallSubexpressionError
+
+_UNCLOSED_FRAME_MARKER = "<unclosed>"
class BuilderCore:
@@ -28,3 +31,19 @@ class BuilderCore:
new_instance = concrete_class.__new__(concrete_class)
new_instance._state = new_state
return new_instance
+
+ def __repr__(self) -> str:
+ """Return ``<ClassName 'pattern-so-far'>`` for interactive display."""
+ rendered = _render_or_marker(self)
+ return f"<{type(self).__name__} {rendered!r}>"
+
+
+def _render_or_marker(builder: BuilderCore) -> str:
+ """Return the emitted regex string, or a placeholder when frames are unclosed."""
+ to_regex_string = getattr(builder, "to_regex_string", None)
+ if to_regex_string is None:
+ return _UNCLOSED_FRAME_MARKER
+ try:
+ return to_regex_string()
+ except CannotCallSubexpressionError:
+ return _UNCLOSED_FRAME_MARKER
diff --git a/tests/builder/repr.test.py b/tests/builder/repr.test.py
new file mode 100644
index 0000000..278f40b
--- /dev/null
+++ b/tests/builder/repr.test.py
@@ -0,0 +1,31 @@
+"""Tests for :meth:`BuilderCore.__repr__` on :class:`RegexBuilder` and :class:`Pattern`."""
+
+from edify import Pattern, RegexBuilder
+
+
+def test_repr_of_a_fresh_regex_builder_shows_the_empty_non_capturing_group():
+ assert repr(RegexBuilder()) == "<RegexBuilder '(?:)'>"
+
+
+def test_repr_of_a_fresh_pattern_shows_the_empty_non_capturing_group():
+ assert repr(Pattern()) == "<Pattern '(?:)'>"
+
+
+def test_repr_of_a_regex_builder_shows_the_pattern_so_far():
+ builder = RegexBuilder().start_of_input().exactly(4).digit().end_of_input()
+ assert repr(builder) == "<RegexBuilder '^\\\\d{4}$'>"
+
+
+def test_repr_of_a_pattern_shows_the_pattern_so_far():
+ pattern = Pattern().one_or_more().word()
+ assert repr(pattern) == "<Pattern '\\\\w+'>"
+
+
+def test_repr_of_a_builder_with_open_frames_shows_the_unclosed_marker():
+ builder = RegexBuilder().any_of().string("cat")
+ assert repr(builder) == "<RegexBuilder '<unclosed>'>"
+
+
+def test_repr_uses_the_concrete_class_name_not_the_base():
+ assert repr(RegexBuilder()).startswith("<RegexBuilder ")
+ assert repr(Pattern()).startswith("<Pattern ")