fix: resolve CI issues - remove unused imports and fix code quality
This commit is contained in:
251
repohealth-cli/src/repohealth/reporters/terminal.py
Normal file
251
repohealth-cli/src/repohealth/reporters/terminal.py
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
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):%}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
))
|
||||||
Reference in New Issue
Block a user