| 1 | // Regenerates src/ghostty/constants.ts from the vendored C headers. |
| 2 | // Parses `typedef enum ... } Name;` blocks, tracking implicit +1 values. |
| 3 | // Run from the package root: node vendor/gen-constants.mjs |
| 4 | import { readFileSync, readdirSync, writeFileSync } from "node:fs"; |
| 5 | import { join, dirname } from "node:path"; |
| 6 | import { fileURLToPath } from "node:url"; |
| 7 | |
| 8 | const root = dirname(fileURLToPath(import.meta.url)); |
| 9 | const headerDir = join(root, "include/ghostty/vt"); |
| 10 | const headers = readdirSync(headerDir, { recursive: true }) |
| 11 | .filter((f) => String(f).endsWith(".h")) |
| 12 | .map((f) => readFileSync(join(headerDir, String(f)), "utf8")); |
| 13 | |
| 14 | const enums = new Map(); |
| 15 | const enumRe = /typedef enum[^{]*\{([\s\S]*?)\}\s*(\w+)\s*;/g; |
| 16 | for (const src of headers) { |
| 17 | for (const m of src.matchAll(enumRe)) { |
| 18 | const [, body, name] = m; |
| 19 | const members = []; |
| 20 | let next = 0; |
| 21 | const stripped = body.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, ""); |
| 22 | for (const entry of stripped.split(",")) { |
| 23 | const em = entry.match(/([A-Z][A-Z0-9_]+)\s*(?:=\s*([^,\s]+))?/); |
| 24 | if (!em) continue; |
| 25 | const [, ident, valueExpr] = em; |
| 26 | if (ident.endsWith("_MAX_VALUE")) continue; |
| 27 | let value = next; |
| 28 | if (valueExpr !== undefined) { |
| 29 | if (/^-?\d+$/.test(valueExpr)) value = Number(valueExpr); |
| 30 | else if (/^0x[0-9a-fA-F]+$/.test(valueExpr)) value = Number(valueExpr); |
| 31 | else continue; // symbolic (GHOSTTY_ENUM_MAX_VALUE etc.) |
| 32 | } |
| 33 | members.push([ident, value]); |
| 34 | next = value + 1; |
| 35 | } |
| 36 | if (members.length > 0 && name.startsWith("Ghostty")) enums.set(name, members); |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | let out = `// GENERATED by vendor/gen-constants.mjs from the vendored libghostty-vt headers\n`; |
| 41 | out += `// (commit in vendor/COMMIT). Do not edit by hand; re-run the generator on bump.\n\n`; |
| 42 | for (const [name, members] of [...enums.entries()].sort(([a], [b]) => a.localeCompare(b))) { |
| 43 | out += `export const ${name} = {\n`; |
| 44 | for (const [ident, value] of members) out += ` ${ident}: ${value},\n`; |
| 45 | out += `} as const;\n\n`; |
| 46 | } |
| 47 | out = `${out.trimEnd()}\n`; |
| 48 | const outPath = join(root, "../src/ghostty/constants.ts"); |
| 49 | if (process.argv.includes("--check")) { |
| 50 | const current = readFileSync(outPath, "utf8"); |
| 51 | if (current !== out) { |
| 52 | let mismatch = 0; |
| 53 | while (current[mismatch] === out[mismatch]) mismatch++; |
| 54 | throw new Error( |
| 55 | `${outPath} is stale at byte ${mismatch}: expected ${JSON.stringify(out.slice(mismatch, mismatch + 80))}, got ${JSON.stringify(current.slice(mismatch, mismatch + 80))}; run node vendor/gen-constants.mjs`, |
| 56 | ); |
| 57 | } |
| 58 | console.log(`verified ${outPath}: ${enums.size} enums`); |
| 59 | } else { |
| 60 | writeFileSync(outPath, out); |
| 61 | console.log(`wrote ${outPath}: ${enums.size} enums`); |
| 62 | } |