import { parse } from 'csv-parse/browser/esm/sync'; import yaml from 'js-yaml'; /** * 全局缓存已加载的 CSV 内容 */ const csvCache = new Map[]>(); /** * 解析 front matter * @param content 包含 front matter 的内容 * @returns 解析结果,包含 front matter 和剩余内容 */ function parseFrontMatter(content: string): { frontmatter?: JSONObject; remainingContent: string } { // 检查是否以 --- 开头 if (!content.trim().startsWith('---')) { return { remainingContent: content }; } // 分割内容 const parts = content.split(/(?:^|\n)---\s*\n/); // 至少需要三个部分:空字符串、front matter、剩余内容 if (parts.length < 3) { return { remainingContent: content }; } try { // 解析 YAML front matter const frontmatterStr = parts[1].trim(); const frontmatter = yaml.load(frontmatterStr) as JSONObject; // 剩余内容是第三部分及之后的所有内容 const remainingContent = parts.slice(2).join('---\n').trimStart(); return { frontmatter, remainingContent }; } catch (error) { console.warn('Failed to parse front matter:', error); return { remainingContent: content }; } } /** * 加载 CSV 文件 * @template T 返回数据的类型,默认为 Record */ export async function loadCSV>(path: string): Promise> { if (csvCache.has(path)) { return csvCache.get(path)! as CSV; } const response = await fetch(path); const content = await response.text(); // 解析 front matter const { frontmatter, remainingContent } = parseFrontMatter(content); const records = parse(remainingContent, { columns: true, comment: '#', trim: true, skipEmptyLines: true }); const result = records as Record[]; // 添加 front matter 到结果中 const csvResult = result as CSV; if (frontmatter) { csvResult.frontmatter = frontmatter; for(const each of result){ Object.setPrototypeOf(each, frontmatter); } } csvCache.set(path, result); return csvResult; } type JSONData = JSONArray | JSONObject | string | number | boolean | null; interface JSONArray extends Array {} interface JSONObject extends Record {} export type CSV = T[] & { frontmatter?: JSONObject; } export function processVariables (body: string, currentRow: T, csv: CSV, filtered?: T[], remix?: boolean): string { const rolled = filtered || csv; function replaceProp(key: string) { const row = remix ? rolled[Math.floor(Math.random() * rolled.length)] : currentRow; const frontMatter = csv.frontmatter; if(key in row) return row[key]; if(frontMatter && key in frontMatter) return frontMatter[key]; return `{{${key}}}`; } return body.replace(/\{\{(\w+)\}\}/g, (_, key) => `${replaceProp(key)}`); }