@@NAME@@
@@SIG@@
@@DESC@@
#!/usr/bin/env python3
"""gen.py — the Ludic documentation generator (v2).
Single source of truth:
docs/language/\1", s)
def split_body(body):
examples = FENCE.findall(body)
body = FENCE.sub("", body)
# pull out a "Parameters:" block
params, keep = [], []
lines = body.split("\n")
i, n = 0, len(lines)
while i < n:
if lines[i].strip().lower().startswith("parameters:"):
i += 1
while i < n and lines[i].strip():
m = PARAM.match(lines[i].strip())
if m:
params.append({"name": m.group(1), "desc": codeify(m.group(2).strip())})
i += 1
else:
keep.append(lines[i]); i += 1
desc = "\n".join(keep).strip()
paras = [codeify(p.strip()) for p in re.split(r"\n\s*\n", desc) if p.strip()]
return "
".join(paras), examples, params def load_sections(): sections = [] for cat in sorted(os.listdir(LANG)): cdir = os.path.join(LANG, cat) if not os.path.isdir(cdir): continue smeta, sblurb = {}, "" sp = os.path.join(cdir, "_section.md") if os.path.exists(sp): smeta, sbody = parse_doc(sp) sblurb = codeify(sbody.strip()) entries = [] for fn in sorted(os.listdir(cdir)): if not fn.endswith(".md") or fn == "_section.md": continue meta, body = parse_doc(os.path.join(cdir, fn)) desc, examples, params = split_body(body) meta["desc_html"] = desc meta["examples"] = examples meta["params"] = params meta["tokens_list"] = meta.get("tokens", "").split() meta["related_list"] = meta.get("related", "").split() entries.append(meta) entries.sort(key=lambda e: (int(e.get("order", 999)), e.get("name", ""))) # A namespace section is discovered, not hardcoded: it is any section # whose symbols are `namespace-method`s, named by their `ns:` field. So # adding a namespace is just adding its docs dir — gen.py needs no edit. ns_name = next((e["ns"] for e in entries if e.get("kind") == "namespace-method" and e.get("ns")), None) sections.append({"id": smeta.get("id", cat), "title": smeta.get("title", cat.title()), "order": int(smeta.get("order", 999)), "blurb": sblurb, "cat": cat, "entries": entries, "ns_name": ns_name}) sections.sort(key=lambda s: (s["order"], s["title"])) return sections # --------------------------------------------------------------------------- # symbol model for the highlighter + search + cards # --------------------------------------------------------------------------- def ns_page(nsname): return "ns-" + nsname.lower() def build_symbols(sections, palette): sym = {"keywords": {}, "types": {}, "phases": {}, "builtins": {}, "nsmethods": {}, "annotations": {}, "namespaces": {}, "tips": {}, "cards": {}, "colors_page": "ns-color"} items = {} # id -> full record for search for s in sections: for e in s["entries"]: eid = e["id"]; kind = e.get("kind", ""); tip = e.get("tip", "") page = eid + ".html" items[eid] = {"id": eid, "name": e.get("name", ""), "sig": e.get("sig", ""), "kind": kind, "category": s["id"], "section": s["title"], "tip": tip, "page": page} sym["cards"][eid] = {"name": e.get("name",""), "sig": e.get("sig",""), "tip": tip, "kind": kind, "page": page, "section": s["title"]} for tok in e["tokens_list"]: if kind == "keyword": sym["keywords"][tok] = eid elif kind == "type": sym["types"][tok] = eid elif kind == "phase": sym["phases"][tok] = eid elif kind == "builtin": sym["builtins"][tok] = {"id": eid, "params": [p["name"] for p in e["params"]]} elif kind == "namespace-method": sym["nsmethods"][tok] = {"id": eid, "params": [p["name"] for p in e["params"]]} elif kind == "annotation": sym["annotations"][tok] = eid if tip: sym["tips"][tok] = tip # namespaces are discovered from the ns methods present, plus Color; each # namespace's tip falls back to its section blurb so no hardcoded list of # namespaces is needed here when a new one is added. ns_blurb = {s["ns_name"]: plain_text(s["blurb"]) for s in sections if s.get("ns_name")} nsnames = set() for tok in sym["nsmethods"]: if "." in tok: nsnames.add(tok.split(".", 1)[0]) nsnames.add("Color") for nn in sorted(nsnames): sym["namespaces"][nn] = ns_page(nn) sym["cards"][ns_page(nn)] = {"name": nn, "sig": nn + ".*", "kind": "namespace", "tip": ns_tip(nn, ns_blurb), "page": ns_page(nn) + ".html", "section": "Namespaces"} return sym, items def plain_text(s, limit=160): """The first sentence of a blurb, stripped of tags — for use as a one-line tip.""" t = re.sub(r"<[^>]+>", "", s or "").strip() m = re.match(r"(.+?[.!?])(\s|$)", t) if m: t = m.group(1) return t if len(t) <= limit else t[:limit].rsplit(" ", 1)[0] + "…" def ns_tip(nsname, ns_blurb): """A namespace's tip: an optional curated override, else its section blurb.""" return NS_TIP.get(nsname) or ns_blurb.get(nsname, "") # Optional short overrides for a namespace's one-line tip. A namespace NOT # listed here falls back to the first sentence of its section blurb (see # ns_tip), so a newly added namespace needs no edit here. Color has no docs # section of its own, so its override is required. NS_TIP = {"Color": "The named color palette."} # --------------------------------------------------------------------------- # shared chrome # --------------------------------------------------------------------------- def nav_html(cfg, links=None, cta_href="api.html"): out = [] for n in (links if links is not None else cfg["nav_links"]): cls = 'class="nav-cta" ' if n.get("href") == cta_href else "" out.append('%s' % (cls, n["href"], esc(n["label"]))) return "".join(out) def head_html(title, desc, css_file): css = open(os.path.join(ASSETS, css_file)).read() return fill("""
%s'
'%s%s' % esc(code) for code in e["examples"]) return '
@@SIG@@
@@DESC@@