import { opendir, readFile, stat, writeFile, } from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; import * as path from 'path'; const nodeModules = path.resolve(path.resolve(import.meta.dirname, '..'), 'node_modules'); const unicodeDir = path.resolve(nodeModules, '@unicode', 'unicode-16.0.0'); async function writeUnicodePropertyMapping() { async function* scan(d: string): AsyncGenerator { for await (const dirent of await opendir(d)) { if (dirent.isDirectory()) { const p = path.join(d, dirent.name); const test = path.join(p, 'code-points.js'); try { await stat(test); yield p; } catch { yield* scan(p); } } } } type Range = readonly [from: number, to: number] const data: Record = {}; for await (const item of scan(unicodeDir)) { const category = path.relative(unicodeDir, item).replace(/\\/g, '/'); const { default: cps } = await import(`@unicode/unicode-16.0.0/${category}/code-points.js`); if (!Array.isArray(cps)) { continue; } if (!category.startsWith('General_Category/') && !category.startsWith('Script/') && !category.startsWith('Script_Extensions/') && !category.startsWith('Binary_Property/')) { continue; } const ranges: Range[] = []; let from = 0; let to = 0; cps.forEach((cp, i) => { if (i === 0) { from = cp; to = cp; } else { if (to + 1 === cp) { to += 1; } else { ranges.push([from, to]); from = cp; to = cp; } } }); ranges.push([from, to]); data[category] = ranges; } await writeFile(path.resolve(path.resolve(import.meta.dirname, '..'), 'src/unicode/CodePointProperties.json'), JSON.stringify(data)); } async function writeUnicodeStringsMapping() { const data: Record = {}; for (const cat of [ 'Basic_Emoji', 'Emoji_Keycap_Sequence', 'RGI_Emoji_Modifier_Sequence', 'RGI_Emoji_Flag_Sequence', 'RGI_Emoji_Tag_Sequence', 'RGI_Emoji_ZWJ_Sequence', 'RGI_Emoji', ]) { const file = path.resolve(unicodeDir, 'Sequence_Property', cat, 'index.js'); // eslint-disable-next-line no-await-in-loop const { default: strings } = await import(pathToFileURL(file).href); data[cat] = strings.join(','); } await writeFile(path.resolve(path.resolve(import.meta.dirname, '..'), 'src', 'unicode/SequenceProperties.json'), JSON.stringify(data)); } async function writeUnicodePropertyAliasMapping() { const file = readFile(new URL('./Unicode/PropertyValueAliases.txt', import.meta.url), 'utf-8'); const lines = (await file).split('\n').filter((line) => line.length > 0 && !line.startsWith('#')); const gc: Record = {}; const sc: Record = {}; const scx: Record = {}; // gc ; M ; Mark ; Combining_Mark // where Mark is the official name, I guess? for (const line of lines) { const [cat, alias, formalName, ...moreAlias] = line .split('#')[0] .split(';') .map((s) => s.trim()); if (cat === 'gc') { gc[alias] = formalName; gc[formalName] = formalName; moreAlias.forEach((name) => { gc[name] = formalName; }); } else if (cat === 'sc') { sc[alias] = formalName; sc[formalName] = formalName; moreAlias.forEach((name) => { sc[name] = formalName; }); } else if (cat === 'scx') { scx[alias] = formalName; scx[formalName] = formalName; moreAlias.forEach((name) => { scx[name] = formalName; }); } } await writeFile( new URL('../src/unicode/PropertyValueAliases.json', import.meta.url), JSON.stringify( { description: 'Unicode Property Value Aliases, generated from https://unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt', General_Category: gc, Script: sc, Script_Extensions: scx, }, undefined, 2, ), 'utf-8', ); } await Promise.all([ writeUnicodePropertyMapping(), writeUnicodeStringsMapping(), writeUnicodePropertyAliasMapping(), ]);