#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ patch_facilitator_c2.py — Chantier C2 (Preview PNG) =================================================== Patch strict de facilitator_v9.py : P1. Config : PREVIEW_SCRIPT (.env, défaut ./preview.sh). P2. Insère run_preview() + maybe_preview() avant la section ARCHIVAGE TRILIUM (asynchrone par défaut, log dédié). P3-P6. Propose les aperçus aux 4 sorties PPTX : flux standard, flux libre, révision ciblée, révision complète. P7. CLI : --preview (mode bloquant, pour usage direct). Usage (dossier du pipeline, single-line) : python3 patch_facilitator_c2.py Compatible avant/après le patch C1 (ancres indépendantes). Vérifie chaque ancre, écrit .bak, compile, idempotent. """ import py_compile import shutil import sys from pathlib import Path TARGET = Path("facilitator_v9.py") FUNC = ''' def run_preview(pptx_path: Path, wait: bool = False) -> bool: """Aperçus PNG par slide via preview.sh (chantier C2). wait=False : lancement en arrière-plan (le DS218 est lent), sortie consignée dans _preview.log. wait=True : bloquant (CLI).""" script = Path(PREVIEW_SCRIPT) if not script.exists(): warn(f"{PREVIEW_SCRIPT} introuvable — aperçus indisponibles.") return False out_dir = pptx_path.parent / f"{pptx_path.stem}_previews" if wait: info("Génération des aperçus (quelques minutes sur le NAS)...") r = subprocess.run([str(script), str(pptx_path)], capture_output=True, text=True) if r.returncode == 0: ok(f"Aperçus : {out_dir}") ok(f"Galerie : {out_dir / 'index.html'}") return True warn("Échec de la génération des aperçus :") print(textwrap.indent((r.stderr or r.stdout or "?").strip(), " ")) return False log = pptx_path.parent / f"{pptx_path.stem}_preview.log" with open(log, "w", encoding="utf-8") as lf: subprocess.Popen([str(script), str(pptx_path)], stdout=lf, stderr=subprocess.STDOUT) info(f"Aperçus en arrière-plan → {out_dir}") info(f"Suivi : {log}") return True def maybe_preview(pptx_path) -> None: """Propose la génération des aperçus après une sortie PPTX.""" if not pptx_path or not Path(PREVIEW_SCRIPT).exists(): return if ask("Générer les aperçus PNG ? (o/N) :").lower() in ( "o", "oui", "y", "yes"): run_preview(pptx_path, wait=False) ''' PATCHES = [ # P1 — config ( 'LAYOUTS_PATH = os.getenv("LAYOUTS_PATH", "layouts_v2.yaml")\n', 'LAYOUTS_PATH = os.getenv("LAYOUTS_PATH", "layouts_v2.yaml")\n' 'PREVIEW_SCRIPT = os.getenv("PREVIEW_SCRIPT", "./preview.sh")' ' # C2\n', ), # P3 — flux standard (indentation 12) ( ' ok(f"Fichier PPTX : {pptx_path}")\n' ' ok(f"Taille : {pptx_path.stat().st_size/1024:.1f}' ' Ko")\n' ' else:\n' ' warn("Le PPTX n\'a pas pu être généré.")\n' ' info(f"Le YAML est dans : {proj.outputs}")\n', ' ok(f"Fichier PPTX : {pptx_path}")\n' ' ok(f"Taille : {pptx_path.stat().st_size/1024:.1f}' ' Ko")\n' ' maybe_preview(pptx_path)\n' ' else:\n' ' warn("Le PPTX n\'a pas pu être généré.")\n' ' info(f"Le YAML est dans : {proj.outputs}")\n', ), # P4 — flux libre (indentation 20) ( ' ok(f"Fichier PPTX : {pptx_path}")\n' ' ok(f"Taille : ' '{pptx_path.stat().st_size/1024:.1f} Ko")\n', ' ok(f"Fichier PPTX : {pptx_path}")\n' ' ok(f"Taille : ' '{pptx_path.stat().st_size/1024:.1f} Ko")\n' ' maybe_preview(pptx_path)\n', ), # P5 — révision ciblée ( ' ok(f"PPTX des slides révisées : {pptx_path}")\n' ' info("Ouvre ce fichier et copie-colle les slides dans ' 'ton deck maître.")\n', ' ok(f"PPTX des slides révisées : {pptx_path}")\n' ' info("Ouvre ce fichier et copie-colle les slides dans ' 'ton deck maître.")\n' ' maybe_preview(pptx_path)\n', ), # P6 — révision complète ( ' section("DECK COMPLET RÉGÉNÉRÉ")\n' ' ok(f"Nouveau PPTX complet : {pptx_path}")\n', ' section("DECK COMPLET RÉGÉNÉRÉ")\n' ' ok(f"Nouveau PPTX complet : {pptx_path}")\n' ' maybe_preview(pptx_path)\n', ), # P7a — argument CLI ( ' parser.add_argument("--render", metavar="YAML",\n' ' help="Rendu direct d\'un YAML existant, ' 'sans agents")\n', ' parser.add_argument("--render", metavar="YAML",\n' ' help="Rendu direct d\'un YAML existant, ' 'sans agents")\n' ' parser.add_argument("--preview", metavar="PPTX",\n' ' help="Aperçus PNG d\'un PPTX existant ' '(C2)")\n', ), # P7b — traitement CLI ( ' if args.render:\n', ' if args.preview:\n' ' p = Path(args.preview)\n' ' if not p.exists():\n' ' warn(f"Fichier introuvable : {p}")\n' ' sys.exit(1)\n' ' sys.exit(0 if run_preview(p, wait=True) else 1)\n' '\n' ' if args.render:\n', ), ] MARKER = "def run_preview" ANCHOR_SECTION = "# ARCHIVAGE TRILIUM" def fail(msg): print(" ! %s" % msg) sys.exit(1) def main(): if not TARGET.exists(): fail("%s introuvable — lancer depuis le dossier du pipeline." % TARGET) content = TARGET.read_text(encoding="utf-8") if MARKER in content: fail("Déjà patché (run_preview présent) — rien à faire.") for i, (old, _) in enumerate(PATCHES, 1): n = content.count(old) if n == 0: fail("Ancre du patch %d introuvable — facilitator inattendu." % i) if n > 1: fail("Ancre du patch %d non unique (%d occurrences)." % (i, n)) if content.count(ANCHOR_SECTION) != 1: fail("Ancre de section ARCHIVAGE TRILIUM introuvable ou non unique.") shutil.copy2(TARGET, str(TARGET) + ".bak-c2") print(" + Sauvegarde : %s.bak-c2" % TARGET) for old, new in PATCHES: content = content.replace(old, new) lines = content.split("\n") idx = next(i for i, l in enumerate(lines) if ANCHOR_SECTION in l) ins = idx - 1 if lines[idx - 1].startswith("# ───") else idx lines[ins:ins] = FUNC.split("\n") content = "\n".join(lines) TARGET.write_text(content, encoding="utf-8") print(" + 7 patchs appliqués.") try: py_compile.compile(str(TARGET), doraise=True) print(" + Compilation OK.") except py_compile.PyCompileError as e: shutil.copy2(str(TARGET) + ".bak-c2", TARGET) fail("Erreur de compilation — fichier restauré :\n%s" % e) print("\n Test direct : python3 facilitator_v9.py --preview " "") print(" Config .env optionnelle : PREVIEW_SCRIPT=./preview.sh") if __name__ == "__main__": main()