"""Point d'entree pour le CLI gitea-dashboard.""" from __future__ import annotations import argparse import os import sys import requests from rich.console import Console from gitea_dashboard.client import GiteaClient from gitea_dashboard.collector import collect_all, collect_milestones from gitea_dashboard.config import load_config, merge_config from gitea_dashboard.display import ( AVAILABLE_COLUMNS, parse_columns, render_dashboard, render_milestones, sort_repos, ) from gitea_dashboard.exporter import export_json, export_milestones_json _DEFAULT_URL = "http://192.168.0.106:3000" 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) --milestones : affiche la vue milestones au lieu du dashboard repos --columns : liste des colonnes a afficher --config : chemin vers un fichier de configuration YAML alternatif Returns: Namespace avec les options parsees. """ 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.", ) parser.add_argument( "--sort", "-s", choices=["name", "issues", "release", "activity"], default="name", help="Critere de tri des repos (defaut: name).", ) parser.add_argument( "--format", "-f", choices=["table", "json"], default="table", dest="format", help="Format de sortie (defaut: table).", ) parser.add_argument( "--health", action="store_true", default=False, help="Verifie la connexion Gitea et affiche la version. Exit 0 si OK, 1 sinon.", ) parser.add_argument( "--no-desc", action="store_true", default=False, help="Masque la colonne Description dans le tableau.", ) parser.add_argument( "--config", default=None, help="Chemin vers un fichier de configuration YAML alternatif.", ) parser.add_argument( "--milestones", action="store_true", default=False, help="Affiche la vue milestones au lieu du dashboard repos.", ) parser.add_argument( "--columns", default=None, help="Colonnes a afficher (separees par virgules). Prefixe '-' pour exclure. 'help' pour lister.", ) return parser.parse_args(argv) def _resolve_config(args: argparse.Namespace) -> argparse.Namespace: """Resout la configuration en appliquant la priorite CLI > env > config > defaults. 1. Charge le fichier config (args.config ou chemin par defaut) 2. Lit les variables d'environnement pertinentes 3. Fusionne avec les valeurs CLI 4. Retourne un Namespace enrichi """ file_config = load_config(args.config) env_vars: dict = {} gitea_url_env = os.environ.get("GITEA_URL") if gitea_url_env: env_vars["url"] = gitea_url_env gitea_auth_env = os.environ.get("GITEA_TOKEN") if gitea_auth_env: env_vars["auth"] = gitea_auth_env cli_args: dict = {} if args.sort != "name": cli_args["sort"] = args.sort if args.repo is not None: cli_args["include"] = args.repo if args.exclude is not None: cli_args["exclude"] = args.exclude if args.no_desc: cli_args["no_desc"] = True defaults = { "url": _DEFAULT_URL, "sort": "name", "no_desc": False, } merged = merge_config(cli_args, env_vars, file_config, defaults) args.resolved_url = merged.get("url", _DEFAULT_URL) args.resolved_auth = merged.get("auth") args.sort = merged.get("sort", args.sort) if merged.get("include") and args.repo is None: args.repo = merged["include"] if merged.get("exclude") and args.exclude is None: args.exclude = merged["exclude"] if merged.get("no_desc") and not args.no_desc: args.no_desc = merged["no_desc"] return args def _run_health_check(client: GiteaClient, console: Console) -> None: """Execute le health check et affiche les resultats. 1. Appelle client.get_version() -> affiche "Gitea vX.Y.Z" 2. Appelle client.get_repos() -> affiche "N repos accessibles" """ version_info = client.get_version() version = version_info.get("version", "inconnue") console.print(f"Gitea v{version}") repos = client.get_repos() console.print(f"{len(repos)} repos accessibles") def _print_columns_help(console: Console) -> None: """Affiche les colonnes disponibles.""" console.print("Colonnes disponibles :") for name, desc in AVAILABLE_COLUMNS.items(): console.print(f" {name:15s} {desc}") def main(argv: list[str] | None = None) -> None: """Point d'entree principal. Args: argv: Arguments CLI. Si None, utilise sys.argv (via argparse). 1. Parse les options CLI 2. Resout la configuration (CLI > env > config > defaults) 3. Cree le GiteaClient 4. Route vers le mode appropriate (health, milestones, dashboard) 5. Gere les erreurs : config manquante, connexion refusee, timeout """ args = parse_args(argv) console = Console(stderr=True) try: args = _resolve_config(args) except (FileNotFoundError, ValueError) as exc: console.print(f"[red]Erreur config : {exc}[/red]") sys.exit(1) # Handle --columns help before auth check if args.columns is not None: cols = parse_columns(args.columns, no_desc=args.no_desc) if cols == ["__help__"]: _print_columns_help(Console()) return else: cols = None auth = args.resolved_auth if hasattr(args, "resolved_auth") and args.resolved_auth else None if not auth: auth = os.environ.get("GITEA_TOKEN") if not auth: console.print( "[red]Erreur : GITEA_TOKEN non defini. Exportez la variable d'environnement.[/red]" ) sys.exit(1) url = ( args.resolved_url if hasattr(args, "resolved_url") else os.environ.get("GITEA_URL", _DEFAULT_URL) ) client = GiteaClient(url, auth) try: if args.health: _run_health_check(client, console) return if args.milestones: milestones = collect_milestones(client, include=args.repo, exclude=args.exclude) if args.format == "json": print(export_milestones_json(milestones)) # noqa: T201 else: render_milestones(milestones) return 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) except requests.Timeout: console.print( "[red]Erreur : delai d'attente depasse. Le serveur Gitea ne repond pas.[/red]" ) sys.exit(1) except requests.RequestException as exc: # Ne jamais afficher le token dans les messages d'erreur msg = str(exc) if auth in msg: msg = msg.replace(auth, "***") console.print(f"[red]Erreur API : {msg}[/red]") sys.exit(1) if args.format == "json": sorted_repos = sort_repos(repos, args.sort) print(export_json(sorted_repos)) # noqa: T201 else: # Resolve columns for dashboard active_cols = cols if cols is not None else parse_columns(None, no_desc=args.no_desc) render_dashboard( repos, sort_key=args.sort, show_description="description" in active_cols, columns=active_cols, )