Files
soleprint/soleprint/atlas2/docgen/cli/scaffold.py
2026-09-24 00:58:19 -03:00

88 lines
3.8 KiB
Python

"""
python3 -m docgen scaffold <docgen.toml> [--only NAME ...]
python3 -m docgen scaffold <docgen.toml> --list # print the spots, write nothing
Write an empty note for every spot a document has — the document's top and end,
each book's top, what came in, the page index, every page, every step, every
API call, what came out — so the ids are listed rather than guessed.
Never overwrites: a spot that already has a note is left alone, so running it
again after the sources change only adds the spots that are new. A stub is a
single HTML comment and renders nothing until somebody writes below it.
Extraction and page structure only — nothing is drawn and no output is written.
"""
import argparse
from pathlib import Path
from .common import PROG, Abort
def main(argv) -> int:
p = argparse.ArgumentParser(prog=f"{PROG} scaffold")
p.add_argument("config", type=Path, nargs="?", default=Path("docgen.toml"))
p.add_argument("--only", action="append", default=[], metavar="NAME",
help="Just these books. Repeatable.")
p.add_argument("--list", action="store_true",
help="Print every spot a note can be filed under, and write nothing — "
"the list to hand a model instead of letting it guess page ids.")
args = p.parse_args(argv)
from ..book.config import ConfigError, ProjectFile, apply_reference, load, select_parts
from ..book.document import Part, plan_spots
from ..book.notes import Notes
if not args.config.exists():
raise Abort(f"no run file at {args.config}")
try:
loaded = load(args.config)
apply_reference(loaded)
if isinstance(loaded, ProjectFile):
groups = {loaded.document.notes: select_parts(loaded, args.only)}
else:
groups = {}
for e in loaded.select(args.only):
part = Part(name=e.name, kind=e.kind, source=e.source, out=e.out,
exclude=e.exclude)
groups.setdefault(e.notes, []).append(part)
except ConfigError as e:
raise Abort(str(e)) from None
total = 0
for root, parts in groups.items():
spots = {}
for part in parts:
try:
spots[part.name] = plan_spots(part)
except Exception as e: # noqa: BLE001 - one bad source must not cost the rest
print(f" skip {part.name}: {type(e).__name__}: {str(e).splitlines()[0]}")
if args.list:
# Relative to the run file's folder, which is how a note's path is
# written in a reply — not as an absolute path on this machine.
base = args.config.resolve().parent
try:
shown = root.relative_to(base)
except ValueError:
shown = root
rows = [(f"{shown}/_intro.md", "top of the document"),
(f"{shown}/_end.md", "end of the document")]
for book, entries in spots.items():
rows.append((f"{shown}/{book}/_intro.md", f"top of book {book}"))
for spot, where in entries:
has = " (has a note)" if Notes(root).book(book, spot) else ""
rows.append((f"{shown}/{book}/{spot}.md", where + has))
width = max(len(r[0]) for r in rows)
for path, where in rows:
print(f"{path:<{width}} {where}")
continue
written = Notes(root).scaffold(spots)
total += len(written)
existing = sum(len(v) for v in spots.values()) + 2 + len(spots) - len(written)
print(f" notes {root} — {len(written)} new stub(s), {existing} already there")
for path in written[:12]:
print(f" {path.relative_to(root)}")
if len(written) > 12:
print(f" … and {len(written) - 12} more")
return 0