#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ patch_facilitator_c4b.py — Correctif C4 (résolution des assets) ================================================================ run_render() ne transmettait jamais --assets à render_engine_v2.py, qui retombait sur son défaut /../assets. Ce défaut n'est correct que si le YAML est dans projets//outputs/ (alors ../assets = projets//assets/) — ce qui couvre le flux normal du pipeline, mais PAS un YAML isolé à la racine (golden, test manuel). Patch : run_render accepte un paramètre assets_dir optionnel. Résolu dans l'ordre : 1. assets_dir explicitement fourni par l'appelant (le pipeline normal le fournira désormais : projets//assets/). 2. Sinon, si le YAML est sous PROJECTS_DIR, le dossier assets/ du projet correspondant est déduit automatiquement. 3. Sinon (golden, test manuel hors projet), fallback sur le défaut du moteur (/../assets) — comportement inchangé pour ces cas. Tous les appelants internes de run_render (pipeline standard, flux libre, révisions, merge C3, --render CLI) sont mis à jour pour passer proj.root / "assets" quand un Project est en contexte. Usage (dossier du pipeline, single-line) : python3 patch_facilitator_c4b.py Compatible C1/C2/C3/C4 déjà appliqués (ancres indépendantes). Vérifie chaque ancre, écrit .bak-c4b, compile, idempotent. """ import py_compile import shutil import sys from pathlib import Path TARGET = Path("facilitator_v9.py") PATCHES = [ # P1 — signature + résolution + transmission --assets ( 'def run_render(yaml_path: Path) -> Optional[Path]:\n' ' section("ÉTAPE 4 — RENDER ENGINE V2")\n' ' if not Path(RENDER_ENGINE_PATH).exists():\n' ' warn(f"{RENDER_ENGINE_PATH} introuvable.")\n' ' return None\n' ' for p in [THEME_PATH, COMPONENTS_PATH, LAYOUTS_PATH]:\n' ' if not os.path.exists(p):\n' ' warn(f"Config manquante : {p}")\n' ' return None\n' ' pptx_out = yaml_path.with_suffix(".pptx")\n' ' info("Lancement de render_engine_v2.py...")\n' ' info(f"Sortie : {pptx_out}")\n' ' result = subprocess.run(\n' ' [sys.executable, RENDER_ENGINE_PATH, str(yaml_path), ' 'str(pptx_out),\n' ' "--theme", THEME_PATH, "--components", ' 'COMPONENTS_PATH,\n' ' "--layouts", LAYOUTS_PATH],\n' ' capture_output=True, text=True)\n', 'def run_render(yaml_path: Path,\n' ' assets_dir: Optional[Path] = None) -> ' 'Optional[Path]:\n' ' section("ÉTAPE 4 — RENDER ENGINE V2")\n' ' if not Path(RENDER_ENGINE_PATH).exists():\n' ' warn(f"{RENDER_ENGINE_PATH} introuvable.")\n' ' return None\n' ' for p in [THEME_PATH, COMPONENTS_PATH, LAYOUTS_PATH]:\n' ' if not os.path.exists(p):\n' ' warn(f"Config manquante : {p}")\n' ' return None\n' ' pptx_out = yaml_path.with_suffix(".pptx")\n' ' info("Lancement de render_engine_v2.py...")\n' ' info(f"Sortie : {pptx_out}")\n' ' if assets_dir is None:\n' ' # Déduction depuis PROJECTS_DIR si le YAML y vit ' '(C4b)\n' ' try:\n' ' rel = yaml_path.resolve().relative_to(\n' ' Path(PROJECTS_DIR).resolve())\n' ' assets_dir = Path(PROJECTS_DIR) / rel.parts[0] ' '/ "assets"\n' ' except ValueError:\n' ' assets_dir = None # hors projet : défaut du ' 'moteur\n' ' cmd = [sys.executable, RENDER_ENGINE_PATH, str(yaml_path),' '\n' ' str(pptx_out),\n' ' "--theme", THEME_PATH, "--components", ' 'COMPONENTS_PATH,\n' ' "--layouts", LAYOUTS_PATH]\n' ' if assets_dir is not None:\n' ' cmd += ["--assets", str(assets_dir)]\n' ' info(f"Assets : {assets_dir}")\n' ' result = subprocess.run(cmd, capture_output=True, ' 'text=True)\n', ), # P2 — CLI --render : passe le dossier assets du projet si déductible ( ' if args.render:\n' ' yaml_path = Path(args.render)\n' ' if not yaml_path.exists():\n' ' warn(f"Fichier introuvable : {yaml_path}")\n' ' sys.exit(1)\n' ' layouts = load_layouts()\n' ' is_valid, message, _ = validate_yaml(\n' ' yaml_path.read_text(encoding="utf-8"), layouts)\n' ' (ok if is_valid else warn)(f"Validation : {message}")\n' ' sys.exit(0 if run_render(yaml_path) else 1)\n', ' if args.render:\n' ' yaml_path = Path(args.render)\n' ' if not yaml_path.exists():\n' ' warn(f"Fichier introuvable : {yaml_path}")\n' ' sys.exit(1)\n' ' layouts = load_layouts()\n' ' is_valid, message, _ = validate_yaml(\n' ' yaml_path.read_text(encoding="utf-8"), layouts)\n' ' (ok if is_valid else warn)(f"Validation : {message}")\n' ' local_assets = yaml_path.parent / "assets"\n' ' assets_dir = local_assets if local_assets.is_dir() ' 'else None\n' ' sys.exit(0 if run_render(yaml_path, assets_dir) ' 'else 1)\n', ), ] # Les 3 appels internes de run_render(yaml_path) — même nom de # variable partout, différenciés par leur contexte immédiat. CALL_PATCHES = [ ( ' info("Rendu du deck COMPLET fusionné.")\n' '\n' ' manifest.file(yaml_path)\n' ' pptx_path = run_render(yaml_path)\n', ' info("Rendu du deck COMPLET fusionné.")\n' '\n' ' manifest.file(yaml_path)\n' ' pptx_path = run_render(yaml_path, proj.root / ' '"assets")\n', ), ( ' if yaml_path and yaml_data:\n' ' manifest.file(yaml_path)\n' ' pptx_path = run_render(yaml_path)\n', ' if yaml_path and yaml_data:\n' ' manifest.file(yaml_path)\n' ' pptx_path = run_render(yaml_path, proj.root ' '/ "assets")\n', ), ( ' if yaml_path and yaml_data:\n' ' manifest.file(yaml_path)\n' ' pptx_path = run_render(yaml_path)\n', ' if yaml_path and yaml_data:\n' ' manifest.file(yaml_path)\n' ' pptx_path = run_render(yaml_path, proj.root / ' '"assets")\n', ), ] MARKER = "assets_dir: Optional[Path] = None" 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é (assets_dir présent) — rien à faire.") if "def merge_revision" not in content: fail("Prérequis manquant : patch_facilitator_c3.py doit être " "appliqué avant celui-ci (C4b s'ancre sur le message de " "fusion introduit par C3).") all_patches = PATCHES + CALL_PATCHES for i, (old, _) in enumerate(all_patches, 1): n = content.count(old) if n == 0: fail("Ancre du patch %d introuvable — facilitator inattendu." " (Le fichier a peut-être déjà été modifié depuis les " "patchs C1/C2/C3.)" % i) if n > 1: fail("Ancre du patch %d non unique (%d occurrences)." % (i, n)) shutil.copy2(TARGET, str(TARGET) + ".bak-c4b") print(" + Sauvegarde : %s.bak-c4b" % TARGET) for old, new in all_patches: content = content.replace(old, new) TARGET.write_text(content, encoding="utf-8") print(" + %d patchs appliqués (2 structurels + %d appels internes)." % (len(all_patches), len(CALL_PATCHES))) try: py_compile.compile(str(TARGET), doraise=True) print(" + Compilation OK.") except py_compile.PyCompileError as e: shutil.copy2(str(TARGET) + ".bak-c4b", TARGET) fail("Erreur de compilation — fichier restauré :\n%s" % e) print("\n Pipeline normal : assets résolus automatiquement " "(projets//assets/).") print(" --render CLI : assets résolus si un dossier assets/ existe" " à côté du YAML (sinon défaut moteur /../assets).") if __name__ == "__main__": main()