from __future__ import annotations import importlib.util import os import shutil import sys from dataclasses import dataclass from pathlib import Path from copienator.platform import find_executable, windows_filename_problems @dataclass(frozen=True) class DiagnosticCheck: name: str ok: bool detail: str required: bool = True def module_available(module: str) -> bool: try: return importlib.util.find_spec(module) is not None except (ImportError, ModuleNotFoundError, ValueError): return False def collect_diagnostics( show_personal_steps: bool, api_key_override: str = "", evaluation: Path | None = None, ) -> list[DiagnosticCheck]: checks = [ DiagnosticCheck( "Système", os.name == "nt" or sys.platform.startswith("linux") or sys.platform == "darwin", f"{sys.platform} — Linux, Windows et macOS sont pris en charge", ), DiagnosticCheck("Python", True, sys.executable), ] modules = { "numpy": "numpy", "pandas": "pandas", "matplotlib": "matplotlib", "Pillow": "PIL", "pydantic": "pydantic", "pypdf": "pypdf", "pdf2image": "pdf2image", "reportlab": "reportlab", "img2pdf": "img2pdf", "PyMuPDF": "pymupdf", "ftfy": "ftfy", "ezodf": "ezodf", "Google GenAI": "google.genai", } checks.extend( DiagnosticCheck(label, module_available(module), f"Module Python : {module}") for label, module in modules.items() ) programs = ( ("Poppler", ("pdftoppm", "pdftocairo"), True), ("LaTeX", ("pdflatex",), True), ("PDF Arranger", ("pdf-arranger", "pdfarranger"), False), ) for label, candidates, required in programs: executable = find_executable(*candidates) if label == "PDF Arranger" and not executable and sys.platform == "darwin": system_open = Path("/usr/bin/open") opener = find_executable("open") or ( str(system_open) if system_open.is_file() else None ) executable = f"{opener} (Aperçu)" if opener else None detail = executable or "Introuvable dans PATH" checks.append(DiagnosticCheck(label, executable is not None, detail, required)) api_key = api_key_override.strip() or os.environ.get("GEMINI_API_KEY") checks.append( DiagnosticCheck( "Clé Gemini", bool(api_key), "GEMINI_API_KEY est définie" if api_key else "À saisir dans le GUI ou dans l’environnement", ) ) if evaluation and (evaluation / "labels").is_file(): labels = [ line.strip() for line in (evaluation / "labels").read_text(encoding="utf-8").splitlines() if line.strip() ] unsafe = [label for label in labels if windows_filename_problems(label)] detail = ( "Tous les labels sont utilisables comme noms de fichiers" if not unsafe else "Caractères Windows interdits : " + ", ".join(unsafe[:3]) ) if len(unsafe) > 3: detail += f" (+{len(unsafe) - 3})" checks.append( DiagnosticCheck( "Labels compatibles Windows", not unsafe, detail, os.name == "nt", ) ) if show_personal_steps: checks.append( DiagnosticCheck( "gestion_classe", shutil.which("gestion_classe") is not None, shutil.which("gestion_classe") or "Introuvable dans PATH", False, ) ) try: from copienator.configuration import CURRENT_SCORE_ODS_PATH, FINAL_SCORE_ODS_PATH for label, configured_path in ( ("ODS courant", CURRENT_SCORE_ODS_PATH), ("ODS final", FINAL_SCORE_ODS_PATH), ): path = Path(configured_path).expanduser() checks.append(DiagnosticCheck(label, path.is_file(), str(path), False)) except ImportError: checks.append(DiagnosticCheck("Configuration personnelle", False, "Variables absentes", False)) return checks