feat(cli): add --repo and --exclude filtering (fixes #5)
Add argparse-based CLI parsing with repeatable --repo/-r (include) and --exclude/-x (exclude) options. Filtering is case-insensitive substring matching, applied post-fetch in collect_all() per ADR-005. - parse_args() separated from main() for testability - main(argv=None) accepts argv for test injection - collect_all() gains optional include/exclude parameters - 14 new tests (8 filtering + 6 CLI parsing/integration) - All 51 tests pass, backward compatible (no args = v1.0.0 behavior) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -15,15 +16,50 @@ from gitea_dashboard.display import render_dashboard
|
||||
_DEFAULT_URL = "http://192.168.0.106:3000"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
"""Parse les arguments CLI.
|
||||
|
||||
Options:
|
||||
--repo / -r : noms de repos a inclure (repeatable)
|
||||
--exclude / -x : noms de repos a exclure (repeatable)
|
||||
|
||||
Returns:
|
||||
Namespace avec .repo (list[str] | None) et .exclude (list[str] | None)
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Dashboard CLI affichant l'etat des repos Gitea.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repo",
|
||||
"-r",
|
||||
action="append",
|
||||
default=None,
|
||||
help="Filtrer par nom de repo (sous-chaine, insensible a la casse). Repeatable.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exclude",
|
||||
"-x",
|
||||
action="append",
|
||||
default=None,
|
||||
help="Exclure les repos par nom (sous-chaine, insensible a la casse). Repeatable.",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> None:
|
||||
"""Point d'entree principal.
|
||||
|
||||
1. Lit GITEA_URL (defaut: http://192.168.0.106:3000) et GITEA_TOKEN (requis)
|
||||
2. Cree le GiteaClient
|
||||
3. Collecte les donnees via collect_all()
|
||||
4. Affiche via render_dashboard()
|
||||
5. Gere les erreurs : config manquante, connexion refusee, timeout
|
||||
Args:
|
||||
argv: Arguments CLI. Si None, utilise sys.argv (via argparse).
|
||||
|
||||
1. Parse les options CLI (--repo, --exclude)
|
||||
2. Lit GITEA_URL (defaut: http://192.168.0.106:3000) et GITEA_TOKEN (requis)
|
||||
3. Cree le GiteaClient
|
||||
4. Collecte les donnees via collect_all() avec filtres
|
||||
5. Affiche via render_dashboard()
|
||||
6. Gere les erreurs : config manquante, connexion refusee, timeout
|
||||
"""
|
||||
args = parse_args(argv)
|
||||
console = Console(stderr=True)
|
||||
|
||||
token = os.environ.get("GITEA_TOKEN")
|
||||
@@ -38,7 +74,7 @@ def main() -> None:
|
||||
client = GiteaClient(url, token)
|
||||
|
||||
try:
|
||||
repos = collect_all(client)
|
||||
repos = collect_all(client, include=args.repo, exclude=args.exclude)
|
||||
except requests.ConnectionError:
|
||||
console.print("[red]Erreur : connexion refusee. Verifiez l'URL et le serveur Gitea.[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -22,13 +22,37 @@ class RepoData:
|
||||
milestones: list[dict] # [{title, open_issues, closed_issues, due_on}]
|
||||
|
||||
|
||||
def collect_all(client: GiteaClient) -> list[RepoData]:
|
||||
"""Collecte les donnees de tous les repos.
|
||||
def _matches_any(name: str, patterns: list[str]) -> bool:
|
||||
"""Return True if name contains any of the patterns (case-insensitive)."""
|
||||
name_lower = name.lower()
|
||||
return any(p.lower() in name_lower for p in patterns)
|
||||
|
||||
Pour chaque repo : enrichit avec release et milestones.
|
||||
Calcule open_issues = open_issues_count - open_pr_counter.
|
||||
|
||||
def collect_all(
|
||||
client: GiteaClient,
|
||||
include: list[str] | None = None,
|
||||
exclude: list[str] | None = None,
|
||||
) -> list[RepoData]:
|
||||
"""Collecte les donnees des repos, avec filtrage optionnel.
|
||||
|
||||
Args:
|
||||
client: Client API Gitea.
|
||||
include: Si fourni, ne garde que les repos dont le nom contient
|
||||
au moins une des sous-chaines (insensible a la casse).
|
||||
exclude: Si fourni, exclut les repos dont le nom contient
|
||||
au moins une des sous-chaines (insensible a la casse).
|
||||
|
||||
Ordre d'application : include d'abord (si present), puis exclude.
|
||||
Si include est None ou vide, tous les repos sont inclus avant l'etape exclude.
|
||||
"""
|
||||
repos = client.get_repos()
|
||||
|
||||
# Filtrage post-fetch : l'API Gitea ne supporte pas le filtre par nom
|
||||
if include:
|
||||
repos = [r for r in repos if _matches_any(r["name"], include)]
|
||||
if exclude:
|
||||
repos = [r for r in repos if not _matches_any(r["name"], exclude)]
|
||||
|
||||
result: list[RepoData] = []
|
||||
|
||||
for repo in repos:
|
||||
|
||||
Reference in New Issue
Block a user