Support multidocument files
This commit is contained in:
parent
c75752b1b7
commit
85b2d5cad7
2 changed files with 191 additions and 22 deletions
147
src/index.ts
147
src/index.ts
|
@ -1,5 +1,82 @@
|
|||
import YAML from "yaml";
|
||||
const FRONTMATTER_REGEX = /^---[\s\S]*?---/gm;
|
||||
import YAML, { YAMLError } from "yaml";
|
||||
|
||||
export type EMDYData = Record<string, unknown>;
|
||||
|
||||
type Mode = "yaml" | "markdown";
|
||||
interface PendingDocument {
|
||||
content: string;
|
||||
markdownKey: string;
|
||||
}
|
||||
|
||||
export class EMDYError extends Error {
|
||||
name = "EMDYError";
|
||||
}
|
||||
|
||||
export class EMDYParseError extends EMDYError {
|
||||
name = "EMDYParseError";
|
||||
original?: Error;
|
||||
|
||||
constructor(message: string, original?: Error) {
|
||||
super(`EMDY Parse Error: ${message}`);
|
||||
this.original = original;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single PendingDocument into EMDYData
|
||||
* @param document The PendingDocument to parse
|
||||
* @returns The parsed EMDYData
|
||||
*/
|
||||
function parseDocument(document: PendingDocument): EMDYData {
|
||||
const yamlLines: string[] = [];
|
||||
const markdownLines: string[] = [];
|
||||
const contentLines = document.content.trim().split("\n");
|
||||
|
||||
let mode: Mode = "markdown";
|
||||
|
||||
for (const line of contentLines) {
|
||||
// Handle boundary lines
|
||||
if (line.trim() === "---") {
|
||||
// Upon encountering a boundary in YAML mode, switch to markdown mode
|
||||
if (mode === "yaml") {
|
||||
mode = "markdown";
|
||||
}
|
||||
|
||||
// When in markdown mode (default), only switch to YAML mode
|
||||
// if no markdown has been collected yet. This ensures that
|
||||
// YAML frontmatter is only parsed if it exists at the start of the document.
|
||||
else if (mode === "markdown" && markdownLines.length === 0) {
|
||||
mode = "yaml";
|
||||
}
|
||||
|
||||
// Continue the loop to skip the boundary line
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle content lines
|
||||
if (mode === "yaml") {
|
||||
yamlLines.push(line);
|
||||
} else {
|
||||
markdownLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
const yamlContent = yamlLines.join("\n").trim();
|
||||
const markdownContent = markdownLines.join("\n").trim();
|
||||
|
||||
try {
|
||||
return {
|
||||
...YAML.parse(yamlContent || "{}"),
|
||||
[document.markdownKey]: markdownContent,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof YAMLError) {
|
||||
throw new EMDYParseError("Failed to parse YAML frontmatter", error);
|
||||
}
|
||||
|
||||
throw new EMDYError(`An unexpected error occured: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a markdown document with optional YAML frontmatter into an object
|
||||
|
@ -8,23 +85,8 @@ const FRONTMATTER_REGEX = /^---[\s\S]*?---/gm;
|
|||
* @param markdownKey The key to use for the markdown content
|
||||
* @returns The parsed markdown content and frontmatter
|
||||
*/
|
||||
function parse(
|
||||
content: string,
|
||||
markdownKey = "content",
|
||||
): Record<string, unknown> {
|
||||
const markdown = content.trim().replace(FRONTMATTER_REGEX, "").trim();
|
||||
let frontmatter: Record<string, unknown> = {};
|
||||
|
||||
if (FRONTMATTER_REGEX.test(content)) {
|
||||
const frontmatterString = content.match(FRONTMATTER_REGEX)?.[0] ?? "";
|
||||
const cleanFrontmatter = frontmatterString.replaceAll("---", "").trim();
|
||||
frontmatter = YAML.parse(cleanFrontmatter);
|
||||
}
|
||||
|
||||
return {
|
||||
[markdownKey]: markdown,
|
||||
...frontmatter,
|
||||
};
|
||||
export function parse(content: string, markdownKey = "content"): EMDYData {
|
||||
return parseDocument({ content, markdownKey });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,7 +96,7 @@ function parse(
|
|||
* @param markdownKey The key to use for the markdown content
|
||||
* @returns The parsed markdown content and frontmatter
|
||||
*/
|
||||
function stringify(
|
||||
export function stringify(
|
||||
data: Record<string, unknown>,
|
||||
markdownKey = "content",
|
||||
): string {
|
||||
|
@ -50,11 +112,54 @@ function stringify(
|
|||
|
||||
const stringifiedFrontmatter = YAML.stringify(frontmatter).trim();
|
||||
|
||||
return `---\n${stringifiedFrontmatter}\n---\n${markdown}`;
|
||||
return `
|
||||
---
|
||||
${stringifiedFrontmatter}
|
||||
---
|
||||
${markdown}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a multi-document file into an array of EMDYData objects
|
||||
* @param content The raw markdown content containing multiple documents
|
||||
* @param markdownKey The key to use for the markdown content in each document
|
||||
* @returns An array of parsed EMDYData objects
|
||||
*/
|
||||
export function parseAll(content: string, markdownKey = "content"): EMDYData[] {
|
||||
const documents: PendingDocument[] = [];
|
||||
const lines = content.split("\n");
|
||||
|
||||
let currentDoc: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim().startsWith("===") && currentDoc.length > 0) {
|
||||
documents.push({
|
||||
content: currentDoc.join("\n").trim(),
|
||||
markdownKey,
|
||||
});
|
||||
currentDoc = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
currentDoc.push(line);
|
||||
}
|
||||
|
||||
// Add any remaining content as the last document
|
||||
if (currentDoc.length > 0) {
|
||||
documents.push({
|
||||
content: currentDoc.join("\n").trim(),
|
||||
markdownKey,
|
||||
});
|
||||
}
|
||||
|
||||
// Split by document boundaries
|
||||
return documents.map(parseDocument);
|
||||
}
|
||||
|
||||
const EMDY = {
|
||||
parse,
|
||||
parseAll,
|
||||
stringify,
|
||||
} as const;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue