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()
|