up/main.py

76 lines
2.5 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""Generate the Tree of Ascension action-points totals manifest.
Scans docs/**/*.md for Markdown tasklist items (`- [ ]` / `- [x]`), counts
them per page, and writes docs/javascripts/toa-totals.json mapping each
page's site URL -> total task count.
The action-points JS (docs/javascripts/extra.js) fetches this manifest once
on first load so nav badges show correct totals BEFORE a user has visited
every page. Counts are fully static (known from the Markdown), so this has
zero runtime cost it's a build-time artifact.
Run before `zensical build`:
python3 main.py
"""
from __future__ import annotations
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parent
DOCS = ROOT / "docs"
OUT = DOCS / "javascripts" / "toa-totals.json"
# Matches a Markdown tasklist item: optional indent, list marker, [ ] or [x].
TASK_RE = re.compile(r"^\s*[-*+]\s+\[[ xX]\]\s+", re.MULTILINE)
# Matches fenced code blocks (``` or ~~~), incl. language hint, non-greedy.
# These contain tasklist *examples* (e.g. the Markdown reference doc) that render
# as plain text, NOT interactive checkboxes — so we strip them before counting.
FENCE_RE = re.compile(r"^([ \t]*)(`{3,}|~{3,}).*?\n.*?^\1\2[ \t]*$", re.MULTILINE | re.DOTALL)
def count_tasks(text: str) -> int:
"""Count real (interactive) tasklist items, excluding fenced code examples."""
stripped = FENCE_RE.sub("", text)
return len(TASK_RE.findall(stripped))
def url_for(md_path: Path) -> str:
"""Map a docs/*.md path to its built site URL (directory-style URLs).
docs/index.md -> /
docs/practices/index.md -> /practices/
docs/practices/daily-sadhana.md-> /practices/daily-sadhana/
"""
rel = md_path.relative_to(DOCS).with_suffix("")
parts = list(rel.parts)
if parts and parts[-1] == "index":
parts = parts[:-1]
path = "/" + "/".join(parts)
if not path.endswith("/"):
path += "/"
return path
def main() -> None:
totals: dict[str, int] = {}
for md in sorted(DOCS.rglob("*.md")):
text = md.read_text(encoding="utf-8")
count = count_tasks(text)
if count:
totals[url_for(md)] = count
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text(json.dumps(totals, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print(f"toa-totals: wrote {len(totals)} page(s) -> {OUT.relative_to(ROOT)}")
for url, n in sorted(totals.items()):
print(f" {url} {n}")
2026-06-02 07:54:36 +00:00
if __name__ == "__main__":
main()