101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""
|
||
|
|
Assemble a Roux issue's Hugo page bundles from a prompts JSON file.
|
||
|
|
|
||
|
|
python3 scripts/build-issue.py --issue 02 --prompts data/prompts/issue-02.json
|
||
|
|
|
||
|
|
Reads an array of objects with keys:
|
||
|
|
slug, title, description, category, tags[], prompt, featured (optional bool)
|
||
|
|
|
||
|
|
Writes content/posts/<issue>/<slug>/index.md for each, with plate numbers assigned
|
||
|
|
by stable sort on slug (matches scripts/import-posts.py). Frontmatter shape mirrors
|
||
|
|
the existing issue-01 bundles. `featured` comes from the JSON, so re-runs never lose
|
||
|
|
the homepage flags (unlike issue 01's hand-added ones).
|
||
|
|
|
||
|
|
Images are expected at content/posts/<issue>/<slug>/<slug>.png (written there by
|
||
|
|
scripts/generate-images.py). Pass --images DIR to copy them in from a staging dir
|
||
|
|
that holds either <slug>.png or <slug>/<slug>.png.
|
||
|
|
"""
|
||
|
|
import argparse
|
||
|
|
import json
|
||
|
|
import shutil
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
SITE = Path(__file__).resolve().parent.parent
|
||
|
|
|
||
|
|
|
||
|
|
def norm_desc(desc: str) -> str:
|
||
|
|
desc = desc.strip()
|
||
|
|
if desc:
|
||
|
|
desc = desc[0].upper() + desc[1:]
|
||
|
|
if not desc.endswith((".", "!", "?")):
|
||
|
|
desc += "."
|
||
|
|
return desc.replace('"', '\\"')
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
ap = argparse.ArgumentParser()
|
||
|
|
ap.add_argument("--issue", required=True)
|
||
|
|
ap.add_argument("--prompts", required=True, type=Path)
|
||
|
|
ap.add_argument("--images", type=Path, help="staging dir to copy <slug>.png from")
|
||
|
|
args = ap.parse_args()
|
||
|
|
|
||
|
|
content = SITE / "content" / "posts" / args.issue
|
||
|
|
content.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
entries = json.loads(args.prompts.read_text(encoding="utf-8"))
|
||
|
|
entries.sort(key=lambda e: e["slug"])
|
||
|
|
|
||
|
|
missing_img = []
|
||
|
|
for idx, entry in enumerate(entries, start=1):
|
||
|
|
slug = entry["slug"]
|
||
|
|
plate = f"{idx:03d}"
|
||
|
|
title = entry["title"].strip().replace('"', '\\"')
|
||
|
|
desc = norm_desc(entry["description"])
|
||
|
|
cat = (entry.get("category") or "Uncategorised").strip()
|
||
|
|
tags = [t.strip() for t in entry.get("tags", []) if t.strip()]
|
||
|
|
|
||
|
|
bundle = content / slug
|
||
|
|
bundle.mkdir(exist_ok=True)
|
||
|
|
img_name = f"{slug}.png"
|
||
|
|
dst_img = bundle / img_name
|
||
|
|
|
||
|
|
if args.images and not dst_img.exists():
|
||
|
|
for cand in (args.images / img_name, args.images / slug / img_name):
|
||
|
|
if cand.is_file():
|
||
|
|
shutil.copy2(cand, dst_img)
|
||
|
|
break
|
||
|
|
if not dst_img.exists():
|
||
|
|
missing_img.append(slug)
|
||
|
|
|
||
|
|
cats_yaml = f' - "{cat}"'
|
||
|
|
tags_yaml = "\n".join(f' - "{t}"' for t in tags) if tags else " []"
|
||
|
|
featured_yaml = "\nfeatured: true" if entry.get("featured") else ""
|
||
|
|
|
||
|
|
md = f"""---
|
||
|
|
title: "{title}"
|
||
|
|
description: "{desc}"
|
||
|
|
plate: "{plate}"
|
||
|
|
slug: "{slug}"
|
||
|
|
issues:
|
||
|
|
- "{args.issue}"
|
||
|
|
image: "{img_name}"
|
||
|
|
weight: {idx}{featured_yaml}
|
||
|
|
categories:
|
||
|
|
{cats_yaml}
|
||
|
|
tags:
|
||
|
|
{tags_yaml}
|
||
|
|
---
|
||
|
|
"""
|
||
|
|
(bundle / "index.md").write_text(md, encoding="utf-8")
|
||
|
|
|
||
|
|
print(f"Wrote {len(entries)} bundles to {content}")
|
||
|
|
if missing_img:
|
||
|
|
print(f"WARNING: {len(missing_img)} bundles have no image yet: "
|
||
|
|
+ ", ".join(missing_img[:10]) + ("…" if len(missing_img) > 10 else ""))
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|