ttrpg-tools/src/cli/tools/frontmatter/read-frontmatter.ts

155 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { readFileSync, existsSync } from 'fs';
import yaml from 'js-yaml';
/**
* 读取 CSV frontmatter 的参数
*/
export interface ReadFrontmatterParams {
/**
* CSV 文件路径(相对路径相对于 MCP 服务器工作目录)
*/
csv_file: string;
}
/**
* Frontmatter 数据结构
*/
export interface DeckFrontmatter {
/**
* 字段定义
*/
fields?: CardField[];
/**
* Deck 配置
*/
deck?: DeckConfig;
/**
* 其他自定义属性
*/
[key: string]: unknown;
}
/**
* 字段样式配置
*
* 位置按照卡牌网格,安排 x,y,w,h 四个整数
* 如 5x8 网格中 1,1,5,8 表示覆盖整个卡牌的区域
*
* 字体使用 f:8 来表示 8mm 字体
*
* 朝向使用 u:n/w/s/e 表示字段的上侧朝向北西南东
*/
export interface CardFieldStyle {
/**
* 位置:[x, y, w, h] 网格坐标和尺寸
* 例如:[1, 1, 5, 8] 表示从 (1,1) 开始,宽 5 格,高 8 格
*/
pos?: [number, number, number, number];
/**
* 字体大小:格式 "f:8" 表示 8mm 字体
*/
font?: string;
/**
* 朝向:上侧朝向 "n" | "w" | "s" | "e"(北/西/南/东)
*/
up?: 'n' | 'w' | 's' | 'e';
}
/**
* 卡牌字段定义
*/
export interface CardField {
name: string;
description?: string;
examples?: string[];
style?: CardFieldStyle;
[key: string]: unknown;
}
/**
* Deck 配置
*/
export interface DeckConfig {
size?: string;
grid?: string;
bleed?: number;
padding?: number;
shape?: 'rectangle' | 'circle' | 'hex' | 'diamond';
layers?: string;
back_layers?: string;
[key: string]: unknown;
}
/**
* 读取 CSV frontmatter 的结果
*/
export interface ReadFrontmatterResult {
success: boolean;
frontmatter?: DeckFrontmatter;
message: string;
}
/**
* 解析 CSV 文件的 frontmatter
*/
function parseFrontMatter(content: string): { frontmatter?: DeckFrontmatter; csvContent: string } {
const parts = content.trim().split(/(?:^|\n)---\s*\n/g);
// 至少需要三个部分空字符串、front matter、CSV 内容
if (parts.length !== 3 || parts[0] !== '') {
return { csvContent: content };
}
try {
const frontmatterStr = parts[1].trim();
const frontmatter = yaml.load(frontmatterStr) as DeckFrontmatter | undefined;
const csvContent = parts.slice(2).join('---\n').trimStart();
return { frontmatter, csvContent };
} catch (error) {
console.warn('Failed to parse front matter:', error);
return { csvContent: content };
}
}
/**
* 读取 CSV 文件的 frontmatter
*/
export function readFrontmatter(params: ReadFrontmatterParams): ReadFrontmatterResult {
const { csv_file } = params;
// 检查文件是否存在
if (!existsSync(csv_file)) {
return {
success: false,
message: `文件不存在:${csv_file}`
};
}
try {
// 读取文件内容
const content = readFileSync(csv_file, 'utf-8');
// 解析 frontmatter
const { frontmatter } = parseFrontMatter(content);
if (!frontmatter) {
return {
success: true,
frontmatter: {},
message: `文件 ${csv_file} 没有 frontmatter返回空对象`
};
}
return {
success: true,
frontmatter,
message: `成功读取 ${csv_file} 的 frontmatter`
};
} catch (error) {
return {
success: false,
message: `读取失败:${error instanceof Error ? error.message : '未知错误'}`
};
}
}