Something went wrong. Try again.
A reference for terminal control sequences whose examples run live in libghostty-vt compiled to WebAssembly.
Something went wrong. Try again.
4.4 kB · 96 lines
JavaScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
// The order of the pages in each section, for the index column and the// section's own table. Zine lists a section's pages only by date or by// title, so the order is worked out here and written into the section's// index.smd as `"order": [ … ]`, which the layouts loop over. An entry with a// `|` in it is another number or mnemonic of a page, from its `index_also`.//// node tools/order.mjs website write the orders// node tools/order.mjs website --check fail if any is out of date//// Most sections go by mnemonic; the ANSI and private modes by mode number; the// operating system commands by OSC number, then by the number after it// (OSC 9 before OSC 9 ; 4).
import { readFileSync, writeFileSync } from "node:fs";import { readPages } from "./pages.mjs";
const [websiteDir = "website", flag] = process.argv.slice(2);const check = flag === "--check";
const collator = new Intl.Collator("en", { numeric: true, sensitivity: "base" });
function compareLists(a, b) { for (let i = 0; i < Math.max(a.length, b.length); i++) { const x = a[i] ?? -1; const y = b[i] ?? -1; if (x !== y) return x - y; } return 0;}
// "Set Window Title (OSC 2)" -> [2]; "(OSC 9 ; 4)" -> [9, 4]. The letters// the older terminals used, "(OSC l)", go after every number, by letter.function oscKey(page) { const inParens = /\(([^()]*)\)\s*$/.exec(page.fields.title ?? "")?.[1] ?? page.fields.custom?.mnemonic ?? ""; const m = /OSC\s+(\d+|[A-Za-z])(?:\s*;\s*(\d+))?/.exec(inParens); if (!m) return [Number.MAX_SAFE_INTEGER]; const first = /\d/.test(m[1]) ? Number(m[1]) : 1e6 + m[1].charCodeAt(0); return [first, m[2] === undefined ? -1 : Number(m[2])];}
function modeKey(page) { return Number(/\d+/.exec(page.fields.custom?.mode ?? "")?.[0] ?? Infinity);}
// A page's `index_mnemonic`, when it has one, is what its entry shows, so it// is what the entry sorts by.const sortLabel = (page) => page.fields.custom?.index_mnemonic ?? page.fields.custom?.mnemonic ?? page.fields.title;const byMnemonic = (a, b) => collator.compare(sortLabel(a), sortLabel(b));const sorters = { ansi: (a, b) => modeKey(a) - modeKey(b), modes: (a, b) => modeKey(a) - modeKey(b), osc: (a, b) => compareLists(oscKey(a), oscKey(b)), parser: (a, b) => collator.compare(a.fields.title, b.fields.title),};
const pages = readPages(websiteDir);const sections = pages.filter((p) => p.rel.endsWith("/index.smd"));let stale = 0;
for (const section of sections) { const dir = section.rel.slice(0, -"/index.smd".length); const members = pages.filter((p) => p.rel.startsWith(`${dir}/`) && p !== section && !p.rel.slice(dir.length + 1).includes("/")); // A page that covers more than one number or mnemonic lists the others in // `index_also`, each as "label|name". Each becomes an entry of its own, // sorted with the pages, written into the order as "page|label|name" so // that the layouts draw it as an entry leading to that page. const entries = members.flatMap((p) => { const key = p.rel.slice(0, -".smd".length); const also = /"index_also": \[([\s\S]*?)\]/.exec(readFileSync(p.file, "utf8"))?.[1] ?? ""; const aliases = [...also.matchAll(/"([^"|]+)\|([^"]+)"/g)].map(([, label, name]) => ({ rel: p.rel, alias: `${key}|${label}|${name}`, // A private mode's alias is its number, "?40", which sorts as a mode. fields: { title: `${name} (${label})`, custom: { mnemonic: label, mode: label, name } }, })); return [p, ...aliases]; }); entries.sort(sorters[dir] ?? byMnemonic); const order = entries.map((p) => p.alias ?? p.rel.slice(0, -".smd".length));
const source = readFileSync(section.file, "utf8"); const block = `.custom = {\n "order": [\n${order.map((o) => ` "${o}",`).join("\n")}\n ],\n},\n`; const withoutOld = source.replace(/\.custom = \{\n "order": \[\n[\s\S]*?\n \],\n\},\n/, ""); const updated = withoutOld.replace(/\n---\n/, `\n${block}---\n`); if (updated !== source) { stale++; if (check) console.error(`${section.rel}: the order is out of date; run node tools/order.mjs ${websiteDir}`); else writeFileSync(section.file, updated); }}
if (check && stale) process.exit(1);console.log(check ? `${sections.length} section orders up to date` : `${stale} section orders written`);