mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-03-09 07:15:51 -05:00
120 lines
4.1 KiB
Python
120 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Check that figures use div syntax (fig-cap and fig-alt on the div), not old-style
|
|
markdown image or chunk options.
|
|
|
|
We standardize on:
|
|
::: {#fig-xxx fig-env="figure" fig-pos="htb" fig-cap="..." fig-alt="..."}
|
|
 OR ```{python} / ```{.tikz} block
|
|
:::
|
|
|
|
This script fails if it finds:
|
|
1. Markdown image figures: {#fig-...} (caption/alt on image; no wrapper div)
|
|
2. Chunk options: #| fig-cap= or #| fig-alt= (YAML options on a code chunk instead of div)
|
|
|
|
Usage (from repo root or book/):
|
|
python3 book/tools/scripts/content/check_figure_div_syntax.py
|
|
python3 tools/scripts/content/check_figure_div_syntax.py -d quarto/contents/
|
|
|
|
Pre-commit: run from book/ with -d quarto/contents/ (default).
|
|
|
|
Exit: 0 if no violations, 1 if any (with message to use div syntax).
|
|
"""
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Default: when run from book/, scan quarto/contents/
|
|
DEFAULT_DIR = Path("quarto/contents")
|
|
|
|
# Markdown image with #fig- on the image (old style). Caption in brackets, path in parens, then {#fig-...}
|
|
MARKDOWN_IMAGE_FIG = re.compile(r"!\[.*\]\s*\([^)]+\)\s*\{#fig-")
|
|
|
|
# Chunk option fig-cap or fig-alt (we use div attributes only)
|
|
CHUNK_FIG_OPTION = re.compile(r"^#\|\s*(fig-cap|fig-alt)\s*[:=]")
|
|
|
|
|
|
def scan_file(qmd_path: Path, contents_dir: Path) -> list[tuple[int, str, str]]:
|
|
"""Return list of (line_1based, kind, line_stripped) for violations."""
|
|
violations = []
|
|
try:
|
|
text = qmd_path.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return violations
|
|
for i, line in enumerate(text.splitlines(), start=1):
|
|
if MARKDOWN_IMAGE_FIG.search(line):
|
|
violations.append((i, "markdown-image-fig", line.strip()[:80]))
|
|
elif CHUNK_FIG_OPTION.search(line):
|
|
violations.append((i, "chunk-fig-option", line.strip()[:80]))
|
|
return violations
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description="Enforce figure div syntax (no ![](){#fig-}, no #| fig-cap/fig-alt)."
|
|
)
|
|
parser.add_argument(
|
|
"-d",
|
|
"--directory",
|
|
type=Path,
|
|
default=DEFAULT_DIR,
|
|
help=f"Directory containing .qmd files (default: {DEFAULT_DIR})",
|
|
)
|
|
parser.add_argument(
|
|
"-q",
|
|
"--quiet",
|
|
action="store_true",
|
|
help="Only exit 1; minimal output",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
cwd = Path.cwd()
|
|
# Pre-commit runs from repo root; manual run may be from book/. Find book/quarto/contents.
|
|
if (cwd / "book" / "quarto" / "contents").is_dir():
|
|
base = cwd / "book"
|
|
elif (cwd / "quarto" / "contents").is_dir():
|
|
base = cwd
|
|
else:
|
|
base = cwd
|
|
content_dir = (base / args.directory).resolve()
|
|
if not content_dir.is_dir():
|
|
if not args.quiet:
|
|
print(f"Directory not found: {content_dir}", file=sys.stderr)
|
|
return 1
|
|
|
|
all_violations: list[tuple[Path, list[tuple[int, str, str]]]] = []
|
|
for qmd in sorted(content_dir.rglob("*.qmd")):
|
|
v = scan_file(qmd, content_dir)
|
|
if v:
|
|
all_violations.append((qmd, v))
|
|
|
|
if not all_violations:
|
|
return 0
|
|
|
|
if args.quiet:
|
|
return 1
|
|
|
|
print("Figure div syntax check failed: use div syntax for all figures.", file=sys.stderr)
|
|
print(" Use: ::: {#fig-xxx fig-env=\"figure\" fig-pos=\"htb\" fig-cap=\"...\" fig-alt=\"...\"}", file=sys.stderr)
|
|
print(" <content:  or code block>", file=sys.stderr)
|
|
print(" :::", file=sys.stderr)
|
|
print(" Do NOT use: {#fig-...} or #| fig-cap= / #| fig-alt=", file=sys.stderr)
|
|
print(" See .claude/rules/book-prose.md Section 6 (Visuals & Assets).", file=sys.stderr)
|
|
print(file=sys.stderr)
|
|
for qmd, violations in all_violations:
|
|
try:
|
|
rel = qmd.relative_to(base)
|
|
except ValueError:
|
|
rel = qmd
|
|
print(f" {rel}", file=sys.stderr)
|
|
for line_no, kind, snippet in violations:
|
|
label = "markdown-image" if kind == "markdown-image-fig" else "chunk fig-cap/fig-alt"
|
|
print(f" L{line_no} ({label}): {snippet}...", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|