feat(v1.2.0): retry API, dernier commit, tri, coloration, export JSON

- client.py: _get_with_retry (max 2 retries, backoff lineaire), get_latest_commit
- collector.py: champ last_commit_date dans RepoData
- display.py: colonne "Dernier commit", _sort_repos (name/issues/release/activity),
  _colorize_milestone_due (rouge/jaune/vert selon echeance)
- cli.py: options --sort/-s et --format/-f (table/json)
- exporter.py: nouveau module, repos_to_dicts + export_json
- 88 tests (35 nouveaux), ruff clean

fixes #8, fixes #7, fixes #10, fixes #9, fixes #6

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sylvain
2026-03-12 03:58:45 +01:00
parent 19f300ccdb
commit 4c66fbe98d
10 changed files with 639 additions and 10 deletions

View File

@@ -67,7 +67,66 @@ def _format_release(release: dict | None) -> str:
return tag
def render_dashboard(repos: list[RepoData], console: Console | None = None) -> None:
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),
@@ -84,19 +143,31 @@ def render_dashboard(repos: list[RepoData], console: Console | None = None) -> N
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 repos:
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)
table.add_row(
name,
f"[{issues_style}]{issues_str}[/{issues_style}]",
release_str,
commit_str,
)
console.print(table)
@@ -125,4 +196,9 @@ def render_dashboard(repos: list[RepoData], console: Console | None = None) -> N
except (ValueError, AttributeError):
pass
console.print(line)
# 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)