Skip to content

Commit 69ebe9c

Browse files
committed
refactor(parser): integrate theme registry for styled error rendering in ParseError
1 parent 5423d45 commit 69ebe9c

File tree

3 files changed

+82
-79
lines changed

3 files changed

+82
-79
lines changed

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# CLI Patterns Makefile
22
# Development and testing automation
33

4-
.PHONY: help install test test-unit test-integration test-coverage test-parser test-executor test-design test-fast test-components lint type-check format clean clean-docker all quality format-check ci-setup ci-native ci-docker verify-sync benchmark test-all ci-summary
4+
.PHONY: help install test test-unit test-integration test-coverage test-parser test-executor test-design test-fast test-components lint lint-fix type-check format clean clean-docker all quality format-check ci-setup ci-native ci-docker verify-sync benchmark test-all ci-summary
55

66
# Default target
77
help:
@@ -18,6 +18,7 @@ help:
1818
@echo "make test-fast - Run non-slow tests only"
1919
@echo "make test-components - Run all component tests (parser, executor, design)"
2020
@echo "make lint - Run ruff linter"
21+
@echo "make lint-fix - Run ruff linter and auto-fix issues"
2122
@echo "make type-check - Run mypy type checking"
2223
@echo "make format - Format code with black"
2324
@echo "make clean - Remove build artifacts and cache"
@@ -79,6 +80,14 @@ lint:
7980
ruff check src/ tests/; \
8081
fi
8182

83+
# Lint code and auto-fix issues
84+
lint-fix:
85+
@if command -v uv > /dev/null 2>&1; then \
86+
uv run ruff check src/ tests/ --fix; \
87+
else \
88+
ruff check src/ tests/ --fix; \
89+
fi
90+
8291
# Type check with mypy
8392
type-check:
8493
@if command -v uv > /dev/null 2>&1; then \

src/cli_patterns/ui/parser/types.py

Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from rich.console import Group, RenderableType
99
from rich.text import Text
1010

11+
from cli_patterns.ui.design.registry import theme_registry
1112
from cli_patterns.ui.design.tokens import HierarchyToken, StatusToken
1213

1314
if TYPE_CHECKING:
@@ -92,6 +93,9 @@ def has_flag(self, flag: str) -> bool:
9293
class ParseError(Exception):
9394
"""Exception raised during command parsing.
9495
96+
This class implements the Rich __rich__() protocol for automatic themed display
97+
when printed to a Rich console. Use console.print(error) for best results.
98+
9599
Attributes:
96100
message: Human-readable error message
97101
error_type: Type of parsing error
@@ -123,26 +127,29 @@ def __str__(self) -> str:
123127
def __rich__(self) -> RenderableType:
124128
"""Rich rendering protocol implementation for automatic themed display.
125129
130+
Uses the global theme_registry to resolve design tokens to themed styles,
131+
ensuring consistency with the application's design system.
132+
126133
Returns:
127134
RenderableType (Group) containing styled error message and suggestions
128135
"""
129136
# Map error_type to StatusToken
130137
status_token = self._get_status_token()
131138

132-
# Create styled error message
139+
# Create styled error message using theme registry
133140
error_text = Text()
134141
error_text.append(
135-
f"{self.error_type}: ", style=self._get_status_style(status_token)
142+
f"{self.error_type}: ", style=theme_registry.resolve(status_token)
136143
)
137144
error_text.append(self.message)
138145

139146
# Create suggestions list with hierarchy styling (limit to 3)
140147
renderables: list[RenderableType] = [error_text]
141148

142149
if self.suggestions:
143-
# Add "Did you mean:" prompt
150+
# Add "Did you mean:" prompt using theme registry
144151
prompt_text = Text(
145-
"\n\nDid you mean:", style=self._get_status_style(StatusToken.INFO)
152+
"\n\nDid you mean:", style=theme_registry.resolve(StatusToken.INFO)
146153
)
147154
renderables.append(prompt_text)
148155

@@ -151,7 +158,7 @@ def __rich__(self) -> RenderableType:
151158
hierarchy = self._get_suggestion_hierarchy(idx)
152159
suggestion_text = Text()
153160
suggestion_text.append(
154-
f"\n{suggestion}", style=self._get_hierarchy_style(hierarchy)
161+
f"\n{suggestion}", style=theme_registry.resolve(hierarchy)
155162
)
156163
renderables.append(suggestion_text)
157164

@@ -185,6 +192,9 @@ def _get_status_token(self) -> StatusToken:
185192
def _get_suggestion_hierarchy(self, index: int) -> HierarchyToken:
186193
"""Get hierarchy token for suggestion based on ranking.
187194
195+
The first suggestion is PRIMARY (best match), second is SECONDARY (good match),
196+
and third is TERTIARY (possible match).
197+
188198
Args:
189199
index: Position in suggestions list (0-based)
190200
@@ -198,50 +208,6 @@ def _get_suggestion_hierarchy(self, index: int) -> HierarchyToken:
198208
else:
199209
return HierarchyToken.TERTIARY # Possible match
200210

