M0-M11 狩夜人笔记初始实现:11里程碑+86测试

This commit is contained in:
hunter-dev
2026-08-24 20:35:22 +08:00
commit 3ebd33536b
106 changed files with 10490 additions and 0 deletions
@@ -0,0 +1,70 @@
// FrontmatterCodec — 文件头 frontmatter 解析/序列化(FR-M1/M2)
// 仅 4 个业务字段:title? / tags? / star? / links?;空数组与默认值不落盘
import type { DocMeta } from './doc.js';
const FENCE = '---';
// 解析:返回 { meta, body };无 frontmatter 时 meta={} body=原文
export function splitFrontmatter(raw: string): { meta: DocMeta; body: string } {
// 必须以 --- 开头(允许前导空白行后紧跟围栏)
const m = raw.match(/^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
if (!m) return { meta: {}, body: raw };
const yaml = m[1];
const body = m[2];
return { meta: parseYaml(yaml), body };
}
// 序列化:把 meta + body 合并为完整文件文本;默认值与空数组不写入
export function joinFrontmatter(meta: DocMeta, body: string): string {
const yaml = serializeYaml(meta);
if (!yaml) return body;
return `${FENCE}\n${yaml}${FENCE}\n${body}`;
}
// ---- 极简 YAML 子集解析(只支持本项目 4 字段的形态)----
function parseYaml(yaml: string): DocMeta {
const meta: DocMeta = {};
const lines = yaml.split(/\r?\n/);
for (const line of lines) {
if (!line.trim() || line.trim().startsWith('#')) continue;
const kv = line.match(/^([a-zA-Z]+):\s*(.*)$/);
if (!kv) continue;
const [, key, val] = kv;
const v = val.trim();
if (key === 'title') meta.title = unquote(v);
else if (key === 'star') meta.star = v === 'true';
else if (key === 'tags') meta.tags = parseList(v);
else if (key === 'links') meta.links = parseList(v);
}
return meta;
}
function parseList(v: string): string[] {
// 内联数组 [a, b] 或流式;本项目只用内联
const m = v.match(/^\[\s*(.*)\s*\]$/);
if (!m) return [];
const inner = m[1].trim();
if (!inner) return [];
return inner.split(',').map(s => unquote(s.trim())).filter(Boolean);
}
function unquote(s: string): string {
const m = s.match(/^['"](.*)['"]$/);
return m ? m[1] : s;
}
// ---- 序列化:剔除默认值与空数组 ----
function serializeYaml(meta: DocMeta): string {
const lines: string[] = [];
if (meta.title !== undefined && meta.title !== '') lines.push(`title: ${quoteIfNeeded(meta.title)}`);
if (meta.tags && meta.tags.length > 0) lines.push(`tags: [${meta.tags.map(quoteIfNeeded).join(', ')}]`);
if (meta.star === true) lines.push('star: true');
if (meta.links && meta.links.length > 0) lines.push(`links: [${meta.links.map(quoteIfNeeded).join(', ')}]`);
return lines.length ? lines.join('\n') + '\n' : '';
}
function quoteIfNeeded(s: string): string {
// 含特殊字符(冒号/井号/方括号/引号/前后空格)则加引号
if (/[:#\[\]"']|^\s|\s$/.test(s)) return JSON.stringify(s);
return s;
}