chore: regroupement des scripts de patch dans patches/
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user