Files
sliding-automation/patch_facilitator_c3.py
T

195 lines
7.4 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
patch_facilitator_c3.py — Chantier C3 (fusion des révisions ciblées)
====================================================================
Patch strict de facilitator_v9.py :
P1. Insère merge_revision() : remplace, dans le dernier YAML complet
(project_state.json → dernier_yaml), les slides régénérées par la
révision ciblée (appariement par position), puis re-rend le deck
ENTIER. Le PPTX partiel « à recoller » disparaît du flux.
P2. Branche la fusion dans run_full_pipeline_pass juste avant le
rendu (suffix revision_ciblee uniquement).
P3. En cas de fusion réussie, l'état du projet est persisté comme un
deck complet (dernier_yaml/dernier_pptx à jour).
P4. Message utilisateur complété au site révision ciblée.
Échec de fusion (pas de dernier_yaml, YAML illisible…) : comportement
actuel conservé à l'identique (PPTX partiel + message de recollage).
Usage (dossier du pipeline, single-line) :
python3 patch_facilitator_c3.py
Compatible avant/après les patchs C1 et C2 (ancres indépendantes).
Vérifie chaque ancre, écrit .bak-c3, compile, idempotent.
"""
import py_compile
import shutil
import sys
from pathlib import Path
TARGET = Path("facilitator_v9.py")
FUNC = '''
def merge_revision(proj: "Project", partial_yaml_path: Path):
"""Fusion YAML des révisions ciblées (chantier C3).
Remplace dans le dernier YAML complet les slides régénérées
(appariement par position ; positions inconnues ajoutées en fin).
Retourne le chemin du YAML complet fusionné, ou None si fusion
impossible (l'appelant conserve alors le flux partiel actuel)."""
state = proj.load_state()
last = state.get("dernier_yaml") or ""
if not last or not Path(last).exists():
warn("Fusion : pas de YAML complet précédent — PPTX partiel "
"conservé.")
return None
try:
full = yaml.safe_load(Path(last).read_text(encoding="utf-8"))
part = yaml.safe_load(
partial_yaml_path.read_text(encoding="utf-8"))
except yaml.YAMLError as e:
warn(f"Fusion : YAML illisible ({e}).")
return None
if not isinstance(full, dict) or not full.get("slides"):
warn("Fusion : le YAML précédent ne contient pas de slides.")
return None
news = {}
for s in (part or {}).get("slides", []):
if isinstance(s, dict) and s.get("position"):
news[int(s["position"])] = s
if not news:
warn("Fusion : aucune slide positionnée dans la révision.")
return None
merged, replaced = [], 0
for i, s in enumerate(full["slides"]):
pos = int(s.get("position", i + 1)) if isinstance(s, dict) \
else i + 1
if pos in news:
merged.append(news.pop(pos))
replaced += 1
else:
merged.append(s)
for pos in sorted(news):
merged.append(news[pos])
full["slides"] = merged
out = proj.out("revision_fusion", "yaml")
out.write_text(
yaml.safe_dump(full, allow_unicode=True, sort_keys=False,
default_flow_style=False, width=100),
encoding="utf-8")
ok(f"Fusion : {replaced} slide(s) remplacée(s), "
f"{len(merged)} au total → {out.name}")
return out
'''
PATCHES = [
# P2 — fusion avant le rendu (revision_ciblee)
(
' # Renommer la sortie selon le suffixe demandé\n'
' if suffix != "input" and yaml_path:\n'
' new_path = proj.out(suffix, "yaml")\n'
' yaml_path.rename(new_path)\n'
' yaml_path = new_path\n'
'\n'
' manifest.file(yaml_path)\n',
' # Renommer la sortie selon le suffixe demandé\n'
' if suffix != "input" and yaml_path:\n'
' new_path = proj.out(suffix, "yaml")\n'
' yaml_path.rename(new_path)\n'
' yaml_path = new_path\n'
'\n'
' merged = False\n'
' if suffix == "revision_ciblee" and yaml_path:\n'
' fused = merge_revision(proj, yaml_path)\n'
' if fused:\n'
' yaml_path, merged = fused, True\n'
' info("Rendu du deck COMPLET fusionné.")\n'
'\n'
' manifest.file(yaml_path)\n',
),
# P3 — persistance d'état si fusion
(
' elif suffix == "revision_ciblee":\n'
' persist_generation(\n'
' proj, markdown=markdown,\n'
' journal_entry="Révision ciblée — slides '
'régénérées séparément.")\n',
' elif suffix == "revision_ciblee":\n'
' if merged:\n'
' persist_generation(\n'
' proj, markdown=markdown, yaml_path=yaml_path,\n'
' pptx_path=pptx_path,\n'
' journal_entry="Révision ciblée fusionnée — '
'deck complet régénéré.")\n'
' else:\n'
' persist_generation(\n'
' proj, markdown=markdown,\n'
' journal_entry="Révision ciblée — slides '
'régénérées séparément.")\n',
),
# P4 — message utilisateur
(
' info("Ouvre ce fichier et copie-colle les slides dans '
'ton deck maître.")\n',
' info("Ouvre ce fichier et copie-colle les slides dans '
'ton deck maître.")\n'
' info("(Si la fusion YAML a réussi — voir ci-dessus — '
'le PPTX est déjà le deck complet.)")\n',
),
]
MARKER = "def merge_revision"
ANCHOR_SECTION = "# FLUX LIBRE — THE FREE DESIGNER"
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é (merge_revision 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 FREE DESIGNER introuvable ou non unique.")
shutil.copy2(TARGET, str(TARGET) + ".bak-fc3")
print(" + Sauvegarde : %s.bak-fc3" % 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(" + 3 patchs + merge_revision appliqués.")
try:
py_compile.compile(str(TARGET), doraise=True)
print(" + Compilation OK.")
except py_compile.PyCompileError as e:
shutil.copy2(str(TARGET) + ".bak-fc3", TARGET)
fail("Erreur de compilation — fichier restauré :\n%s" % e)
print("\n La prochaine révision ciblée régénérera le deck complet "
"(revision_fusion.yaml).")
if __name__ == "__main__":
main()