Skip to content

Commit f1a927a

Browse files
dugshubclaude
andcommitted
feat(parser): add ErrorFormatter for enhanced error display
Implements ErrorFormatter class that provides rich terminal formatting for parser errors with: - Styled error titles and messages - Color-coded status indicators (error, warning, info) - Formatted suggestion lists - Integration with design system tokens 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 78bacda commit f1a927a

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
"""Error formatter for parser errors with design token integration."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Optional
6+
7+
from rich.console import Console
8+
from rich.text import Text
9+
10+
from cli_patterns.ui.design.components import ErrorDisplay
11+
from cli_patterns.ui.design.tokens import (
12+
DisplayMetadata,
13+
DisplayStyle,
14+
EmphasisToken,
15+
StatusToken,
16+
)
17+
from cli_patterns.ui.parser.types import ParseError
18+
19+
20+
class ErrorFormatter:
21+
"""Formatter for ParseError objects with design token styling.
22+
23+
This class provides a high-level interface for formatting ParseError objects
24+
into styled Rich Text objects using the design token system and ErrorDisplay
25+
component for consistent styling.
26+
"""
27+
28+
def __init__(self, console: Optional[Console] = None) -> None:
29+
"""Initialize ErrorFormatter.
30+
31+
Args:
32+
console: Optional Rich console instance. If not provided, a default
33+
console will be created.
34+
"""
35+
self.console = console or Console()
36+
self.error_display = ErrorDisplay()
37+
38+
def format_error(self, error: ParseError) -> Text:
39+
"""Format a ParseError into styled Rich Text.
40+
41+
This is the main entry point for error formatting. It handles both
42+
legacy ParseError objects without display metadata and enhanced
43+
ParseError objects with display metadata.
44+
45+
Args:
46+
error: ParseError instance to format
47+
48+
Returns:
49+
Styled Rich Text object containing the formatted error
50+
"""
51+
# Get display metadata if available, or None for backward compatibility
52+
metadata = getattr(error, "display_metadata", None)
53+
54+
# Format the error data
55+
error_data = self.error_display.format_error_data(
56+
error.error_type, error.message, error.suggestions
57+
)
58+
59+
# Create styles based on metadata
60+
title_style = self.error_display.create_error_title_style(metadata)
61+
content_style = self.error_display.create_error_content_style(metadata)
62+
suggestion_style = self.error_display.create_suggestion_style(metadata)
63+
64+
# Render error components
65+
title_text = self.error_display.render_error_title(
66+
error_data["title"], title_style, self.console
67+
)
68+
message_text = self.error_display.render_error_message(
69+
error_data["message"], content_style, self.console
70+
)
71+
72+
# Combine into final formatted text
73+
result = Text()
74+
75+
# Add title with styling
76+
styled_title = self.apply_error_styling(title_text, StatusToken.ERROR)
77+
result.append_text(styled_title)
78+
result.append("\n\n")
79+
80+
# Add message
81+
result.append_text(message_text)
82+
83+
# Add suggestions if present
84+
suggestions = error_data.get("suggestions", [])
85+
if suggestions:
86+
suggestions_text = self.format_suggestions(suggestions)
87+
if suggestions_text and suggestions_text.plain.strip():
88+
result.append("\n\nSuggestions:\n")
89+
styled_suggestions = self.apply_suggestion_styling(
90+
suggestions_text, suggestion_style.emphasis
91+
)
92+
result.append_text(styled_suggestions)
93+
94+
return result
95+
96+
def format_suggestions(self, suggestions: list[str]) -> Optional[Text]:
97+
"""Format a list of suggestions into styled text.
98+
99+
Args:
100+
suggestions: List of suggestion strings
101+
102+
Returns:
103+
Styled Rich Text object or None for empty suggestions
104+
"""
105+
if not suggestions:
106+
return Text("")
107+
108+
# Format as bullet list
109+
text_parts = []
110+
for suggestion in suggestions:
111+
text_parts.append(f"• {suggestion}")
112+
113+
return Text("\n".join(text_parts))
114+
115+
def apply_error_styling(self, text: Text, status: StatusToken) -> Text:
116+
"""Apply error styling to text based on status token.
117+
118+
Args:
119+
text: Rich Text object to style
120+
status: Status token for styling
121+
122+
Returns:
123+
Styled Rich Text object
124+
"""
125+
# Apply status-based styling
126+
# The actual styling would be resolved through the theme system
127+
# For now, return the text as-is with basic styling
128+
if status == StatusToken.ERROR:
129+
text.stylize("bold red")
130+
elif status == StatusToken.WARNING:
131+
text.stylize("bold yellow")
132+
elif status == StatusToken.INFO:
133+
text.stylize("blue")
134+
135+
return text
136+
137+
def apply_suggestion_styling(self, text: Text, emphasis: EmphasisToken) -> Text:
138+
"""Apply suggestion styling to text based on emphasis token.
139+
140+
Args:
141+
text: Rich Text object to style
142+
emphasis: Emphasis token for styling
143+
144+
Returns:
145+
Styled Rich Text object
146+
"""
147+
# Apply emphasis-based styling
148+
if emphasis == EmphasisToken.SUBTLE:
149+
text.stylize("dim")
150+
elif emphasis == EmphasisToken.STRONG:
151+
text.stylize("bold")
152+
# NORMAL emphasis doesn't need additional styling
153+
154+
return text
155+
156+
def resolve_metadata_style(
157+
self, metadata: Optional[DisplayMetadata], status: StatusToken
158+
) -> DisplayStyle:
159+
"""Resolve display metadata to a complete display style.
160+
161+
Args:
162+
metadata: Optional display metadata
163+
status: Status token to apply
164+
165+
Returns:
166+
Complete DisplayStyle object
167+
"""
168+
if metadata:
169+
return metadata.with_status(status)
170+
else:
171+
# Return default style for backward compatibility
172+
return DisplayStyle.from_metadata(
173+
DisplayMetadata.model_validate(
174+
{"category": "cat_1", "hierarchy": "primary", "emphasis": "normal"}
175+
),
176+
status=status,
177+
)

0 commit comments

Comments
 (0)