From ebaf187d899bb29e6c16e483c5511ef1e900f0e5 Mon Sep 17 00:00:00 2001 From: biss Date: Thu, 18 Jun 2026 15:47:55 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E6=88=90=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + scripts/generate-abbrlinks.mjs | 126 +++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 scripts/generate-abbrlinks.mjs diff --git a/package.json b/package.json index befb19f..2839978 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { + "prebuild": "node scripts/generate-abbrlinks.mjs", "build": "astro build", "clean": "astro sync", "dev": "astro dev", diff --git a/scripts/generate-abbrlinks.mjs b/scripts/generate-abbrlinks.mjs new file mode 100644 index 0000000..cc7ef37 --- /dev/null +++ b/scripts/generate-abbrlinks.mjs @@ -0,0 +1,126 @@ +import { readdir, readFile, writeFile } from 'node:fs/promises'; +import { basename, extname, join, relative } from 'node:path'; + +const POSTS_DIR = join(process.cwd(), 'source', '_posts'); + +const CRC32_TABLE = new Uint32Array(256); + +for (let i = 0; i < 256; i += 1) { + let value = i; + for (let bit = 0; bit < 8; bit += 1) { + value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1; + } + CRC32_TABLE[i] = value >>> 0; +} + +function crc32(input) { + const bytes = new TextEncoder().encode(input); + let crc = 0xffffffff; + + for (const byte of bytes) { + crc = CRC32_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8); + } + + return ((crc ^ 0xffffffff) >>> 0).toString(16); +} + +async function listMarkdownFiles(dir) { + const entries = await readdir(dir, { withFileTypes: true }); + const files = await Promise.all( + entries.map(async (entry) => { + const path = join(dir, entry.name); + if (entry.isDirectory()) return listMarkdownFiles(path); + if (entry.isFile() && extname(entry.name).toLowerCase() === '.md') return [path]; + return []; + }), + ); + + return files.flat(); +} + +function getFrontmatterBounds(content) { + if (!content.startsWith('---')) return null; + + const newline = content.includes('\r\n') ? '\r\n' : '\n'; + const lines = content.split(newline); + const closeIndex = lines.findIndex((line, index) => index > 0 && line.trim() === '---'); + + if (closeIndex === -1) return null; + return { closeIndex, lines, newline }; +} + +function getScalar(frontmatterLines, key) { + const pattern = new RegExp(`^${key}\\s*:\\s*(.*)$`); + const match = frontmatterLines.map((line) => line.match(pattern)).find(Boolean); + if (!match) return ''; + + return match[1] + .trim() + .replace(/^['"]|['"]$/g, '') + .trim(); +} + +function makeSeed(file, frontmatterLines) { + const title = getScalar(frontmatterLines, 'title'); + if (title) return title; + + const filename = getScalar(frontmatterLines, 'filename'); + if (filename) return filename; + + return basename(file, '.md'); +} + +function makeUniqueAbbrlink(seed, usedAbbrlinks) { + let attempt = 0; + let abbrlink = crc32(seed); + + while (usedAbbrlinks.has(abbrlink)) { + attempt += 1; + abbrlink = crc32(`${seed}:${attempt}`); + } + + usedAbbrlinks.add(abbrlink); + return abbrlink; +} + +async function main() { + const files = await listMarkdownFiles(POSTS_DIR); + const usedAbbrlinks = new Set(); + const pending = []; + + for (const file of files) { + const content = await readFile(file, 'utf8'); + const bounds = getFrontmatterBounds(content); + if (!bounds) continue; + + const frontmatterLines = bounds.lines.slice(1, bounds.closeIndex); + const existing = getScalar(frontmatterLines, 'abbrlink'); + + if (existing) { + usedAbbrlinks.add(existing); + continue; + } + + pending.push({ file, content, bounds, frontmatterLines }); + } + + for (const item of pending) { + const seed = makeSeed(item.file, item.frontmatterLines); + const abbrlink = makeUniqueAbbrlink(seed, usedAbbrlinks); + const insertAt = item.bounds.closeIndex; + + item.bounds.lines.splice(insertAt, 0, `abbrlink: ${abbrlink}`); + await writeFile(item.file, item.bounds.lines.join(item.bounds.newline), 'utf8'); + + console.log(`Added abbrlink ${abbrlink} to ${relative(process.cwd(), item.file)}`); + } + + if (pending.length === 0) { + console.log('All posts already have abbrlink.'); + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +});