diff options
| -rw-r--r-- | edify/builder/core.py | 25 | ||||
| -rw-r--r-- | tests/builder/repr.test.py | 31 |
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 ") |
