docs(api): per-symbol pages, fuzzy search, deep token linking, hover cards
All checks were successful
docs / build-and-deploy (push) Successful in 2s
All checks were successful
docs / build-and-deploy (push) Successful in 2s
Rebuild the API Reference around one page per symbol and richer, verified content.
Pages & navigation
- One HTML page per symbol (kw-*, type-*, phase-*, screen-*, fn-*, annot-*, op-*)
instead of a single scrolling page; namespace overview pages (ns-screen …
ns-color) and a searchable index (api.html) with client-side fuzzy search.
- Sticky-header scroll offset (scroll-margin) so a jumped-to entry/param/color is
never hidden, plus a flash highlight on the scrolled-to target.
Deep linking in every snippet & example
- Namespace members split: `Screen`→namespace page, `fill_rectangle`→method page;
`Color`→palette page, `Charcoal`→its swatch — separately.
- Named arguments (`width:`) link to that parameter's anchor on the method page.
- Hover any token for a summary card built from the real API data (symbols.json).
Content & coverage
- Full authoritative surface documented from the compiler: every keyword, type,
the 6 phases (Start/Input/FixedUpdate/Update/LateUpdate/Render, each its own
page), all 22 annotations, namespace methods with parameter docs, builtins,
the world_* reflection ABI, networking, operators — 155 symbols.
- Longer, clearer explanations; "model"/"model instance" terminology, not "entity";
descriptive identifiers in every example (Position{column,row}, Velocity{delta_x,
delta_y}, Health{current,maximum}, Player/Enemy) — no Pos/Seg/x/dx.
- Accuracy fixes from compiler ground-truth: world_count() takes no arg,
world_query_next(property, cursor) arg order, event fields bind by name; dropped
`when` and `module` (not in the self-hosted parser).
Tooling
- inventory.json + check.py: coverage guard (every symbol has a page), duplicate-
token guard, and broken-link guard — fail CI so docs can't drift.
- validate.py: compiles every ```ludic example against bin/ludicc (158 compile).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
25f987e30d
commit
3c7ec9b016
172 changed files with 5240 additions and 895 deletions
|
|
@ -1,78 +1,87 @@
|
|||
#!/usr/bin/env python3
|
||||
"""gen.py — the Ludic documentation generator.
|
||||
"""gen.py — the Ludic documentation generator (v2).
|
||||
|
||||
Single source of truth:
|
||||
docs/language/<category>/<id>.md one file per keyword / type / builtin /
|
||||
namespace method / operator / annotation
|
||||
docs/language/<category>/_section.md section title + blurb + order
|
||||
docs/language/colors/palette.json the named-color palette (from palette.py)
|
||||
docs/site/site.json landing-page messaging (hero, features, …)
|
||||
docs/site/snippets/*.ludic the code snippets shown on the landing page
|
||||
docs/language/<category>/<id>.md one file per symbol (front-matter + body)
|
||||
docs/language/<category>/_section.md section title/blurb/order
|
||||
docs/language/colors/palette.json the named-color palette
|
||||
docs/site/site.json landing-page messaging
|
||||
docs/site/snippets/*.ludic real programs shown on the landing page
|
||||
|
||||
Outputs (into --out, default build/pages) — the whole pages-branch payload:
|
||||
api.html the full API Reference, one entry per symbol
|
||||
index.html the landing page (hero + showcase from real .ludic files)
|
||||
ludic-highlight.js the highlighter, its symbol tables generated from the above
|
||||
symbols.json the machine-readable symbol index (also useful to editors)
|
||||
index.html the landing page
|
||||
api.html the reference index, with fuzzy search
|
||||
<id>.html ONE page per symbol (kw-handler.html, screen-clear.html…)
|
||||
ns-<name>.html one overview page per namespace (Screen/Color/Input/…)
|
||||
ludic-highlight.js the highlighter — its symbol tables, tips, per-item link
|
||||
targets, per-parameter anchors and hover-card data all
|
||||
generated from the sources above
|
||||
symbols.json the machine-readable index (drives search + hover cards)
|
||||
.nojekyll
|
||||
|
||||
Nothing here is hand-maintained twice: add a symbol file and it appears in the
|
||||
reference, is recognized + tipped + linked in every snippet, and lands in
|
||||
symbols.json — automatically. Zero third-party dependencies (stdlib only).
|
||||
Every keyword/type/phase/builtin/namespace-method/annotation/operator/color is
|
||||
addressable by its own page; named parameters and named colors are addressable
|
||||
by anchor. Python standard library only.
|
||||
"""
|
||||
import json, os, html, re, sys, argparse
|
||||
import json, os, html, re, argparse
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
ASSETS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets")
|
||||
LANG = os.path.join(ROOT, "docs", "language")
|
||||
SITE = os.path.join(ROOT, "docs", "site")
|
||||
HEADER_OFFSET = 84 # sticky nav height; used for scroll-margin
|
||||
|
||||
def esc(s): return html.escape(s, quote=False)
|
||||
def esc(s): return html.escape(s or "", quote=False)
|
||||
def escattr(s): return html.escape(s or "", quote=True)
|
||||
|
||||
def fill(tmpl, mapping):
|
||||
"""Placeholder substitution that never collides with % or { } in CSS/JS."""
|
||||
for k, v in mapping.items():
|
||||
tmpl = tmpl.replace("@@" + k + "@@", str(v))
|
||||
return tmpl
|
||||
|
||||
def pct(tmpl, mapping):
|
||||
"""Substitute only %(name)s markers; leave bare % (CSS 100%, code % 2) alone.
|
||||
Replacement values are inserted literally and never re-scanned."""
|
||||
return re.sub(r"%\((\w+)\)s", lambda m: mapping[m.group(1)], tmpl)
|
||||
# ---------------------------------------------------------------------------
|
||||
# parsing
|
||||
# ---------------------------------------------------------------------------
|
||||
FENCE = re.compile(r"```ludic\n(.*?)\n```", re.S)
|
||||
PARAM = re.compile(r"^-\s*`?(\w+)`?\s*[—-]+\s*(.*)$")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# front-matter + body parsing (no yaml dependency)
|
||||
# ---------------------------------------------------------------------------
|
||||
def parse_doc(path):
|
||||
text = open(path, encoding="utf-8").read()
|
||||
meta, body = {}, text
|
||||
if text.startswith("---"):
|
||||
end = text.find("\n---", 3)
|
||||
if end != -1:
|
||||
fm = text[3:end].strip("\n")
|
||||
for line in text[3:end].strip("\n").split("\n"):
|
||||
if line.strip() and ":" in line:
|
||||
k, v = line.split(":", 1)
|
||||
meta[k.strip()] = v.strip()
|
||||
body = text[end + 4:].lstrip("\n")
|
||||
for line in fm.split("\n"):
|
||||
if not line.strip() or ":" not in line:
|
||||
continue
|
||||
k, v = line.split(":", 1)
|
||||
meta[k.strip()] = v.strip()
|
||||
return meta, body
|
||||
|
||||
FENCE = re.compile(r"```ludic\n(.*?)\n```", re.S)
|
||||
def codeify(s):
|
||||
return re.sub(r"`([^`]+)`", r"<code>\1</code>", s)
|
||||
|
||||
def split_body(body):
|
||||
"""Return (description_html, [examples]) — fences pulled out as examples."""
|
||||
examples = FENCE.findall(body)
|
||||
desc = FENCE.sub("", body).strip()
|
||||
# author convenience: `code` -> <code>code</code> (leaves existing tags alone)
|
||||
desc = re.sub(r"`([^`]+)`", r"<code>\1</code>", desc)
|
||||
paras = [p.strip() for p in re.split(r"\n\s*\n", desc) if p.strip()]
|
||||
desc_html = "</p><p>".join(paras)
|
||||
return desc_html, examples
|
||||
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 "</p><p>".join(paras), examples, params
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# load the symbol model
|
||||
# ---------------------------------------------------------------------------
|
||||
def load_sections():
|
||||
sections = []
|
||||
for cat in sorted(os.listdir(LANG)):
|
||||
|
|
@ -80,416 +89,304 @@ def load_sections():
|
|||
if not os.path.isdir(cdir):
|
||||
continue
|
||||
smeta, sblurb = {}, ""
|
||||
secpath = os.path.join(cdir, "_section.md")
|
||||
if os.path.exists(secpath):
|
||||
smeta, sbody = parse_doc(secpath)
|
||||
sblurb = re.sub(r"`([^`]+)`", r"<code>\1</code>", sbody.strip())
|
||||
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 os.listdir(cdir):
|
||||
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 = split_body(body)
|
||||
desc, examples, params = split_body(body)
|
||||
meta["desc_html"] = desc
|
||||
meta["examples"] = examples
|
||||
meta["tokens_list"] = meta.get("tokens", "").split() if meta.get("tokens") else []
|
||||
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", "")))
|
||||
sections.append({
|
||||
"id": smeta.get("id", cat),
|
||||
"title": smeta.get("title", cat.title()),
|
||||
"order": int(smeta.get("order", 999)),
|
||||
"blurb": sblurb,
|
||||
"entries": entries,
|
||||
})
|
||||
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})
|
||||
sections.sort(key=lambda s: (s["order"], s["title"]))
|
||||
return sections
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# build the highlighter symbol tables from the model
|
||||
# symbol model for the highlighter + search + cards
|
||||
# ---------------------------------------------------------------------------
|
||||
def build_symbols(sections):
|
||||
def ns_page(nsname): return "ns-" + nsname.lower()
|
||||
|
||||
def build_symbols(sections, palette):
|
||||
sym = {"keywords": {}, "types": {}, "phases": {}, "builtins": {},
|
||||
"nsmethods": {}, "annotations": {}, "tips": {},
|
||||
"namespaces": [], "colors_anchor": "colors", "annotations_anchor": "annotations"}
|
||||
namespaces = set()
|
||||
"nsmethods": {}, "annotations": {}, "namespaces": {}, "tips": {},
|
||||
"cards": {}, "colors_page": "ns-color"}
|
||||
items = {} # id -> full record for search
|
||||
for s in sections:
|
||||
for e in s["entries"]:
|
||||
kind = e.get("kind", "")
|
||||
anchor = e["id"]
|
||||
tip = e.get("tip", "")
|
||||
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] = anchor
|
||||
elif kind == "type":
|
||||
sym["types"][tok] = anchor
|
||||
elif kind == "phase":
|
||||
sym["phases"][tok] = anchor
|
||||
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] = anchor
|
||||
sym["builtins"][tok] = {"id": eid, "params": [p["name"] for p in e["params"]]}
|
||||
elif kind == "namespace-method":
|
||||
sym["nsmethods"][tok] = anchor
|
||||
if "." in tok:
|
||||
namespaces.add(tok.split(".", 1)[0])
|
||||
sym["nsmethods"][tok] = {"id": eid, "params": [p["name"] for p in e["params"]]}
|
||||
elif kind == "annotation":
|
||||
sym["annotations"][tok] = anchor
|
||||
if tip:
|
||||
sym["tips"][tok] = tip
|
||||
if s["id"] == "colors":
|
||||
sym["colors_anchor"] = "colors"
|
||||
if s["id"] == "annotations":
|
||||
sym["annotations_anchor"] = "annotations"
|
||||
namespaces.add("Color") # Color.* is recognized and linked to the palette
|
||||
sym["namespaces"] = sorted(namespaces)
|
||||
return sym
|
||||
sym["annotations"][tok] = eid
|
||||
if tip: sym["tips"][tok] = tip
|
||||
# namespaces: Screen/Input/Random/Map from ns methods, plus Color
|
||||
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.get(nn, ""), "page": ns_page(nn) + ".html",
|
||||
"section": "Namespaces"}
|
||||
return sym, items
|
||||
|
||||
NS_TIP = {"Screen": "The 2D drawing surface.", "Color": "The named color palette.",
|
||||
"Input": "Reading the keyboard.", "Random": "The seeded, deterministic RNG.",
|
||||
"Map": "The character-grid tilemap."}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# render the API Reference
|
||||
# shared chrome
|
||||
# ---------------------------------------------------------------------------
|
||||
def render_entry(e):
|
||||
ex = ""
|
||||
for code in e.get("examples", []):
|
||||
ex += '<pre data-lang="ludic" class="ex">' + esc(code) + "</pre>"
|
||||
desc = e.get("desc_html", "")
|
||||
return (
|
||||
'<div class="entry" id="{id}">'
|
||||
'<div class="entry-head"><h3>{name}</h3><a class="anchor" href="#{id}">#</a></div>'
|
||||
'<code class="sig">{sig}</code>'
|
||||
'<p>{desc}</p>{ex}</div>'
|
||||
).format(id=e["id"], name=esc(e.get("name", "")), sig=esc(e.get("sig", "")),
|
||||
desc=desc, ex=ex)
|
||||
def nav_html(cfg, active=None):
|
||||
out = []
|
||||
for n in cfg["nav_links"]:
|
||||
cls = 'class="nav-cta" ' if n.get("href") == "api.html" else ""
|
||||
out.append('<a %shref="%s">%s</a>' % (cls, n["href"], esc(n["label"])))
|
||||
return "".join(out)
|
||||
|
||||
def render_palette(palette):
|
||||
out = ['<div class="swatches">']
|
||||
for grp in palette["groups"]:
|
||||
out.append('<div class="swatch-group"><h4>' + esc(grp["name"]) + '</h4><div class="swatch-row">')
|
||||
for col in grp["colors"]:
|
||||
h = col["hex"]
|
||||
out.append(
|
||||
'<div class="swatch"><span class="chip" style="background:#{h}"></span>'
|
||||
'<span class="cname">Color.{n}</span><span class="chex">#{h}</span></div>'
|
||||
.format(h=h, n=esc(col["name"])))
|
||||
out.append("</div></div>")
|
||||
out.append("</div>")
|
||||
return "\n".join(out)
|
||||
|
||||
def render_section(s, palette):
|
||||
if s["id"] == "colors":
|
||||
body = render_palette(palette)
|
||||
else:
|
||||
body = "\n".join(render_entry(e) for e in s["entries"])
|
||||
return ('<section class="ref-sec" id="{id}"><h2>{title}</h2>'
|
||||
'<p class="sec-blurb">{blurb}</p>{body}</section>').format(
|
||||
id=s["id"], title=esc(s["title"]), blurb=s["blurb"], body=body)
|
||||
|
||||
def render_api(sections, palette, cfg):
|
||||
css = open(os.path.join(ASSETS, "api.css")).read()
|
||||
nav = '<nav class="side">' + "".join(
|
||||
'<a href="#{i}">{t}</a>'.format(i=s["id"], t=esc(s["title"])) for s in sections) + "</nav>"
|
||||
content = "\n".join(render_section(s, palette) for s in sections)
|
||||
navlinks = "".join(
|
||||
'<a {cls}href="{h}">{l}</a>'.format(
|
||||
h=n["href"], l=esc(n["label"]),
|
||||
cls='class="nav-cta" ' if n.get("href") == "api.html" else "")
|
||||
for n in cfg["nav_links"])
|
||||
tmpl = """<!doctype html>
|
||||
def head_html(title, desc, css_file):
|
||||
css = open(os.path.join(ASSETS, css_file)).read()
|
||||
return fill("""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Ludic — API Reference</title>
|
||||
<meta name="description" content="The complete Ludic API Reference: every keyword, type, builtin, the Screen/Color/Input/Random/Map namespaces, and the full named-color palette.">
|
||||
<title>@@TITLE@@</title>
|
||||
<meta name="description" content="@@DESC@@">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
@@CSS@@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="nav">
|
||||
<div class="wrap nav-in">
|
||||
<a class="brand" href="index.html"><span class="logo">L</span> @@BRAND@@</a>
|
||||
<nav class="nav-links">@@NAVLINKS@@</nav>
|
||||
</div>
|
||||
</header>
|
||||
<div class="wrap ref-layout">
|
||||
@@NAV@@
|
||||
<main class="ref-main">
|
||||
<div class="ref-intro">
|
||||
<div class="kicker">Reference</div>
|
||||
<h1>API Reference</h1>
|
||||
<p>Every keyword, type, builtin, namespace and color in Ludic. In any code sample across this site, hover a token and click to jump straight to its entry here.</p>
|
||||
</div>
|
||||
@@CONTENT@@
|
||||
</main>
|
||||
</div>
|
||||
<script src="ludic-highlight.js"></script>
|
||||
<script>
|
||||
Ludic.highlightAll();
|
||||
const secs = [...document.querySelectorAll('.ref-sec')];
|
||||
const navlinks = [...document.querySelectorAll('.side a')];
|
||||
const spy = new IntersectionObserver((entries)=>{
|
||||
entries.forEach(e=>{ if(e.isIntersecting){
|
||||
navlinks.forEach(a=>a.classList.toggle('active', a.getAttribute('href')==='#'+e.target.id));
|
||||
}});
|
||||
},{rootMargin:'-10% 0px -80% 0px'});
|
||||
secs.forEach(s=>spy.observe(s));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return fill(tmpl, dict(CSS=css, BRAND=esc(cfg["brand"]), NAVLINKS=navlinks, NAV=nav, CONTENT=content))
|
||||
</head>""", {"TITLE": esc(title), "DESC": escattr(desc), "CSS": css})
|
||||
|
||||
def topbar(cfg):
|
||||
return ('<header class="nav"><div class="wrap nav-in">'
|
||||
'<a class="brand" href="index.html"><span class="logo">L</span> %s</a>'
|
||||
'<nav class="nav-links">%s</nav></div></header>') % (esc(cfg["brand"]), nav_html(cfg))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# render the landing page
|
||||
# per-item page
|
||||
# ---------------------------------------------------------------------------
|
||||
def read_snippet(rel):
|
||||
return open(os.path.join(ROOT, rel), encoding="utf-8").read().rstrip("\n")
|
||||
def render_params(e):
|
||||
if not e["params"]:
|
||||
return ""
|
||||
rows = "".join(
|
||||
'<div class="param" id="param-%s"><code class="pname">%s</code>'
|
||||
'<span class="pdesc">%s</span></div>' % (p["name"], esc(p["name"]), p["desc"])
|
||||
for p in e["params"])
|
||||
return '<div class="params"><h2>Parameters</h2>%s</div>' % rows
|
||||
|
||||
def render_examples(e):
|
||||
if not e["examples"]:
|
||||
return ""
|
||||
blocks = "".join('<pre data-lang="ludic">%s</pre>' % esc(code) for code in e["examples"])
|
||||
return '<div class="examples"><h2>Example</h2>%s</div>' % blocks
|
||||
|
||||
def render_related(e, items):
|
||||
rel = [r for r in e["related_list"] if r in items]
|
||||
if not rel:
|
||||
return ""
|
||||
links = "".join('<a class="rel" href="%s.html">%s</a>' % (r, esc(items[r]["name"])) for r in rel)
|
||||
return '<div class="related"><h2>Related</h2><div class="rel-row">%s</div></div>' % links
|
||||
|
||||
def item_page(e, section, cfg, items):
|
||||
kindlabel = {"namespace-method": "method", "builtin": "function"}.get(e.get("kind",""), e.get("kind",""))
|
||||
body = fill("""@@TOPBAR@@
|
||||
<main class="wrap item">
|
||||
<div class="crumbs"><a href="api.html">API Reference</a> <span>›</span> <a href="@@SECHREF@@">@@SECTION@@</a> <span>›</span> <span class="here">@@NAME@@</span></div>
|
||||
<div class="item-head">
|
||||
<span class="kind-badge kind-@@KIND@@">@@KINDLABEL@@</span>
|
||||
<h1 id="top">@@NAME@@</h1>
|
||||
</div>
|
||||
<code class="sig">@@SIG@@</code>
|
||||
<div class="desc"><p>@@DESC@@</p></div>
|
||||
@@PARAMS@@
|
||||
@@EXAMPLES@@
|
||||
@@RELATED@@
|
||||
<a class="back" href="api.html">← All symbols</a>
|
||||
</main>
|
||||
<script src="ludic-highlight.js"></script>
|
||||
<script>Ludic.highlightAll(); Ludic.installCards(); Ludic.flashTarget();</script>
|
||||
</body></html>""", {
|
||||
"TOPBAR": topbar(cfg),
|
||||
"SECHREF": "ns-" + section["cat"] + ".html" if section["cat"] in ("screen","input","random","map") else "api.html#" + section["id"],
|
||||
"SECTION": esc(section["title"]),
|
||||
"NAME": esc(e.get("name","")),
|
||||
"KIND": e.get("kind","").replace("namespace-method","method"),
|
||||
"KINDLABEL": esc(kindlabel),
|
||||
"SIG": esc(e.get("sig","")),
|
||||
"DESC": e.get("desc_html","") or esc(e.get("tip","")),
|
||||
"PARAMS": render_params(e),
|
||||
"EXAMPLES": render_examples(e),
|
||||
"RELATED": render_related(e, items),
|
||||
})
|
||||
return head_html(e.get("name","") + " — Ludic", e.get("tip",""), "item.css") + "\n<body>\n" + body
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# namespace overview pages
|
||||
# ---------------------------------------------------------------------------
|
||||
def ns_overview_page(nsname, section, cfg):
|
||||
rows = ""
|
||||
for e in section["entries"]:
|
||||
rows += ('<a class="ns-method" href="%s.html"><code class="nm-sig">%s</code>'
|
||||
'<span class="nm-tip">%s</span></a>') % (e["id"], esc(e.get("sig","")), esc(e.get("tip","")))
|
||||
body = fill("""@@TOPBAR@@
|
||||
<main class="wrap item">
|
||||
<div class="crumbs"><a href="api.html">API Reference</a> <span>›</span> <span class="here">@@NAME@@</span></div>
|
||||
<div class="item-head"><span class="kind-badge kind-namespace">namespace</span><h1 id="top">@@NAME@@</h1></div>
|
||||
<p class="ns-blurb">@@BLURB@@</p>
|
||||
<div class="ns-methods">@@ROWS@@</div>
|
||||
<a class="back" href="api.html">← All symbols</a>
|
||||
</main>
|
||||
<script src="ludic-highlight.js"></script>
|
||||
<script>Ludic.installCards(); Ludic.flashTarget();</script>
|
||||
</body></html>""", {"TOPBAR": topbar(cfg), "NAME": esc(nsname), "BLURB": section["blurb"], "ROWS": rows})
|
||||
return head_html(nsname + " — Ludic", NS_TIP.get(nsname,""), "item.css") + "\n<body>\n" + body
|
||||
|
||||
def color_page(palette, cfg):
|
||||
groups = ""
|
||||
for grp in palette["groups"]:
|
||||
sw = ""
|
||||
for col in grp["colors"]:
|
||||
h = col["hex"]; nm = col["name"]; aid = nm.lower()
|
||||
sw += ('<div class="swatch" id="%s"><span class="chip" style="background:#%s"></span>'
|
||||
'<span class="cname">Color.%s</span><span class="chex">#%s</span></div>'
|
||||
) % (aid, h, esc(nm), h)
|
||||
groups += '<div class="swatch-group"><h3>%s</h3><div class="swatch-row">%s</div></div>' % (esc(grp["name"]), sw)
|
||||
body = fill("""@@TOPBAR@@
|
||||
<main class="wrap item">
|
||||
<div class="crumbs"><a href="api.html">API Reference</a> <span>›</span> <span class="here">Color</span></div>
|
||||
<div class="item-head"><span class="kind-badge kind-namespace">namespace</span><h1 id="top">Color</h1></div>
|
||||
<p class="ns-blurb">@@BLURB@@ Every <code>Color.Name</code> lowers to a plain <code>0xRRGGBB</code> integer at compile time — no runtime cost. @@COUNT@@ names are built in.</p>
|
||||
@@GROUPS@@
|
||||
<a class="back" href="api.html">← All symbols</a>
|
||||
</main>
|
||||
<script src="ludic-highlight.js"></script>
|
||||
<script>Ludic.installCards(); Ludic.flashTarget();</script>
|
||||
</body></html>""", {"TOPBAR": topbar(cfg), "BLURB": "The named color palette.",
|
||||
"COUNT": palette["count"], "GROUPS": groups})
|
||||
return head_html("Color — Ludic", "The Ludic named-color palette.", "item.css") + "\n<body>\n" + body
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# api index with fuzzy search
|
||||
# ---------------------------------------------------------------------------
|
||||
def api_index(sections, cfg):
|
||||
cards = ""
|
||||
for s in sections:
|
||||
if s["cat"] == "colors":
|
||||
cards += ('<section class="idx-sec" data-sec="%s"><h2 id="%s">%s</h2>'
|
||||
'<p class="sec-blurb">%s</p><div class="idx-grid">'
|
||||
'<a class="idx-item" href="ns-color.html" data-name="Color" data-tip="The named color palette.">'
|
||||
'<code>Color</code><span>The named color palette (221 names).</span></a></div></section>'
|
||||
) % (s["id"], s["id"], esc(s["title"]), s["blurb"])
|
||||
continue
|
||||
rows = ""
|
||||
for e in s["entries"]:
|
||||
rows += ('<a class="idx-item" href="%s.html" data-name="%s" data-tip="%s"><code>%s</code><span>%s</span></a>'
|
||||
) % (e["id"], escattr(e.get("name","")), escattr(e.get("tip","")),
|
||||
esc(e.get("name","")), esc(e.get("tip","")))
|
||||
cards += ('<section class="idx-sec" data-sec="%s"><h2 id="%s">%s</h2>'
|
||||
'<p class="sec-blurb">%s</p><div class="idx-grid">%s</div></section>'
|
||||
) % (s["id"], s["id"], esc(s["title"]), s["blurb"], rows)
|
||||
body = fill("""@@TOPBAR@@
|
||||
<main class="wrap ref">
|
||||
<div class="ref-intro">
|
||||
<div class="kicker">Reference</div>
|
||||
<h1>API Reference</h1>
|
||||
<p>Every keyword, type, phase, builtin, namespace method, annotation and color in Ludic — each on its own page. Search, or browse by section. In any code sample across this site, hover a token for a summary and click to jump to its page.</p>
|
||||
<input id="search" class="search" type="search" placeholder="Search symbols… (e.g. handler, fill_rectangle, @Sync)" autocomplete="off" autofocus>
|
||||
<div id="noresults" class="noresults" hidden>No symbols match.</div>
|
||||
</div>
|
||||
@@CARDS@@
|
||||
</main>
|
||||
<script src="ludic-highlight.js"></script>
|
||||
<script>Ludic.installCards(); Ludic.installSearch();</script>
|
||||
</body></html>""", {"TOPBAR": topbar(cfg), "CARDS": cards})
|
||||
return head_html("Ludic — API Reference", "The complete Ludic API Reference: every keyword, type, builtin, namespace, annotation and color, each on its own page, with fuzzy search.", "item.css") + "\n<body>\n" + body
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# landing page (data-driven, unchanged structure; highlighter now deep-links)
|
||||
# ---------------------------------------------------------------------------
|
||||
def read_snippet(rel): return open(os.path.join(ROOT, rel), encoding="utf-8").read().rstrip("\n")
|
||||
|
||||
def render_index(cfg):
|
||||
css = open(os.path.join(ASSETS, "site.css")).read()
|
||||
hero = cfg["hero"]
|
||||
navlinks = "".join(
|
||||
'<a {cls}href="{h}">{l}</a>'.format(
|
||||
h=n["href"], l=esc(n["label"]),
|
||||
cls='class="nav-cta" ' if n.get("href") == "api.html" else "")
|
||||
for n in cfg["nav_links"])
|
||||
# hero snippet
|
||||
navlinks = nav_html(cfg)
|
||||
hero_code = esc(read_snippet(hero["snippet"]))
|
||||
# pipeline
|
||||
stages = ""
|
||||
for st in hero["pipeline"]:
|
||||
hl = " hl" if st == "ludicc" else ""
|
||||
stages += '<span class="stage%s">%s</span>' % (hl, esc(st))
|
||||
if st != hero["pipeline"][-1]:
|
||||
stages += '<span class="arrow">→</span>'
|
||||
# features
|
||||
feats = ""
|
||||
for c in cfg["features"]["cards"]:
|
||||
feats += ('<div class="feat reveal"><div class="ico">%s</div>'
|
||||
'<h3>%s</h3><p>%s</p></div>') % (c["icon"], c["title"], c["html"])
|
||||
# philosophy
|
||||
if st != hero["pipeline"][-1]: stages += '<span class="arrow">→</span>'
|
||||
feats = "".join('<div class="feat reveal"><div class="ico">%s</div><h3>%s</h3><p>%s</p></div>'
|
||||
% (c["icon"], c["title"], c["html"]) for c in cfg["features"]["cards"])
|
||||
phil = cfg["philosophy"]
|
||||
phil_paras = "".join("<p>%s</p>" % p for p in phil["paras"])
|
||||
phil_stats = "".join('<div class="stat"><div class="big">%s</div><div class="lbl">%s</div></div>'
|
||||
% (s["big"], esc(s["lbl"])) for s in phil["stats"])
|
||||
# get-started steps + terminal
|
||||
start = cfg["start"]
|
||||
steps = ""
|
||||
for i, s in enumerate(start["steps"], 1):
|
||||
steps += ('<div class="step reveal"><div class="n">%d</div>'
|
||||
'<div><h4>%s</h4><p>%s</p></div></div>') % (i, s["title"], s["html"])
|
||||
steps = "".join('<div class="step reveal"><div class="n">%d</div><div><h4>%s</h4><p>%s</p></div></div>'
|
||||
% (i, s["title"], s["html"]) for i, s in enumerate(start["steps"], 1))
|
||||
term = ""
|
||||
for t in start["terminal"]:
|
||||
if t.get("blank"):
|
||||
term += "\n"
|
||||
elif "comment" in t:
|
||||
term += '<span class="t-com"># %s</span>\n' % esc(t["comment"])
|
||||
elif "cmd" in t:
|
||||
term += '<span class="prompt">$</span> %s\n' % esc(t["cmd"])
|
||||
elif "out" in t:
|
||||
term += '<span class="out">%s</span>\n' % esc(t["out"])
|
||||
# editors
|
||||
if t.get("blank"): term += "\n"
|
||||
elif "comment" in t: term += '<span class="t-com"># %s</span>\n' % esc(t["comment"])
|
||||
elif "cmd" in t: term += '<span class="prompt">$</span> %s\n' % esc(t["cmd"])
|
||||
elif "out" in t: term += '<span class="out">%s</span>\n' % esc(t["out"])
|
||||
ed = cfg["editors"]
|
||||
eds = "".join('<div class="ed"><span class="k">◆</span> %s</div>' % esc(x) for x in ed["list"])
|
||||
# showcase samples -> JS array, code read from real files
|
||||
samples = []
|
||||
for s in cfg["showcase"]["samples"]:
|
||||
samples.append({"name": s["name"], "label": s["label"],
|
||||
"note": s["note"], "code": read_snippet(s["file"])})
|
||||
samples_json = json.dumps(samples)
|
||||
# footer links
|
||||
samples = [{"name": s["name"], "label": s["label"], "note": s["note"], "code": read_snippet(s["file"])}
|
||||
for s in cfg["showcase"]["samples"]]
|
||||
footlinks = "".join('<a href="%s">%s</a>' % (n["href"], esc(n["label"])) for n in cfg["nav_links"])
|
||||
footlinks += '<a href="%s">Source ↗</a>' % cfg["repo_url"]
|
||||
m = cfg["meta"]
|
||||
|
||||
tmpl = """<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>%(title)s</title>
|
||||
<meta name="description" content="%(desc)s">
|
||||
<meta property="og:title" content="%(ogt)s">
|
||||
<meta property="og:description" content="%(ogd)s">
|
||||
<meta property="og:type" content="website">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
%(css)s
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="nav">
|
||||
<div class="wrap nav-in">
|
||||
<a class="brand" href="#top"><span class="logo">L</span> %(brand)s</a>
|
||||
<nav class="nav-links">%(navlinks)s
|
||||
<a class="nav-cta" href="%(repo)s">Source ↗</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<a id="top"></a>
|
||||
<section class="hero">
|
||||
<div class="wrap hero-grid">
|
||||
<div>
|
||||
<span class="pill"><span class="dot"></span> %(pill)s</span>
|
||||
<h1>%(title_pre)s <span class="accent">%(title_accent)s</span></h1>
|
||||
<p class="lead">%(lead)s</p>
|
||||
<div class="cta-row">
|
||||
<a class="btn btn-primary" href="%(pcta_h)s">%(pcta_l)s</a>
|
||||
<a class="btn btn-ghost" href="%(scta_h)s">%(scta_l)s</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="code-card reveal">
|
||||
<div class="code-top">
|
||||
<span class="tl r"></span><span class="tl y"></span><span class="tl g"></span>
|
||||
<span class="code-name">%(hero_name)s</span>
|
||||
</div>
|
||||
<pre data-lang="ludic">%(hero_code)s</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrap" style="margin-top:52px">
|
||||
<div class="pipeline reveal">%(stages)s</div>
|
||||
<p style="text-align:center;color:var(--muted);font-size:13.5px;margin:14px 0 0">%(pipenote)s</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="kicker">%(feat_kicker)s</div>
|
||||
<h2>%(feat_title)s</h2>
|
||||
<p>%(feat_intro)s</p>
|
||||
</div>
|
||||
<div class="feat-grid">%(feats)s</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="showcase" style="padding-top:24px">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="kicker">%(sc_kicker)s</div>
|
||||
<h2>%(sc_title)s</h2>
|
||||
<p>%(sc_intro)s</p>
|
||||
</div>
|
||||
<div class="tabs reveal" id="tabs"></div>
|
||||
<div id="panels"></div>
|
||||
<div class="showcase-note reveal"><span class="b">↳</span><span id="note"></span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="padding-top:24px">
|
||||
<div class="wrap">
|
||||
<div class="banner reveal">
|
||||
<div>
|
||||
<div class="kicker" style="color:var(--blue)">%(phil_kicker)s</div>
|
||||
<h2>%(phil_title)s</h2>
|
||||
%(phil_paras)s
|
||||
</div>
|
||||
<div class="stat-row">%(phil_stats)s</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="start">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="kicker">%(start_kicker)s</div>
|
||||
<h2>%(start_title)s</h2>
|
||||
<p>%(start_intro)s</p>
|
||||
</div>
|
||||
<div class="steps">
|
||||
<div>%(steps)s</div>
|
||||
<div class="term reveal">
|
||||
<div class="code-top">
|
||||
<span class="tl r"></span><span class="tl y"></span><span class="tl g"></span>
|
||||
<span class="code-name">%(term_name)s</span>
|
||||
</div>
|
||||
<pre>%(term)s</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="editors" style="padding-top:24px">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="kicker">%(ed_kicker)s</div>
|
||||
<h2>%(ed_title)s</h2>
|
||||
<p>%(ed_intro)s</p>
|
||||
</div>
|
||||
<div class="editors reveal">%(eds)s</div>
|
||||
<p style="margin-top:22px;color:var(--muted);font-size:14.5px">%(ed_note)s</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="wrap foot-in">
|
||||
<div>
|
||||
<div class="brand" style="margin-bottom:8px"><span class="logo">L</span> %(brand)s</div>
|
||||
<div class="muted">%(tagline)s</div>
|
||||
</div>
|
||||
<nav class="foot-links">%(footlinks)s</nav>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="ludic-highlight.js"></script>
|
||||
<script>
|
||||
Ludic.highlightAll();
|
||||
const SAMPLES = %(samples)s;
|
||||
const tabsEl = document.getElementById("tabs");
|
||||
const panelsEl = document.getElementById("panels");
|
||||
const noteEl = document.getElementById("note");
|
||||
SAMPLES.forEach((s, idx)=>{
|
||||
const t = document.createElement("button");
|
||||
t.className = "tab" + (idx===0 ? " active" : "");
|
||||
t.textContent = s.label;
|
||||
t.onclick = ()=>select(idx);
|
||||
tabsEl.appendChild(t);
|
||||
const card = document.createElement("div");
|
||||
card.className = "code-card panel-code" + (idx===0 ? " active" : "");
|
||||
card.innerHTML =
|
||||
'<div class="code-top"><span class="tl r"></span><span class="tl y"></span><span class="tl g"></span><span class="code-name">'
|
||||
+ s.name + '</span></div><pre>' + Ludic.highlight(s.code) + '</pre>';
|
||||
panelsEl.appendChild(card);
|
||||
});
|
||||
function select(idx){
|
||||
[...tabsEl.children].forEach((t,i)=>t.classList.toggle("active", i===idx));
|
||||
[...panelsEl.children].forEach((p,i)=>p.classList.toggle("active", i===idx));
|
||||
noteEl.textContent = SAMPLES[idx].note;
|
||||
}
|
||||
noteEl.textContent = SAMPLES[0].note;
|
||||
const io = new IntersectionObserver((entries)=>{
|
||||
entries.forEach(e=>{ if(e.isIntersecting){ e.target.classList.add("in"); io.unobserve(e.target); } });
|
||||
},{threshold:.12});
|
||||
document.querySelectorAll(".reveal").forEach(el=>io.observe(el));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
tmpl = open(os.path.join(ASSETS, "index.tmpl.html")).read()
|
||||
return pct(tmpl, dict(
|
||||
css=css, brand=esc(cfg["brand"]), tagline=esc(cfg["tagline"]), repo=cfg["repo_url"],
|
||||
title=esc(m["title"]), desc=esc(m["description"]), ogt=esc(m["og_title"]), ogd=esc(m["og_description"]),
|
||||
navlinks=navlinks,
|
||||
pill=esc(hero["pill"]), title_pre=esc(hero["title_pre"]), title_accent=esc(hero["title_accent"]),
|
||||
title=esc(m["title"]), desc=escattr(m["description"]), ogt=esc(m["og_title"]), ogd=escattr(m["og_description"]),
|
||||
navlinks=navlinks, pill=esc(hero["pill"]), title_pre=esc(hero["title_pre"]), title_accent=esc(hero["title_accent"]),
|
||||
lead=hero["lead"], pcta_h=hero["primary_cta"]["href"], pcta_l=esc(hero["primary_cta"]["label"]),
|
||||
scta_h=hero["secondary_cta"]["href"], scta_l=esc(hero["secondary_cta"]["label"]),
|
||||
hero_name=esc(hero["snippet_name"]), hero_code=hero_code, stages=stages, pipenote=hero["pipeline_note"],
|
||||
feat_kicker=esc(cfg["features"]["kicker"]), feat_title=esc(cfg["features"]["title"]),
|
||||
feat_intro=cfg["features"]["intro"], feats=feats,
|
||||
sc_kicker=esc(cfg["showcase"]["kicker"]), sc_title=esc(cfg["showcase"]["title"]),
|
||||
sc_intro=cfg["showcase"]["intro"],
|
||||
feat_kicker=esc(cfg["features"]["kicker"]), feat_title=esc(cfg["features"]["title"]), feat_intro=cfg["features"]["intro"], feats=feats,
|
||||
sc_kicker=esc(cfg["showcase"]["kicker"]), sc_title=esc(cfg["showcase"]["title"]), sc_intro=cfg["showcase"]["intro"],
|
||||
phil_kicker=esc(phil["kicker"]), phil_title=esc(phil["title"]), phil_paras=phil_paras, phil_stats=phil_stats,
|
||||
start_kicker=esc(start["kicker"]), start_title=esc(start["title"]), start_intro=esc(start["intro"]),
|
||||
steps=steps, term_name=esc(start["terminal_name"]), term=term,
|
||||
ed_kicker=esc(cfg["editors"]["kicker"]), ed_title=esc(cfg["editors"]["title"]),
|
||||
ed_intro=cfg["editors"]["intro"], eds=eds, ed_note=cfg["editors"]["note"],
|
||||
footlinks=footlinks, samples=samples_json,
|
||||
ed_kicker=esc(ed["kicker"]), ed_title=esc(ed["title"]), ed_intro=ed["intro"], eds=eds, ed_note=ed["note"],
|
||||
footlinks=footlinks, samples=json.dumps(samples),
|
||||
))
|
||||
|
||||
def pct(tmpl, mapping):
|
||||
return re.sub(r"%\((\w+)\)s", lambda mm: mapping[mm.group(1)], tmpl)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
def render_highlighter(symbols):
|
||||
tmpl = open(os.path.join(ASSETS, "ludic-highlight.tmpl.js")).read()
|
||||
|
|
@ -504,22 +401,39 @@ def main():
|
|||
|
||||
sections = load_sections()
|
||||
palette = json.load(open(os.path.join(LANG, "colors", "palette.json")))
|
||||
symbols = build_symbols(sections)
|
||||
symbols, items = build_symbols(sections, palette)
|
||||
cfg = json.load(open(os.path.join(SITE, "site.json")))
|
||||
|
||||
open(os.path.join(out, "api.html"), "w").write(render_api(sections, palette, cfg))
|
||||
open(os.path.join(out, "index.html"), "w").write(render_index(cfg))
|
||||
open(os.path.join(out, "ludic-highlight.js"), "w").write(render_highlighter(symbols))
|
||||
open(os.path.join(out, "symbols.json"), "w").write(json.dumps(symbols, indent=2, ensure_ascii=False))
|
||||
open(os.path.join(out, ".nojekyll"), "w").write("")
|
||||
W = lambda name, content: open(os.path.join(out, name), "w").write(content)
|
||||
|
||||
# per-item pages
|
||||
npages = 0
|
||||
for s in sections:
|
||||
if s["cat"] == "colors":
|
||||
continue
|
||||
for e in s["entries"]:
|
||||
W(e["id"] + ".html", item_page(e, s, cfg, items)); npages += 1
|
||||
# namespace overview pages
|
||||
by_cat = {s["cat"]: s for s in sections}
|
||||
for nsname, cat in (("Screen","screen"),("Input","input"),("Random","random"),("Map","map")):
|
||||
if cat in by_cat:
|
||||
W(ns_page(nsname) + ".html", ns_overview_page(nsname, by_cat[cat], cfg)); npages += 1
|
||||
W("ns-color.html", color_page(palette, cfg)); npages += 1
|
||||
# index + landing + assets
|
||||
W("api.html", api_index(sections, cfg))
|
||||
W("index.html", render_index(cfg))
|
||||
W("ludic-highlight.js", render_highlighter(symbols))
|
||||
W("symbols.json", json.dumps({"items": items, "highlight": symbols}, indent=2, ensure_ascii=False))
|
||||
W(".nojekyll", "")
|
||||
|
||||
n_entries = sum(len(s["entries"]) for s in sections)
|
||||
print("docs generated -> %s" % out)
|
||||
print(" sections: %d symbols: %d colors: %d"
|
||||
% (len(sections), n_entries, palette["count"]))
|
||||
print(" highlighter tokens: %d kw / %d type / %d builtin / %d ns-method / %d annot"
|
||||
% (len(symbols["keywords"]), len(symbols["types"]), len(symbols["builtins"]),
|
||||
len(symbols["nsmethods"]), len(symbols["annotations"])))
|
||||
print(" sections: %d symbols: %d colors: %d item pages: %d"
|
||||
% (len(sections), n_entries, palette["count"], npages))
|
||||
print(" highlighter: %d kw / %d type / %d phase / %d builtin / %d ns-method / %d annot / %d ns"
|
||||
% (len(symbols["keywords"]), len(symbols["types"]), len(symbols["phases"]),
|
||||
len(symbols["builtins"]), len(symbols["nsmethods"]), len(symbols["annotations"]),
|
||||
len(symbols["namespaces"])))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue