import { Marked, type Tokens } from "marked"; const sources = import.meta.glob("./*.md", { query: "?raw", import: "default", eager: true }); const examples = import.meta.glob("../../../examples/docs/*.rs", { query: "?raw", import: "default", eager: true }); /** The guides in reading order, by file name. */ const ORDER = ["introduction"]; export interface Guide { /** The path segment; the introduction has none. */ slug: string; title: string; html: string; /** The `##` headings, for the on-this-page list. */ toc: { id: string; text: string }[]; } const escape = (text: string) => text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); const slugify = (text: string) => text .toLowerCase() .replace(/<[^>]+>/g, "") .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); function read(file: string): Guide { const source = sources[`./${file}.md`]; if (source === undefined) throw new Error(`docs: no ${file}.md`); let title = ""; const toc: Guide["toc"] = []; const marked = new Marked({ renderer: { heading({ tokens, depth }: Tokens.Heading) { const html = this.parser.parseInline(tokens); if (depth === 1) { title = html.replace(/<[^>]+>/g, ""); return ""; } const id = slugify(html); if (depth === 2) { if (toc.some((h) => h.id === id)) throw new Error(`docs: ${file}.md repeats heading ${id}`); toc.push({ id, text: html.replace(/<[^>]+>/g, "") }); } return `${html}`; }, code({ text, lang }: Tokens.Code) { const [language = "", ...rest] = (lang ?? "").split(/\s+/); const example = rest.find((part) => part.startsWith("example="))?.slice("example=".length); let code = text; let caption = language; if (example) { const body = examples[`../../../examples/docs/${example}.rs`]; if (body === undefined) throw new Error(`docs: ${file}.md names a missing example ${example}`); code = body.trimEnd(); caption = `examples/docs/${example}.rs ยท cargo run --example docs_${example}`; } return `
${escape(caption)}
${escape(code)}
`; }, }, }); const html = marked.parse(source, { async: false }); if (!title) throw new Error(`docs: ${file}.md has no # title`); return { slug: file === "introduction" ? "" : file, title, html, toc }; } export const guides: Guide[] = ORDER.map(read);