Files
letta-code/src/utils/frontmatter.ts
2025-12-15 20:16:25 -08:00

116 lines
2.9 KiB
TypeScript

/**
* Shared frontmatter parsing utility for Markdown files with YAML frontmatter
*/
/**
* Parse a comma-separated string into an array of trimmed, non-empty strings
*/
export function parseCommaSeparatedList(str: string | undefined): string[] {
if (!str || str.trim() === "") return [];
return str
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
}
/**
* Get a string field from a frontmatter object, or undefined if not a string
*/
export function getStringField(
obj: Record<string, string | string[]>,
field: string,
): string | undefined {
const val = obj[field];
return typeof val === "string" ? val : undefined;
}
/**
* Parse frontmatter and content from a markdown file
*/
export function parseFrontmatter(content: string): {
frontmatter: Record<string, string | string[]>;
body: string;
} {
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
const match = content.match(frontmatterRegex);
if (!match || !match[1] || !match[2]) {
return { frontmatter: {}, body: content };
}
const frontmatterText = match[1];
const body = match[2];
const frontmatter: Record<string, string | string[]> = {};
// Parse YAML-like frontmatter (simple key: value pairs and arrays)
const lines = frontmatterText.split("\n");
let currentKey: string | null = null;
let currentArray: string[] = [];
for (const line of lines) {
// Check if this is an array item
if (line.trim().startsWith("-") && currentKey) {
const value = line.trim().slice(1).trim();
currentArray.push(value);
continue;
}
// If we were building an array, save it
if (currentKey && currentArray.length > 0) {
frontmatter[currentKey] = currentArray;
currentKey = null;
currentArray = [];
}
const colonIndex = line.indexOf(":");
if (colonIndex > 0) {
const key = line.slice(0, colonIndex).trim();
const value = line.slice(colonIndex + 1).trim();
currentKey = key;
if (value) {
// Simple key: value pair
frontmatter[key] = value;
currentKey = null;
} else {
// Might be starting an array
currentArray = [];
}
}
}
// Save any remaining array
if (currentKey && currentArray.length > 0) {
frontmatter[currentKey] = currentArray;
}
return { frontmatter, body: body.trim() };
}
/**
* Generate frontmatter string from an object
*/
export function generateFrontmatter(
data: Record<string, string | string[] | undefined>,
): string {
const lines: string[] = ["---"];
for (const [key, value] of Object.entries(data)) {
if (value === undefined) continue;
if (Array.isArray(value)) {
if (value.length > 0) {
lines.push(`${key}:`);
for (const item of value) {
lines.push(` - ${item}`);
}
}
} else {
lines.push(`${key}: ${value}`);
}
}
lines.push("---");
return lines.join("\n");
}