143 lines
5.1 KiB
Python
143 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
patch_facilitator_c4.py — Chantier C4 (Assets & images)
|
||
=======================================================
|
||
Patch strict de facilitator_v9.py :
|
||
P1. validate_freeform accepte le nouveau type de bloc « image ».
|
||
P2. /lire inventorie aussi projets/<slug>/assets/ (images + dimensions)
|
||
et injecte la liste dans le contexte Narrator — le Narrator peut
|
||
alors référencer les fichiers par nom dans les layouts image_*.
|
||
Le dossier assets/ est créé au premier /lire s'il n'existe pas.
|
||
P3. Insère list_assets() (inventaire, dimensions via Pillow si
|
||
disponible, sinon noms seuls).
|
||
|
||
Usage (dossier du pipeline, single-line) :
|
||
python3 patch_facilitator_c4.py
|
||
Compatible tout ordre avec C1/C2/C3. Vérifie chaque ancre, écrit
|
||
.bak-fc4, compile, idempotent.
|
||
"""
|
||
|
||
import py_compile
|
||
import shutil
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
TARGET = Path("facilitator_v9.py")
|
||
|
||
FUNC = '''
|
||
def list_assets(proj: "Project"):
|
||
"""Inventaire des images de projets/<slug>/assets/ (chantier C4).
|
||
Crée le dossier au premier appel. Retourne un bloc texte destiné au
|
||
contexte Narrator, ou une chaîne vide si aucune image."""
|
||
assets = proj.root / "assets"
|
||
assets.mkdir(exist_ok=True)
|
||
exts = {".png", ".jpg", ".jpeg", ".webp"}
|
||
files = sorted(p for p in assets.iterdir()
|
||
if p.suffix.lower() in exts and p.is_file())
|
||
if not files:
|
||
return ""
|
||
lines = ["IMAGES DISPONIBLES DANS assets/ (utilisables dans les "
|
||
"layouts image_split / image_full et le bloc freeform "
|
||
"image, par leur nom de fichier) :"]
|
||
for p in files:
|
||
dims = ""
|
||
try:
|
||
from PIL import Image
|
||
with Image.open(p) as im:
|
||
dims = f" ({im.size[0]}×{im.size[1]})"
|
||
except Exception:
|
||
pass
|
||
lines.append(f"- {p.name}{dims}")
|
||
return "\\n".join(lines)
|
||
|
||
'''
|
||
|
||
PATCHES = [
|
||
# P1 — type de bloc freeform « image »
|
||
(
|
||
'VALID_BLOCK_TYPES = {"title", "heading", "text", "stat", '
|
||
'"circle",\n'
|
||
' "badge", "card", "rect", "line"}\n',
|
||
'VALID_BLOCK_TYPES = {"title", "heading", "text", "stat", '
|
||
'"circle",\n'
|
||
' "badge", "card", "rect", "line", "image"}\n',
|
||
),
|
||
# P2 — /lire : inventaire des assets
|
||
(
|
||
' elif cmd == "/lire":\n'
|
||
' context, new_files = load_documents(proj, '
|
||
'loaded_docs)\n'
|
||
' if not new_files:\n'
|
||
' info("Aucun nouveau document dans inputs/.")\n'
|
||
' continue\n',
|
||
' elif cmd == "/lire":\n'
|
||
' context, new_files = load_documents(proj, '
|
||
'loaded_docs)\n'
|
||
' assets_info = list_assets(proj)\n'
|
||
' if assets_info:\n'
|
||
' context = ((context or "").strip()\n'
|
||
' + "\\n\\n" + assets_info).strip()\n'
|
||
' ok(f"{len(assets_info.splitlines()) - 1} '
|
||
'image(s) dans assets/.")\n'
|
||
' if not new_files and not assets_info:\n'
|
||
' info("Aucun nouveau document dans inputs/ ni '
|
||
'image dans assets/.")\n'
|
||
' continue\n',
|
||
),
|
||
]
|
||
|
||
MARKER = "def list_assets"
|
||
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é (list_assets 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-fc4")
|
||
print(" + Sauvegarde : %s.bak-fc4" % 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(" + 2 patchs + list_assets appliqués.")
|
||
try:
|
||
py_compile.compile(str(TARGET), doraise=True)
|
||
print(" + Compilation OK.")
|
||
except py_compile.PyCompileError as e:
|
||
shutil.copy2(str(TARGET) + ".bak-fc4", TARGET)
|
||
fail("Erreur de compilation — fichier restauré :\n%s" % e)
|
||
print("\n Dépose tes images dans projets/<slug>/assets/ puis /lire "
|
||
"dans la session.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|