"""OMEGA CLI UI — Rich rendering helpers with graceful plain-text fallback.""" import os from typing import Any, Dict, Optional, Sequence, Tuple # Graceful import — fall back to plain text if Rich unavailable or NO_COLOR set try: if os.environ.get("NO_COLOR"): raise ImportError("NO_COLOR") from rich.console import Console from rich.panel import Panel from rich.table import Table from rich.text import Text RICH_AVAILABLE = True console = Console() except ImportError: RICH_AVAILABLE = False console = None # type: ignore[assignment] def print_header(title: str) -> None: """Print a styled header (Rich Panel or plain === title ===).""" if RICH_AVAILABLE: console.print(Panel(title, style="bold cyan", expand=False)) else: print(f"\n=== {title} ===\n") def print_section(title: str) -> None: """Print a section separator.""" if RICH_AVAILABLE: console.print(f"\n[bold]{title}[/bold]") console.print("─" * min(len(title) + 4, 60), style="dim") else: print(f"\n--- {title} ---") def print_kv(pairs: Sequence[Tuple[str, Any]], indent: int = 2) -> None: """Print key-value pairs with colored keys or plain text.""" prefix = " " * indent if RICH_AVAILABLE: for key, value in pairs: console.print(f"{prefix}[bold cyan]{key}:[/bold cyan] {value}") else: for key, value in pairs: print(f"{prefix}{key}: {value}") def print_table( title: Optional[str], columns: Sequence[str], rows: Sequence[Sequence[Any]], *, styles: Optional[Sequence[Optional[str]]] = None, ) -> None: """Print a formatted table (Rich Table or aligned plain text).""" if RICH_AVAILABLE: table = Table(title=title, show_lines=False, pad_edge=True) for i, col in enumerate(columns): style = styles[i] if styles and i < len(styles) else None table.add_column(col, style=style) for row in rows: table.add_row(*(str(cell) for cell in row)) console.print(table) else: if title: print(f"\n{title}") if not rows: print(" (empty)") return # Calculate column widths widths = [len(str(c)) for c in columns] for row in rows: for i, cell in enumerate(row): if i < len(widths): widths[i] = max(widths[i], len(str(cell))) # Header header = " ".join(str(c).ljust(widths[i]) for i, c in enumerate(columns)) print(f" {header}") print(f" {' '.join('-' * w for w in widths)}") for row in rows: line = " ".join(str(cell).ljust(widths[i]) for i, cell in enumerate(row) if i < len(widths)) print(f" {line}") def print_bar_chart( items: Sequence[Tuple[str, int]], title: Optional[str] = None, total: Optional[int] = None, ) -> None: """Print a horizontal bar chart with colored blocks or ASCII #.""" if total is None: total = sum(count for _, count in items) if total == 0: if title: print(f" {title}: (no data)") return if RICH_AVAILABLE: table = Table(title=title, show_lines=False, pad_edge=True, show_header=True) table.add_column("Type", style="bold") table.add_column("Count", justify="right") table.add_column("%", justify="right") table.add_column("", min_width=25) colors = ["cyan", "green", "yellow", "magenta", "blue", "red", "white"] for i, (label, count) in enumerate(items): pct = count / total * 100 bar_len = int(pct / 2) color = colors[i % len(colors)] bar = Text("█" * bar_len, style=color) table.add_row(label, str(count), f"{pct:.1f}%", bar) console.print(table) else: if title: print(f"\n{title}") for label, count in items: pct = count / total * 100 bar = "#" * int(pct / 2) print(f" {label:<20} {count:>5} {pct:5.1f}% {bar}") _STATUS_SYMBOLS: Dict[str, Tuple[str, str]] = { "ok": (" [bold green]✓[/bold green]", " [OK]"), "fail": (" [bold red]✗[/bold red]", " [FAIL]"), "warn": (" [bold yellow]![/bold yellow]", " [WARN]"), } def print_status_line(status: str, msg: str) -> None: """Print a status line: green check / red X / yellow warning, or plain [OK]/[FAIL]/[WARN].""" rich_sym, plain_sym = _STATUS_SYMBOLS.get(status, (" ?", " [?]")) if RICH_AVAILABLE: console.print(f"{rich_sym} {msg}") else: print(f"{plain_sym} {msg}") def print_summary(errors: int, warnings: int) -> None: """Print a final summary line.""" if RICH_AVAILABLE: console.print("─" * 40, style="dim") if errors == 0 and warnings == 0: console.print("[bold green]All checks passed![/bold green]") elif errors == 0: console.print(f"[bold green]All checks passed[/bold green] with [yellow]{warnings} warning(s)[/yellow]") else: console.print(f"[bold red]{errors} error(s)[/bold red], [yellow]{warnings} warning(s)[/yellow]") else: print("=" * 40) if errors == 0 and warnings == 0: print("All checks passed!") elif errors == 0: print(f"All checks passed with {warnings} warning(s)") else: print(f"{errors} error(s), {warnings} warning(s)")