#!/usr/bin/env python3 """Vérifications du manuscrit : stubs restants, figures manquantes, scripts des figures, citations/références non définies (log), \\synth sans encadré, équations hors chapitre, CSV de vérification. Usage : tools/check_manuscript.py [--log build/main.log] [--no-log] [--strict]""" import re, sys, csv, pathlib, argparse ROOT = pathlib.Path(__file__).resolve().parents[1] ap = argparse.ArgumentParser(); ap.add_argument("--log"); ap.add_argument("--no-log", action="store_true"); ap.add_argument("--strict", action="store_true") a = ap.parse_args() problems, notes = [], [] chapters = sorted((ROOT/"manuscript/chapters").glob("ch*.tex")) apps = sorted((ROOT/"manuscript/appendices").glob("*.tex")) texts = {p: p.read_text(encoding="utf-8") for p in chapters + apps} # 1. stubs stubs = [p.name for p, t in texts.items() if "\\stubchapter" in t] notes.append(f"{len(chapters)-sum(1 for s in stubs if s.startswith('ch'))}/{len(chapters)} chapitres rédigés ; stubs restants : {', '.join(stubs) or 'aucun'}") if a.strict and stubs: problems.append(f"stubs restants : {stubs}") # 2. figures incluses existent + inscrites dans verification/figures.csv figreg = {} fcsv = ROOT/"verification/figures.csv" if fcsv.exists(): for row in csv.DictReader(fcsv.open(encoding="utf-8")): figreg[row["fichier"]] = row incl = re.compile(r"\\includegraphics(?:\[[^\]]*\])?\{([^}]+)\}") for p, t in texts.items(): for f in incl.findall(t): cands = [ROOT/"assets/figures"/f, ROOT/"assets/figures"/(f+".pdf"), ROOT/"assets/figures"/(f+".png")] if not any(c.exists() for c in cands): problems.append(f"{p.name}: figure absente {f}") key = f if f.endswith((".pdf",".png")) else f+".pdf" if key not in figreg and not f.startswith("logo"): problems.append(f"{p.name}: figure {f} non inscrite dans verification/figures.csv") for key, row in figreg.items(): s = ROOT/row["script"] if row["script"] and not s.exists(): problems.append(f"figures.csv: script introuvable {row['script']} ({key})") if "schema" not in (row.get("type_donnees") or "").lower() and "tikz" not in key.lower() and not (ROOT/"assets/figures"/key).exists(): problems.append(f"figures.csv: fichier inscrit mais absent {key}") # 3. compteurs n_fig = sum(len(incl.findall(t)) + t.count("\\begin{tikzpicture}") for t in texts.values()) n_tab = sum(t.count("\\begin{table}") + t.count("\\begin{longtable}") for t in texts.values()) n_eq = sum(t.count("\\begin{equation}") + t.count("\\begin{align}") + sum(ln.count("\\\\") for ln in re.findall(r"\\begin\{align\}(.*?)\\end\{align\}", t, re.S)) for t in texts.values()) n_ex = sum(t.count("\\begin{exercice}") for t in texts.values()) n_words = sum(len(re.sub(r"\\[a-zA-Z]+|[{}%$&_^~]", " ", t).split()) for p, t in texts.items() if p.name.startswith("ch")) notes.append(f"figures≈{n_fig} tableaux≈{n_tab} équations≈{n_eq} exercices={n_ex} mots(chapitres)≈{n_words}") # 4. log LaTeX if a.log and not a.no_log: lp = ROOT/a.log if not lp.exists(): problems.append(f"log introuvable : {a.log}") else: L = lp.read_text(errors="ignore") for m in sorted(set(re.findall(r"LaTeX Warning: (Reference|Citation) `([^']+)' .*undefined", L))): problems.append(f"{m[0]} non définie : {m[1]}") if "There were undefined references" in L: problems.append("références non définies (voir log)") if re.search(r"^!", L, re.M): problems.append("erreur LaTeX fatale dans le log") m = re.findall(r"Output written on .*? \((\d+) pages", L); notes.append(f"pages PDF : {m[-1] if m else '?'}") notes.append(f"overfull hbox : {L.count('Overfull \\\\hbox')}") # 5. CSV de vérification bien formés for name, cols in {"verification/claims.csv": ["id"], "verification/calculations.csv": ["id"], "verification/figures.csv": ["fichier"], "sources/registry.csv": ["id"]}.items(): f = ROOT/name if not f.exists(): problems.append(f"{name} manquant"); continue r = csv.DictReader(f.open(encoding="utf-8")) if not r.fieldnames or any(c not in r.fieldnames for c in cols): problems.append(f"{name}: en-tête invalide") else: notes.append(f"{name}: {sum(1 for _ in r)} lignes") print("\n".join("• " + n for n in notes)) if problems: print(f"\n{len(problems)} problème(s) :"); print("\n".join(" ✗ " + p for p in problems[:80])) sys.exit(1 if (a.strict or a.log) else 0) print("\n✓ aucun problème bloquant")