生成链接
This commit is contained in:
@@ -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;
|
||||
});
|
||||
Reference in New Issue
Block a user