repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
pi/agent/skills/nerdfont-icons/scripts/get-nerdfont-codepoint.js
Raw#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import zlib from "node:zlib";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const URL = "https://www.nerdfonts.com/cheat-sheet?q=nf";
const ROOT = path.resolve(__dirname, "..");
const DB = path.join(ROOT, "references", "nerdfont-icons.txt.br");
const FORMATS = new Set([
"hex",
"codepoint",
"glyph",
"json",
"all",
"js",
"html",
"java",
"json-escape",
"css",
"css-content",
"python",
"rust",
"go",
"c",
"cpp",
"swift",
"lua",
"shell",
"printf",
"toml",
"yaml",
"vim",
"elisp",
"wezterm",
]);
function usage(exitCode = 2) {
const msg = `usage: node scripts/get-nerdfont-codepoint.js <query> [format]\n\nformat enum:\n hex eb99\n codepoint U+EB99\n glyph actual glyph char\n js \\u{EB99}\n html \n java \\uEB99 or surrogate pair\n json-escape \\uEB99 or surrogate pair\n css \\EB99\n css-content content: "\\EB99";\n python \\U0000EB99\n rust \\u{EB99}\n go \\U0000EB99\n c|cpp \\U0000EB99\n swift \\u{EB99}\n lua utf8.char(0xEB99)\n shell $'\\U0000EB99'\n printf printf '\\U0000EB99\\n'\n toml|yaml \\U0000EB99\n vim nr2char(0xEB99)\n elisp (string #xEB99)\n wezterm wezterm.nerdfonts.cod_account\n json { name, hex, ... }\n all name<TAB>hex<TAB>codepoint<TAB>glyph\n\nflags:\n --refresh update references/nerdfont-icons.txt\n`;
(exitCode ? console.error : console.log)(msg);
process.exit(exitCode);
}
function readDb() {
if (!fs.existsSync(DB)) return [];
const raw = fs.readFileSync(DB);
const text = zlib.brotliDecompressSync(raw).toString("utf8");
return text
.split(/\r?\n/)
.filter(Boolean)
.map((line) => {
const [name, hex] = line.split(/\s+/);
return name && hex ? { name, hex: hex.toLowerCase() } : null;
})
.filter(Boolean);
}
async function fetchText(url) {
const res = await fetch(url, {
headers: { "user-agent": "pi-nerdfont-icons-skill" },
});
if (!res.ok) throw new Error(`fetch failed: ${res.status} ${res.statusText}`);
return await res.text();
}
async function refreshDb() {
const html = await fetchText(URL);
const match = html.match(/const glyphs = \{([\s\S]*?)\n\}\n<\/script>/);
if (!match) throw new Error("glyph table not found");
const entries = [
...match[1].matchAll(/"([^"]+)"\s*:\s*"([0-9a-fA-F]+)"/g),
].map((m) => [m[1], m[2].toLowerCase()]);
const lines = entries
.filter(([name]) => name.startsWith("nf-"))
.sort(([a], [b]) => a.localeCompare(b))
.map(([name, hex]) => `${name}\t${hex}`);
fs.mkdirSync(path.dirname(DB), { recursive: true });
const next = `${lines.join("\n")}\n`;
const nextBytes = zlib.brotliCompressSync(next, {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 11,
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
},
});
let prev = null;
if (fs.existsSync(DB)) {
const raw = fs.readFileSync(DB);
prev = zlib.brotliDecompressSync(raw).toString("utf8");
}
if (next !== prev) fs.writeFileSync(DB, nextBytes);
return lines.length;
}
function normalizeName(s) {
return s.trim().toLowerCase().replace(/\s+/g, "_");
}
function findMatches(db, query) {
const q = normalizeName(query);
const variants = new Set([q]);
if (!q.startsWith("nf-")) {
variants.add(`nf-${q}`);
variants.add(`nf-md-${q}`);
variants.add(`nf-fa-${q}`);
variants.add(`nf-fae-${q}`);
variants.add(`nf-cod-${q}`);
variants.add(`nf-oct-${q}`);
}
const exact = db.filter((x) => variants.has(x.name.toLowerCase()));
if (exact.length) return exact;
const needle = q.replace(/^nf-[a-z]+-/, "");
return db.filter((x) => x.name.toLowerCase().includes(needle));
}
function javaEscape(cp) {
if (cp <= 0xffff)
return `\\u${cp.toString(16).toUpperCase().padStart(4, "0")}`;
const n = cp - 0x10000;
const hi = 0xd800 + (n >> 10);
const lo = 0xdc00 + (n & 0x3ff);
return `\\u${hi.toString(16).toUpperCase()}\\u${lo.toString(16).toUpperCase()}`;
}
function eightHex(cp) {
return cp.toString(16).toUpperCase().padStart(8, "0");
}
function render(item, format) {
const cp = Number.parseInt(item.hex, 16);
const hexUpper = item.hex.toUpperCase();
const glyph = String.fromCodePoint(cp);
const row = {
name: item.name,
hex: item.hex,
codepoint: `U+${hexUpper}`,
glyph,
js: `\\u{${hexUpper}}`,
html: `&#x${hexUpper};`,
java: javaEscape(cp),
jsonEscape: javaEscape(cp),
css: `\\${hexUpper}`,
cssContent: `content: "\\${hexUpper}";`,
python: `\\U${eightHex(cp)}`,
rust: `\\u{${hexUpper}}`,
go: `\\U${eightHex(cp)}`,
c: `\\U${eightHex(cp)}`,
cpp: `\\U${eightHex(cp)}`,
swift: `\\u{${hexUpper}}`,
lua: `utf8.char(0x${hexUpper})`,
shell: `$'\\U${eightHex(cp)}'`,
printf: `printf '\\U${eightHex(cp)}\\n'`,
toml: `\\U${eightHex(cp)}`,
yaml: `\\U${eightHex(cp)}`,
vim: `nr2char(0x${hexUpper})`,
elisp: `(string #x${hexUpper})`,
wezterm: `wezterm.nerdfonts.${item.name.replace(/^nf-/, "").replace(/-/g, "_")}`,
};
switch (format) {
case "hex":
return row.hex;
case "codepoint":
return row.codepoint;
case "glyph":
return row.glyph;
case "js":
return row.js;
case "html":
return row.html;
case "java":
return row.java;
case "json-escape":
return row.jsonEscape;
case "css":
return row.css;
case "css-content":
return row.cssContent;
case "python":
return row.python;
case "rust":
return row.rust;
case "go":
return row.go;
case "c":
return row.c;
case "cpp":
return row.cpp;
case "swift":
return row.swift;
case "lua":
return row.lua;
case "shell":
return row.shell;
case "printf":
return row.printf;
case "toml":
return row.toml;
case "yaml":
return row.yaml;
case "vim":
return row.vim;
case "elisp":
return row.elisp;
case "wezterm":
return row.wezterm;
case "json":
return JSON.stringify(row);
case "all":
return `${row.name}\t${row.hex}\t${row.codepoint}\t${row.glyph}`;
default:
throw new Error(`bad format: ${format}`);
}
}
async function main() {
const args = process.argv.slice(2);
if (args.includes("-h") || args.includes("--help")) usage(0);
const refresh = args.includes("--refresh");
const rest = args.filter((x) => x !== "--refresh");
const query = rest[0];
const format = rest[1] ?? "codepoint";
if (refresh) {
const count = await refreshDb();
if (!query) {
console.error(`updated ${count} icons: ${DB}`);
return;
}
}
if (!query || !FORMATS.has(format)) usage(2);
let db = readDb();
let matches = findMatches(db, query);
if (matches.length === 0) {
await refreshDb();
db = readDb();
matches = findMatches(db, query);
}
if (matches.length === 0) process.exit(1);
const exact = matches.find(
(x) => x.name.toLowerCase() === normalizeName(query),
);
const out = exact ? [exact] : matches;
console.log(out.map((x) => render(x, format)).join("\n"));
}
main().catch((err) => {
console.error(err.message);
process.exit(1);
});