170 lines
5.7 KiB
JavaScript
170 lines
5.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Normalisiert quote und youtube_video: _slug und Dateiname = prefix + lesbarer Teil.
|
|
* - quote: quote-{slugify(author)}
|
|
* - youtube_video: youtube-video-{slugify(title)|slugify(id ohne Präfix)|youtubeId}
|
|
* Aktualisiert alle Referenzen in content/de.
|
|
*/
|
|
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const CONTENT_DE = path.join(__dirname, "..", "content", "de");
|
|
|
|
function slugify(value) {
|
|
if (value == null || value === "") return "";
|
|
return String(value)
|
|
.replace(/_/g, "-")
|
|
.replace(/\s+/g, "-")
|
|
.replace(/[^a-zA-Z0-9-]+/g, "-")
|
|
.replace(/-+/g, "-")
|
|
.replace(/^-|-$/g, "")
|
|
.toLowerCase() || "untitled";
|
|
}
|
|
|
|
function stripPrefix(s, prefixes) {
|
|
let t = s || "";
|
|
for (const p of prefixes) {
|
|
if (t.toLowerCase().startsWith(p.toLowerCase())) {
|
|
t = t.slice(p.length).replace(/^-+/, "");
|
|
break;
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
|
|
function parseJson5(str) {
|
|
try {
|
|
return JSON.parse(str);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ─── Quote: prefix + slugify(author) ─────────────────────────────────────
|
|
function processQuote(quoteDir) {
|
|
if (!fs.existsSync(quoteDir)) return new Map();
|
|
const files = fs.readdirSync(quoteDir).filter((f) => f.endsWith(".json5")).sort();
|
|
const oldToNew = new Map();
|
|
const used = new Set();
|
|
|
|
for (const file of files) {
|
|
const raw = fs.readFileSync(path.join(quoteDir, file), "utf8");
|
|
const data = parseJson5(raw);
|
|
const oldSlug = data?._slug || file.replace(/\.json5$/, "");
|
|
const author = data?.author ?? "";
|
|
const quoteText = data?.quote ?? "";
|
|
let rest = slugify(author) || slugify(quoteText.slice(0, 50)) || "zitat";
|
|
let newSlug = "quote-" + rest;
|
|
let n = 0;
|
|
while (used.has(newSlug)) {
|
|
n++;
|
|
newSlug = "quote-" + rest + (n === 1 ? "-1" : "-" + n);
|
|
}
|
|
used.add(newSlug);
|
|
oldToNew.set(oldSlug, newSlug);
|
|
}
|
|
|
|
for (const file of files) {
|
|
const filePath = path.join(quoteDir, file);
|
|
const data = parseJson5(fs.readFileSync(filePath, "utf8"));
|
|
const oldSlug = data?._slug || file.replace(/\.json5$/, "");
|
|
const newSlug = oldToNew.get(oldSlug);
|
|
if (!newSlug) continue;
|
|
data._slug = newSlug;
|
|
const newPath = path.join(quoteDir, newSlug + ".json5");
|
|
fs.writeFileSync(newPath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
if (path.basename(newPath) !== file) fs.unlinkSync(filePath);
|
|
}
|
|
return oldToNew;
|
|
}
|
|
|
|
// ─── Youtube_video: prefix + title oder id (ohne Präfix) oder youtubeId ───
|
|
const YOUTUBE_ID_PREFIXES = [
|
|
"youtube-video - ",
|
|
"component-youtube-",
|
|
"component-video-",
|
|
"https-www-youtube-com-watch-v-",
|
|
"https-youtu-be-",
|
|
];
|
|
|
|
function processYoutubeVideo(ytDir) {
|
|
if (!fs.existsSync(ytDir)) return new Map();
|
|
const files = fs.readdirSync(ytDir).filter((f) => f.endsWith(".json5")).sort();
|
|
const oldToNew = new Map();
|
|
const used = new Set();
|
|
|
|
for (const file of files) {
|
|
const raw = fs.readFileSync(path.join(ytDir, file), "utf8");
|
|
const data = parseJson5(raw);
|
|
const oldSlug = data?._slug || file.replace(/\.json5$/, "");
|
|
const id = data?.id ?? "";
|
|
const title = data?.title ?? "";
|
|
const youtubeId = data?.youtubeId ?? "";
|
|
let rest = slugify(title) || slugify(stripPrefix(id, YOUTUBE_ID_PREFIXES)) || slugify(stripPrefix(oldSlug, YOUTUBE_ID_PREFIXES)) || youtubeId.toLowerCase() || "untitled";
|
|
let newSlug = "youtube-video-" + rest;
|
|
let n = 0;
|
|
while (used.has(newSlug)) {
|
|
n++;
|
|
newSlug = "youtube-video-" + rest + (n === 1 ? "-1" : "-" + n);
|
|
}
|
|
used.add(newSlug);
|
|
oldToNew.set(oldSlug, newSlug);
|
|
}
|
|
|
|
for (const file of files) {
|
|
const filePath = path.join(ytDir, file);
|
|
const data = parseJson5(fs.readFileSync(filePath, "utf8"));
|
|
const oldSlug = data?._slug || file.replace(/\.json5$/, "");
|
|
const newSlug = oldToNew.get(oldSlug);
|
|
if (!newSlug) continue;
|
|
data._slug = newSlug;
|
|
if (data.id && data.id === oldSlug) data.id = newSlug;
|
|
const newPath = path.join(ytDir, newSlug + ".json5");
|
|
fs.writeFileSync(newPath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
if (path.basename(newPath) !== file) fs.unlinkSync(filePath);
|
|
}
|
|
return oldToNew;
|
|
}
|
|
|
|
// ─── Main ────────────────────────────────────────────────────────────────
|
|
const quoteDir = path.join(CONTENT_DE, "quote");
|
|
const ytDir = path.join(CONTENT_DE, "youtube_video");
|
|
|
|
const mapQuote = processQuote(quoteDir);
|
|
const mapYoutube = processYoutubeVideo(ytDir);
|
|
|
|
const allMaps = [mapQuote, mapYoutube];
|
|
console.log("quote:", Object.fromEntries(mapQuote));
|
|
console.log("youtube_video:", Object.fromEntries(mapYoutube));
|
|
|
|
function walkDir(dir, fn) {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
for (const e of entries) {
|
|
const full = path.join(dir, e.name);
|
|
if (e.isDirectory()) walkDir(full, fn);
|
|
else if (e.name.endsWith(".json5")) fn(full);
|
|
}
|
|
}
|
|
|
|
walkDir(CONTENT_DE, (filePath) => {
|
|
let content = fs.readFileSync(filePath, "utf8");
|
|
let changed = false;
|
|
for (const oldToNew of allMaps) {
|
|
for (const [oldSlug, newSlug] of oldToNew) {
|
|
if (oldSlug === newSlug) continue;
|
|
const escaped = oldSlug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
const newContent = content.replace(new RegExp('"' + escaped + '"', "g"), '"' + newSlug + '"');
|
|
if (newContent !== content) {
|
|
content = newContent;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
if (changed) fs.writeFileSync(filePath, content, "utf8");
|
|
});
|
|
|
|
console.log("Fertig. Quote- und youtube-video-Slugs normalisiert und Referenzen aktualisiert.");
|