Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# CLI Patterns Makefile
# Development and testing automation

.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
.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

# Default target
help:
Expand All @@ -18,6 +18,7 @@ help:
@echo "make test-fast - Run non-slow tests only"
@echo "make test-components - Run all component tests (parser, executor, design)"
@echo "make lint - Run ruff linter"
@echo "make lint-fix - Run ruff linter and auto-fix issues"
@echo "make type-check - Run mypy type checking"
@echo "make format - Format code with black"
@echo "make clean - Remove build artifacts and cache"
Expand Down Expand Up @@ -79,6 +80,14 @@ lint:
ruff check src/ tests/; \
fi

# Lint code and auto-fix issues
lint-fix:
@if command -v uv > /dev/null 2>&1; then \
uv run ruff check src/ tests/ --fix; \
else \
ruff check src/ tests/ --fix; \
fi

# Type check with mypy
type-check:
@if command -v uv > /dev/null 2>&1; then \
Expand Down
93 changes: 93 additions & 0 deletions src/cli_patterns/ui/parser/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Optional

from rich.console import Group, RenderableType
from rich.text import Text

from cli_patterns.ui.design.registry import theme_registry
from cli_patterns.ui.design.tokens import HierarchyToken, StatusToken

if TYPE_CHECKING:
pass

Expand Down Expand Up @@ -87,6 +93,9 @@ def has_flag(self, flag: str) -> bool:
class ParseError(Exception):
"""Exception raised during command parsing.

This class implements the Rich __rich__() protocol for automatic themed display
when printed to a Rich console. Use console.print(error) for best results.

Attributes:
message: Human-readable error message
error_type: Type of parsing error
Expand Down Expand Up @@ -115,6 +124,90 @@ def __str__(self) -> str:
"""String representation of the error."""
return f"{self.error_type}: {self.message}"

def __rich__(self) -> RenderableType:
"""Rich rendering protocol implementation for automatic themed display.

Uses the global theme_registry to resolve design tokens to themed styles,
ensuring consistency with the application's design system.

Returns:
RenderableType (Group) containing styled error message and suggestions
"""
# Map error_type to StatusToken
status_token = self._get_status_token()

# Create styled error message using theme registry
error_text = Text()
error_text.append(
f"{self.error_type}: ", style=theme_registry.resolve(status_token)
)
error_text.append(self.message)

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

if self.suggestions:
# Add "Did you mean:" prompt using theme registry
prompt_text = Text(
"\n\nDid you mean:", style=theme_registry.resolve(StatusToken.INFO)
)
renderables.append(prompt_text)

# Add up to 3 suggestions with hierarchy styling
for idx, suggestion in enumerate(self.suggestions[:3]):
hierarchy = self._get_suggestion_hierarchy(idx)
suggestion_text = Text()
suggestion_text.append(
f"\n • {suggestion}", style=theme_registry.resolve(hierarchy)
)
renderables.append(suggestion_text)

return Group(*renderables)

def _get_status_token(self) -> StatusToken:
"""Map error_type to appropriate StatusToken.

Returns:
StatusToken based on error_type
"""
error_type_lower = self.error_type.lower()

if "syntax" in error_type_lower:
return StatusToken.ERROR
elif (
"unknown_command" in error_type_lower
or "command_not_found" in error_type_lower
):
return StatusToken.WARNING
elif (
"invalid_args" in error_type_lower or "invalid_argument" in error_type_lower
):
return StatusToken.ERROR
elif "deprecated" in error_type_lower:
return StatusToken.WARNING
else:
# Default to ERROR for unknown error types
return StatusToken.ERROR

def _get_suggestion_hierarchy(self, index: int) -> HierarchyToken:
"""Get hierarchy token for suggestion based on ranking.

The first suggestion is PRIMARY (best match), second is SECONDARY (good match),
and third is TERTIARY (possible match).

Args:
index: Position in suggestions list (0-based)

Returns:
HierarchyToken indicating visual importance
"""
if index == 0:
return HierarchyToken.PRIMARY # Best match
elif index == 1:
return HierarchyToken.SECONDARY # Good match
else:
return HierarchyToken.TERTIARY # Possible match


@dataclass
class Context:
Expand Down
Loading
Loading