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 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"), f"{sys.platform} — Linux et Windows 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": "fitz", "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 = next((shutil.which(candidate) for candidate in candidates if shutil.which(candidate)), 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