201-
def _get_status_style(self, status: StatusToken) -> str:
202-
"""Get Rich style string for StatusToken.
203-
204-
Args:
205-
status: StatusToken to convert to style
206-
207-
Returns:
208-
Rich style string
209-
"""
210-
if status == StatusToken.ERROR:
211-
return "bold red"
212-
elif status == StatusToken.WARNING:
213-
return "bold yellow"
214-
elif status == StatusToken.INFO:
215-
return "blue"
216-
elif status == StatusToken.SUCCESS:
217-
return "bold green"
218-
elif status == StatusToken.RUNNING:
219-
return "cyan"
220-
elif status == StatusToken.MUTED:
221-
return "dim"
222-
else:
223-
return "default"
224-
225-
def _get_hierarchy_style(self, hierarchy: HierarchyToken) -> str:
226-
"""Get Rich style string for HierarchyToken.
227-
228-
Args:
229-
hierarchy: HierarchyToken to convert to style
230-
231-
Returns:
232-
Rich style string
233-
"""
234-
if hierarchy == HierarchyToken.PRIMARY:
235-
return "bold"
236-
elif hierarchy == HierarchyToken.SECONDARY:
237-
return "default"
238-
elif hierarchy == HierarchyToken.TERTIARY:
239-
return "dim"
240-
elif hierarchy == HierarchyToken.QUATERNARY:
241-
return "dim italic"
242-
else:
243-
return "default"
244-
245211

246212
@dataclass
247213
class Context:

tests/unit/ui/parser/test_types.py

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -722,35 +722,63 @@ def test_suggestion_hierarchy_third_is_tertiary(self) -> None:
722722
hierarchy = error._get_suggestion_hierarchy(2)
723723
assert hierarchy == HierarchyToken.TERTIARY
724724

725-
def test_status_style_error(self) -> None:
726-
"""Test ERROR status maps to correct style."""
727-
error = ParseError("TEST", "Test", [])
728-
style = error._get_status_style(StatusToken.ERROR)
729-
assert "red" in style
730-
731-
def test_status_style_warning(self) -> None:
732-
"""Test WARNING status maps to correct style."""
733-
error = ParseError("TEST", "Test", [])
734-
style = error._get_status_style(StatusToken.WARNING)
735-
assert "yellow" in style
736-
737-
def test_status_style_info(self) -> None:
738-
"""Test INFO status maps to correct style."""
739-
error = ParseError("TEST", "Test", [])
740-
style = error._get_status_style(StatusToken.INFO)
741-
assert "blue" in style
742-
743-
def test_hierarchy_style_primary(self) -> None:
744-
"""Test PRIMARY hierarchy maps to bold style."""
745-
error = ParseError("TEST", "Test", [])
746-
style = error._get_hierarchy_style(HierarchyToken.PRIMARY)
747-
assert "bold" in style
748-
749-
def test_hierarchy_style_tertiary(self) -> None:
750-
"""Test TERTIARY hierarchy maps to dim style."""
751-
error = ParseError("TEST", "Test", [])
752-
style = error._get_hierarchy_style(HierarchyToken.TERTIARY)
753-
assert "dim" in style
725+
def test_theme_registry_integration_status_tokens(self) -> None:
726+
"""Test that __rich__() uses theme_registry for StatusToken styling."""
727+
from cli_patterns.ui.design.registry import theme_registry
728+
729+
error = ParseError(
730+
error_type="SYNTAX_ERROR",
731+
message="Test theme integration",
732+
suggestions=[],
733+
)
734+
735+
# Get the status token and verify it can be resolved
736+
status = error._get_status_token()
737+
style = theme_registry.resolve(status)
738+
739+
# Verify theme registry returns a valid style string
740+
assert isinstance(style, str)
741+
assert len(style) > 0
742+
743+
def test_theme_registry_integration_hierarchy_tokens(self) -> None:
744+
"""Test that __rich__() uses theme_registry for HierarchyToken styling."""
745+
from cli_patterns.ui.design.registry import theme_registry
746+
747+
error = ParseError(
748+
error_type="TEST",
749+
message="Test hierarchy styling",
750+
suggestions=["First", "Second", "Third"],
751+
)
752+
753+
# Get hierarchy tokens and verify they can be resolved
754+
for idx in range(3):
755+
hierarchy = error._get_suggestion_hierarchy(idx)
756+
style = theme_registry.resolve(hierarchy)
757+
758+
# Verify theme registry returns a valid style string
759+
assert isinstance(style, str)
760+
assert len(style) > 0
761+
762+
def test_theme_registry_integration_in_rich_output(self) -> None:
763+
"""Test that Rich rendering uses theme_registry resolved styles."""
764+
error = ParseError(
765+
error_type="THEME_TEST",
766+
message="Testing theme registry integration",
767+
suggestions=["Suggestion 1"],
768+
)
769+
770+
# Render with Rich
771+
result = error.__rich__()
772+
assert isinstance(result, Group)
773+
774+
# Verify the error can be printed (theme resolution doesn't throw)
775+
console = Console()
776+
with console.capture() as capture:
777+
console.print(error)
778+
output = capture.get()
779+
780+
assert "THEME_TEST" in output
781+
assert "Testing theme registry integration" in output
754782

755783
def test_rich_rendering_with_console_print(self) -> None:
756784
"""Test that ParseError can be directly printed to Rich console."""

0 commit comments

Comments
 (0)