fluffos/docs/gen_sidebar.py
Yucong Sun 89060c5a6b
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages

Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:

- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
  walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
  emits sidebars.generated.json — a full Docusaurus category tree per
  directory. Category landing pages are now `generated-index` card pages
  (title/description/slug), so all generated index.md files are deleted.
  --check mode verifies freshness; new .github/workflows/docs-sidebar.yml
  runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
  one-line descriptions (shown on the landing cards), explicit ordering
  (driver/cli/concepts read top-down from user-facing to internals) and
  per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
  Historical) that splices in the generated trees.

Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
  (act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
  (check_memory, dump_*, clear_debug_level, destructed_objects) to
  internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
  general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
  element_of}.

Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
  admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
  workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.

`npm run build` passes clean (onBrokenLinks: throw, no warnings).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om

* docs: serve the Chinese corpus through Docusaurus i18n

Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):

- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
  categorized English layout (name-matched 1:1; `hash` maps to strings/
  per its own frontmatter). apply/ pages map 1:1; the stray English-text
  zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
  longer exists in the driver and is dropped; stdlib/db/database_zh.md
  becomes the i18n translation of stdlib/db/database.md; the Chinese
  build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
  /zh-CN/, so the whole site is navigable in either locale from the new
  navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
  stable `key` fields (the directory/doc path) so translation keys are
  unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
  strings both document `hash`). Category labels, generated-index
  titles/descriptions, navbar and footer are translated in
  i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
  zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
  sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
  the localized build (the file->permalink map points at the localized
  copy), so concepts/, the two socket_*_option pages and the config.md
  generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
  counterparts (a doc named after its parent directory is otherwise a
  Docusaurus category-index doc, colliding with the generated-index
  route).

`npm run build` builds both locales clean (onBrokenLinks: throw).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00

161 lines
5.7 KiB
Python
Executable file

#!/usr/bin/env python3
"""Generate sidebars.generated.json for the reference doc trees (efun/, apply/, ...).
Usage: gen_sidebar.py [--check]
Replaces the old gen_index.py, which wrote per-directory index.md link pages.
Navigation is now the Docusaurus sidebar itself: this script walks each
reference tree and emits a fully-expandable category tree per directory.
Category landing pages are Docusaurus `generated-index` pages (auto-generated
card grids), so no index.md files are written or needed in these trees.
Curated presentation lives in sidebar_meta.json, keyed by directory (or page)
path relative to docs/:
"efun/arrays": { "label": "Arrays", "description": "..." }
"driver": { "label": "...", "description": "...",
"order": ["config", "wasm", ...],
"labels": { "config": "Runtime Configuration" } }
Per-directory meta keys:
label sidebar label for the category (default: directory name)
description shown on the generated-index landing page
order page/subdir names listed first, in this order; anything not
listed follows alphabetically (subdirectories, then pages)
labels per-page sidebar label overrides (default: file name)
sidebars.ts imports sidebars.generated.json and splices each tree into the
sidebar skeleton. Run this script after adding/removing/moving any page in
the trees below; CI verifies freshness with --check.
"""
import json
import os
import sys
DOCS_ROOT = os.path.dirname(os.path.realpath(__file__))
META_FILE = os.path.join(DOCS_ROOT, "sidebar_meta.json")
OUT_FILE = os.path.join(DOCS_ROOT, "sidebars.generated.json")
# The doc trees this script owns. lpc/ is NOT here: its index pages are
# hand-written and its sidebar is hand-authored in sidebars.ts. The Chinese
# corpus lives in i18n/zh-CN/ (Docusaurus i18n) and shares this sidebar;
# its category labels are translated in
# i18n/zh-CN/docusaurus-plugin-content-docs/current.json.
TREES = ["efun", "apply", "stdlib", "concepts", "driver", "cli"]
# Directory entries that are never documentation content.
SKIP_DIRS = {"node_modules"}
def load_meta():
with open(META_FILE, encoding="utf-8") as f:
return json.load(f)
def list_entries(path):
"""Return (md_files, subdirs) for a directory, sorted and filtered.
md_files are page names without the .md/.mdx extension (index excluded);
subdirs are documentation subdirectories.
"""
files = []
subdirs = []
for entry in sorted(os.listdir(path)):
if entry.startswith(".") or entry in SKIP_DIRS:
continue
full = os.path.join(path, entry)
if os.path.isdir(full):
subdirs.append(entry)
else:
for ext in (".md", ".mdx"):
if entry.endswith(ext) and entry[: -len(ext)] != "index":
files.append(entry[: -len(ext)])
break
return files, subdirs
def apply_order(names, order):
"""Names from `order` first (in that order), the rest keep sorted order."""
if not order:
return names
ordered = [n for n in order if n in names]
return ordered + [n for n in names if n not in ordered]
def build_category(meta, rel):
"""Build the sidebar category item for directory `rel` (docs-relative)."""
path = os.path.join(DOCS_ROOT, rel)
files, subdirs = list_entries(path)
dir_meta = meta.get(rel, {})
label = dir_meta.get("label", os.path.basename(rel))
labels = dir_meta.get("labels", {})
order = dir_meta.get("order", [])
# Subdirectories first, then loose pages — with `order` pulling any
# explicitly-listed names (of either kind) to the front.
items = []
for name in apply_order(subdirs + files, order):
if name in subdirs:
items.append(build_category(meta, f"{rel}/{name}"))
else:
items.append(
{
"type": "doc",
"id": f"{rel}/{name}",
# Unique translation key (labels repeat: e.g. hash exists
# in both efun/crypto and efun/strings).
"key": f"{rel}/{name}",
"label": labels.get(name, name),
}
)
# A stable, unique translation key: labels repeat across trees (both
# efun/ and stdlib/ have "Arrays"), which would collide in i18n
# translation files (sidebar.docs.category.<key>).
category = {"type": "category", "key": rel, "label": label}
if os.path.exists(os.path.join(path, "index.md")):
# A hand-written landing page wins over the generated card grid.
category["link"] = {"type": "doc", "id": f"{rel}/index"}
else:
link = {
"type": "generated-index",
"title": label,
"slug": f"/{rel}/",
}
if "description" in dir_meta:
link["description"] = dir_meta["description"]
category["link"] = link
category["items"] = items
return category
def generate():
meta = load_meta()
return {tree: build_category(meta, tree) for tree in TREES}
def render(sidebars):
return json.dumps(sidebars, indent=2, ensure_ascii=False) + "\n"
def main(argv):
output = render(generate())
if "--check" in argv:
try:
with open(OUT_FILE, encoding="utf-8") as f:
current = f.read()
except FileNotFoundError:
current = ""
if current != output:
sys.exit(
"sidebars.generated.json is stale; run docs/gen_sidebar.py "
"and commit the result"
)
return
with open(OUT_FILE, "w", encoding="utf-8") as f:
f.write(output)
if __name__ == "__main__":
main(sys.argv[1:])