- Modular block files: split into blocks-algo, blocks-data, blocks-proc - New modules: sections.js (labelled canvas areas), proc-browser.js (procedure tree modal), lib-loader.js (lazy library loading), templates.js (project templates), errno-data.js (errno table) - Test infrastructure: browser test suite (28 tests) and Node pure-function tests (75 tests) - Warning system: unified .block-warn for disconnected inputs, unconnected error hooks, and zero-exit-in-error-path - Error path highlighting: red flow paths and block borders for error-only subgraphs - Input hooks: hidden by default, shown during linking; outputs always at bottom - Block editor modal: pencil icon, colour picker for accent, compact field labels - Doc generation: standalone node script extracts JSDoc -> docs/API.md (124 declarations) - Persistence: save/restore sections, errno selection, block colours - Fixes: _segmentsIntersect bug, WCEnd crash from closest(this), drawpath guard on detached elements
159 lines
5.6 KiB
JavaScript
159 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
|
// ── Standalone API documentation generator ────────────────────────────────
|
|
//
|
|
// Reads all production .js files, extracts JSDoc /** ... */ comment blocks
|
|
// together with the declaration they annotate, and writes a formatted
|
|
// Markdown file to docs/API.md.
|
|
//
|
|
// Zero dependencies - uses only Node.js built-in fs and path.
|
|
// Run: node scripts/generate-docs.js
|
|
//
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const SOURCE_DIR = ".";
|
|
const OUTPUT_FILE = path.join("docs", "API.md");
|
|
const SOURCE_GLOB = [
|
|
"dom.js", "helpers.js", "error.js", "errno-data.js",
|
|
"draw.js", "sections.js", "wcblock.js", "blocks-algo.js",
|
|
"blocks-data.js", "blocks-proc.js", "wcprogram.js",
|
|
"persist.js", "lib-loader.js", "proc-browser.js",
|
|
"templates.js", "app.js"
|
|
];
|
|
|
|
// ── Parse helpers ────────────────────────────────────────────────────────
|
|
|
|
/** Extracts the first doc comment block preceding a declaration. */
|
|
function extractJSDoc(lines, startIdx) {
|
|
let i = startIdx;
|
|
while (i >= 0 && lines[i].trim() === "") i--;
|
|
if (i < 0) return null;
|
|
const line = lines[i].trim();
|
|
if (!line.endsWith("*/")) return null;
|
|
const end = i;
|
|
while (i >= 0 && !lines[i].trim().startsWith("/**")) i--;
|
|
if (i < 0) return null;
|
|
const raw = lines.slice(i, end + 1).join("\n");
|
|
return { raw, startLine: i + 1 };
|
|
}
|
|
|
|
/** Strips opening, closing, and leading star markers from a JSDoc block. */
|
|
function cleanJSDoc(raw) {
|
|
const singleLine = !raw.includes("\n");
|
|
if (singleLine) {
|
|
return raw.replace(/^\/\*\*\s*/, "").replace(/\s*\*\/$/, "").trim();
|
|
}
|
|
return raw
|
|
.replace(/^\/\*\*[\s\S]*?\n/, "")
|
|
.replace(/\s*\*\/\s*$/, "")
|
|
.split("\n")
|
|
.map(l => l.replace(/^\s*\* ?/, ""))
|
|
.join("\n")
|
|
.trim();
|
|
}
|
|
|
|
/** Extracts the name from a declaration line like class Foo, function foo, or const foo. */
|
|
function declName(line) {
|
|
const m = line.match(/^(?:class|function|const)\s+(\w+)/);
|
|
return m ? m[1] : null;
|
|
}
|
|
|
|
/** Determines the declaration kind (class/function/const) from the line. */
|
|
function declKind(line) {
|
|
if (/^class\b/.test(line)) return "class";
|
|
if (/^function\b/.test(line)) return "function";
|
|
if (/^const\b/.test(line)) return "const";
|
|
return "declaration";
|
|
}
|
|
|
|
// ── Generator ────────────────────────────────────────────────────────────
|
|
|
|
function generate() {
|
|
const blocks = [];
|
|
|
|
for (const filename of SOURCE_GLOB) {
|
|
const absPath = path.resolve(SOURCE_DIR, filename);
|
|
if (!fs.existsSync(absPath)) {
|
|
console.warn(` [warn] ${filename} not found, skipping`);
|
|
continue;
|
|
}
|
|
const src = fs.readFileSync(absPath, "utf-8");
|
|
const lines = src.split("\n");
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
const name = declName(line);
|
|
if (!name) continue;
|
|
const jsdoc = extractJSDoc(lines, i - 1);
|
|
if (!jsdoc) continue;
|
|
const kind = declKind(line);
|
|
const desc = cleanJSDoc(jsdoc.raw);
|
|
|
|
// extract @param and @returns from the cleaned JSDoc
|
|
const params = [];
|
|
const returns = [];
|
|
const descLines = desc.split("\n");
|
|
const bodyLines = [];
|
|
for (const dl of descLines) {
|
|
const pm = dl.match(/^\s*@param\s+\{([^}]+)\}\s+(\S+)\s*(.*)/);
|
|
const rm = dl.match(/^\s*@returns\s+\{([^}]+)\}\s*(.*)/);
|
|
const tm = dl.match(/^\s*@type\s+\{([^}]+)\}\s*(.*)/);
|
|
if (pm) params.push({ type: pm[1], name: pm[2], desc: pm[3] });
|
|
else if (rm) returns.push({ type: rm[1], desc: rm[2] });
|
|
else if (tm) bodyLines.push(`Type: \`${tm[1]}\` ${tm[2]}`);
|
|
else bodyLines.push(dl);
|
|
}
|
|
|
|
const body = bodyLines.join("\n").trim();
|
|
blocks.push({ filename, name, kind, line: i + 1, body, params, returns });
|
|
}
|
|
}
|
|
|
|
// ── Write output ─────────────────────────────────────────────────────
|
|
|
|
fs.mkdirSync(path.dirname(OUTPUT_FILE), { recursive: true });
|
|
const out = fs.createWriteStream(OUTPUT_FILE);
|
|
out.write("# API Reference\n\n");
|
|
out.write(`Generated from JSDoc annotations in ${SOURCE_GLOB.length} source files.\n\n`);
|
|
|
|
// Group by file
|
|
let currentFile = "";
|
|
for (const b of blocks) {
|
|
if (b.filename !== currentFile) {
|
|
currentFile = b.filename;
|
|
out.write(`\n---\n## ${b.filename}\n\n`);
|
|
}
|
|
|
|
const anchor = `${b.kind}-${b.name}`;
|
|
out.write(`### <a name="${anchor}"></a>\`${b.kind}\` ${b.name} \n`);
|
|
out.write(`*Line ${b.line}*\n\n`);
|
|
|
|
if (b.body) out.write(b.body + "\n\n");
|
|
|
|
if (b.params.length > 0) {
|
|
out.write("**Parameters:**\n\n");
|
|
out.write("| Name | Type | Description |\n");
|
|
out.write("|------|------|------------|\n");
|
|
for (const p of b.params) {
|
|
out.write(`| \`${p.name}\` | \`${p.type}\` | ${p.desc} |\n`);
|
|
}
|
|
out.write("\n");
|
|
}
|
|
|
|
if (b.returns.length > 0) {
|
|
out.write("**Returns:** ");
|
|
for (const r of b.returns) {
|
|
out.write(`\`${r.type}\` — ${r.desc}`);
|
|
}
|
|
out.write("\n\n");
|
|
}
|
|
}
|
|
|
|
out.end();
|
|
console.log(`Wrote ${OUTPUT_FILE} (${blocks.length} documented declarations)`);
|
|
}
|
|
|
|
generate();
|