"""Formatage et affichage Rich du dashboard Gitea.""" from __future__ import annotations from datetime import datetime, timezone from rich.console import Console from rich.table import Table from gitea_dashboard.collector import RepoData def _format_repo_name(repo: RepoData) -> str: """Formate le nom du repo avec les indicateurs visuels.""" indicators = [] if repo.is_fork: indicators.append("[F]") if repo.is_archived: indicators.append("[A]") if repo.is_mirror: indicators.append("[M]") if indicators: return f"{repo.name} {' '.join(indicators)}" return repo.name def _format_relative_date(iso_date: str) -> str: """Convertit une date ISO en date relative lisible.""" try: dt = datetime.fromisoformat(iso_date.replace("Z", "+00:00")) except (ValueError, AttributeError): return "" now = datetime.now(timezone.utc) delta = now - dt days = delta.days if days < 0: return "dans le futur" if days == 0: return "aujourd'hui" if days == 1: return "il y a 1j" if days < 30: return f"il y a {days}j" months = days // 30 if months < 12: return f"il y a {months}m" years = days // 365 return f"il y a {years}a" def _format_release(release: dict | None) -> str: """Formate la release pour l'affichage.""" if release is None: return "\u2014" tag = release.get("tag_name", "") published = release.get("published_at", "") if published: relative = _format_relative_date(published) if relative: return f"{tag} ({relative})" return tag def _colorize_milestone_due(due_on: str | None) -> str: """Retourne le style Rich selon la proximite de l'echeance. - Rouge : echeance depassee - Jaune : echeance dans les 7 prochains jours - Vert : echeance dans plus de 7 jours - Chaine vide : pas d'echeance definie """ if not due_on: return "" try: dt = datetime.fromisoformat(due_on.replace("Z", "+00:00")) except (ValueError, AttributeError): return "" now = datetime.now(timezone.utc) delta = dt - now days = delta.days if days < 0: return "red" if days < 7: return "yellow" return "green" def sort_repos(repos: list[RepoData], sort_key: str) -> list[RepoData]: """Trie la liste des repos selon le critere donne. Args: repos: Liste des repos a trier. sort_key: Critere de tri parmi : - "name" : alphabetique par nom (defaut) - "issues" : par nombre d'issues ouvertes (decroissant) - "release" : par date de derniere release (plus recent d'abord) - "activity" : par date du dernier commit (plus recent d'abord) """ if sort_key == "name": return sorted(repos, key=lambda r: r.name.lower()) if sort_key == "issues": return sorted(repos, key=lambda r: r.open_issues, reverse=True) if sort_key == "release": # Repos sans release en dernier (date vide = epoch 0) def release_date(r: RepoData) -> str: if r.latest_release and r.latest_release.get("published_at"): return r.latest_release["published_at"] return "" return sorted(repos, key=release_date, reverse=True) if sort_key == "activity": # Repos sans commit en dernier (date vide = epoch 0) return sorted(repos, key=lambda r: r.last_commit_date or "", reverse=True) return repos def render_dashboard( repos: list[RepoData], console: Console | None = None, sort_key: str = "name", ) -> None: """Affiche le dashboard complet dans le terminal. - Tableau principal : nom repo, indicateurs (fork/archive/mirror), issues ouvertes, derniere release (tag + date relative) - Section milestones : par repo ayant des milestones, nom, progression (closed/total), date echeance Le parametre console permet l'injection pour les tests. """ if console is None: console = Console() if not repos: console.print("Aucun repo trouve.") return # Tri des repos sorted_repos = sort_repos(repos, sort_key) # Tableau principal table = Table(title="Gitea Dashboard") table.add_column("Repo", style="bold") table.add_column("Issues", justify="right") table.add_column("Release") table.add_column("Dernier commit") for repo in sorted_repos: name = _format_repo_name(repo) issues_str = str(repo.open_issues) issues_style = "red" if repo.open_issues > 0 else "green" release_str = _format_release(repo.latest_release) commit_str = ( _format_relative_date(repo.last_commit_date) if repo.last_commit_date else "\u2014" ) table.add_row( name, f"[{issues_style}]{issues_str}[/{issues_style}]", release_str, commit_str, ) console.print(table) # Section milestones — uniquement si au moins un repo en a repos_with_milestones = [r for r in sorted_repos if r.milestones] if repos_with_milestones: console.print() console.print("[bold]Milestones[/bold]") for repo in repos_with_milestones: for ms in repo.milestones: title = ms["title"] closed = ms["closed_issues"] total = ms["open_issues"] + ms["closed_issues"] pct = round(closed / total * 100) if total > 0 else 0 line = f" {repo.name} / {title} : {closed}/{total} ({pct}%)" due_on = ms.get("due_on") if due_on: # Extraire juste la date (YYYY-MM-DD) try: dt = datetime.fromisoformat(due_on.replace("Z", "+00:00")) line += f" \u2014 echeance {dt.strftime('%Y-%m-%d')}" except (ValueError, AttributeError): pass # Coloration selon la proximite de l'echeance style = _colorize_milestone_due(due_on) if style: console.print(f"[{style}]{line}[/{style}]") else: console.print(line)