Skip to content

Commit 5423d45

Browse files
dugshubclaude
andcommitted
CLI-10: Implement __rich__() method for ParseError
Add Rich protocol implementation to ParseError class enabling automatic themed rendering when displayed through Rich consoles. This eliminates the need for manual ErrorFormatter usage at error catch points. ## Implementation - Add __rich__() method returning rich.console.Group with styled components - Map error_type to StatusToken (syntax→ERROR, unknown_command→WARNING, etc.) - Style suggestions with HierarchyToken by ranking (PRIMARY, SECONDARY, TERTIARY) - Auto-limit suggestions to 3 maximum for concise output - Add 25 comprehensive unit tests for Rich rendering ## Benefits - Centralized styling in ParseError class - Works automatically with console.print(error) - Fully backward compatible with existing code - MyPy strict mode compliant - No manual ErrorFormatter needed at catch points All 602 tests pass. Closes CLI-10. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 95ed07d commit 5423d45

File tree

2 files changed

+429
-0
lines changed

2 files changed

+429
-0
lines changed

src/cli_patterns/ui/parser/types.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
from dataclasses import dataclass, field
66
from typing import TYPE_CHECKING, Any, Optional
77

8+
from rich.console import Group, RenderableType
9+
from rich.text import Text
10+
11+
from cli_patterns.ui.design.tokens import HierarchyToken, StatusToken
12+
813
if TYPE_CHECKING:
914
pass
1015

@@ -115,6 +120,128 @@ def __str__(self) -> str:
115120
"""String representation of the error."""
116121
return f"{self.error_type}: {self.message}"
117122

123+
def __rich__(self) -> RenderableType:
124+
"""Rich rendering protocol implementation for automatic themed display.
125+
126+
Returns:
127+
RenderableType (Group) containing styled error message and suggestions
128+
"""
129+
# Map error_type to StatusToken
130+
status_token = self._get_status_token()
131+
132+
# Create styled error message
133+
error_text = Text()
134+
error_text.append(
135+
f"{self.error_type}: ", style=self._get_status_style(status_token)
136+
)
137+
error_text.append(self.message)
138+
139+
# Create suggestions list with hierarchy styling (limit to 3)
140+
renderables: list[RenderableType] = [error_text]
141+
142+
if self.suggestions:
143+
# Add "Did you mean:" prompt
144+
prompt_text = Text(
145+
"\n\nDid you mean:", style=self._get_status_style(StatusToken.INFO)
146+
)
147+
renderables.append(prompt_text)
148+
149+
# Add up to 3 suggestions with hierarchy styling
150+
for idx, suggestion in enumerate(self.suggestions[:3]):
151+
hierarchy = self._get_suggestion_hierarchy(idx)
152+
suggestion_text = Text()
153+
suggestion_text.append(
154+
f"\n{suggestion}", style=self._get_hierarchy_style(hierarchy)
155+
)
156+
renderables.append(suggestion_text)
157+
158+
return Group(*renderables)
159+
160+
def _get_status_token(self) -> StatusToken:
161+
"""Map error_type to appropriate StatusToken.
162+
163+
Returns:
164+
StatusToken based on error_type
165+
"""
166+
error_type_lower = self.error_type.lower()
167+
168+
if "syntax" in error_type_lower:
169+
return StatusToken.ERROR
170+
elif (
171+
"unknown_command" in error_type_lower
172+
or "command_not_found" in error_type_lower
173+
):
174+
return StatusToken.WARNING
175+
elif (
176+
"invalid_args" in error_type_lower or "invalid_argument" in error_type_lower
177+
):
178+
return StatusToken.ERROR
179+
elif "deprecated" in error_type_lower:
180+
return StatusToken.WARNING
181+
else:
182+
# Default to ERROR for unknown error types
183+
return StatusToken.ERROR
184+
185+
def _get_suggestion_hierarchy(self, index: int) -> HierarchyToken:
186+
"""Get hierarchy token for suggestion based on ranking.
187+
188+
Args:
189+
index: Position in suggestions list (0-based)
190+
191+
Returns:
192+
HierarchyToken indicating visual importance
193+
"""
194+
if index == 0:
195+
return HierarchyToken.PRIMARY # Best match
196+
elif index == 1:
197+
return HierarchyToken.SECONDARY # Good match
198+
else:
199+
return HierarchyToken.TERTIARY # Possible match
200+
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+
118245

119246
@dataclass
120247
class Context:

0 commit comments

Comments
 (0)