"""Terminal reporter using Rich library.""" from typing import Optional from rich.box import ROUNDED from rich.console import Console from rich.panel import Panel from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn from rich.table import Table from rich.text import Text from repohealth.models.result import RepositoryResult class TerminalReporter: """Reporter for terminal output using Rich.""" RISK_COLORS = { "critical": "red", "high": "orange3", "medium": "yellow", "low": "green", "unknown": "grey" } def __init__(self, console: Optional[Console] = None): """Initialize the reporter. Args: console: Rich Console instance. """ self.console = console or Console() def display_result(self, result: RepositoryResult) -> None: """Display a complete analysis result. Args: result: RepositoryResult to display. """ self.console.print(Panel( self._get_overview_text(result), title="Repository Health Analysis", subtitle=f"Analyzed: {result.analyzed_at.strftime('%Y-%m-%d %H:%M')}", expand=False )) self._display_risk_summary(result) self._display_file_stats(result) self._display_hotspots(result) self._display_suggestions(result) def _get_overview_text(self, result: RepositoryResult) -> Text: """Get overview text for the result. Args: result: RepositoryResult to display. Returns: Rich Text object. """ text = Text() text.append("Repository: ", style="bold") text.append(f"{result.repository_path}\n") text.append("Files Analyzed: ", style="bold") text.append(f"{result.files_analyzed}\n") text.append("Total Commits: ", style="bold") text.append(f"{result.total_commits}\n") text.append("Unique Authors: ", style="bold") text.append(f"{result.unique_authors}\n") text.append("Overall Bus Factor: ", style="bold") text.append(f"{result.overall_bus_factor:.2f}\n") text.append("Gini Coefficient: ", style="bold") text.append(f"{result.gini_coefficient:.3f}\n") return text def _display_risk_summary(self, result: RepositoryResult) -> None: """Display risk summary. Args: result: RepositoryResult to display. """ summary = result.risk_summary if not summary: return table = Table(title="Risk Summary", box=ROUNDED) table.add_column("Risk Level", justify="center") table.add_column("Count", justify="center") table.add_column("Percentage", justify="center") levels = ["critical", "high", "medium", "low"] for level in levels: count = summary.get(level, 0) pct = summary.get(f"percentage_{level}", 0) color = self.RISK_COLORS.get(level, "grey") table.add_row( f"[{color}]{level.upper()}[/]", str(count), f"{pct:.1f}%" ) self.console.print(Panel(table, title="Risk Overview", expand=False)) def _display_file_stats(self, result: RepositoryResult) -> None: """Display file statistics table. Args: result: RepositoryResult to display. """ if not result.files: return table = Table(title="Top Files by Risk", box=ROUNDED) table.add_column("File", style="dim", width=40) table.add_column("Commits", justify="right") table.add_column("Authors", justify="right") table.add_column("Bus Factor", justify="right") table.add_column("Risk", justify="center") table.add_column("Top Author %", justify="right") sorted_files = sorted( result.files, key=lambda x: ( {"critical": 0, "high": 1, "medium": 2, "low": 3}.get(x.get("risk_level"), 4), -x.get("bus_factor", 1) ) )[:15] for file_data in sorted_files: risk_level = file_data.get("risk_level", "unknown") color = self.RISK_COLORS.get(risk_level, "grey") table.add_row( file_data.get("path", "")[:40], str(file_data.get("total_commits", 0)), str(file_data.get("num_authors", 0)), f"{file_data.get('bus_factor', 1):.2f}", f"[{color}]{risk_level.upper()}[/]", f"{file_data.get('top_author_share', 0):.0%}" ) self.console.print(Panel(table, title="File Analysis", expand=False)) def _display_hotspots(self, result: RepositoryResult) -> None: """Display knowledge hotspots. Args: result: RepositoryResult to display. """ if not result.hotspots: return table = Table(title="Knowledge Hotspots", box=ROUNDED) table.add_column("File", style="dim", width=35) table.add_column("Top Author", width=20) table.add_column("Ownership", justify="right") table.add_column("Bus Factor", justify="right") table.add_column("Risk", justify="center") for hotspot in result.hotspots[:10]: color = self.RISK_COLORS.get(hotspot.risk_level, "grey") table.add_row( hotspot.file_path[:35], hotspot.top_author[:20], f"{hotspot.top_author_share:.0%}", f"{hotspot.bus_factor:.2f}", f"[{color}]{hotspot.risk_level.upper()}[/]" ) self.console.print(Panel(table, title="Hotspots", expand=False)) def _display_suggestions(self, result: RepositoryResult) -> None: """Display diversification suggestions. Args: result: RepositoryResult to display. """ if not result.suggestions: return table = Table(title="Diversification Suggestions", box=ROUNDED) table.add_column("Priority", width=10) table.add_column("File", style="dim", width=30) table.add_column("Action", width=40) priority_colors = { "critical": "red", "high": "orange3", "medium": "yellow" } for suggestion in result.suggestions[:10]: color = priority_colors.get(suggestion.priority, "grey") table.add_row( f"[{color}]{suggestion.priority.upper()}[/]", suggestion.file_path[:30], suggestion.action[:40] ) self.console.print(Panel(table, title="Suggestions", expand=False)) def display_progress(self, message: str) -> Progress: """Display a progress indicator. Args: message: Progress message. Returns: Progress instance for updating. """ return Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TaskProgressColumn(), console=self.console ) def display_error(self, message: str) -> None: """Display an error message. Args: message: Error message to display. """ self.console.print(Panel( Text(message, style="red"), title="Error", expand=False )) def display_warning(self, message: str) -> None: """Display a warning message. Args: message: Warning message to display. """ self.console.print(Panel( Text(message, style="yellow"), title="Warning", expand=False )) def display_info(self, message: str) -> None: """Display an info message. Args: message: Info message to display. """ self.console.print(Panel( Text(message, style="blue"), title="Info", expand=False ))