UNPKG

@paroicms/site-generator-plugin

Version:

ParoiCMS Site Generator Plugin

148 lines (147 loc) 5.23 kB
export function parseMarkdownBulletedList(markdown) { const lines = markdown.split(/\r?\n/); const parsedLines = lines.map(parseLine).filter((line) => !!line); return { home: convertParsedLinesToTree(parsedLines) }; } export function convertParsedLinesToTree(lines) { if (lines.length === 0) { throw new Error("Input array cannot be empty"); } const firstLine = lines[0]; // Ensure first line is a routing document with indent 0 if (firstLine.indent !== 0 || firstLine.kind !== "routingDocument") { throw new Error("First line must be a routing document with zero indentation"); } // Create root node const root = { kind: "routingDocument", typeName: firstLine.typeName, }; // Stack to keep track of parent nodes at each level const stack = [root]; let currentDepth = 0; // Process remaining lines for (let i = 1; i < lines.length; ++i) { const line = lines[i]; const node = createNode(line); // If indent is greater than current level, add as child to last node in stack if (line.indent > currentDepth) { if (line.indent !== currentDepth + 1) { throw new Error(`Invalid indentation level at line ${line.lineNumber}`); } const lastNode = stack[stack.length - 1]; appendChildToParentNode(node, lastNode); stack.push(node); } // If indent is less than current level, pop stack until we reach correct level else if (line.indent < currentDepth) { while (line.indent <= currentDepth) { stack.pop(); --currentDepth; } const lastNode = stack[stack.length - 1]; appendChildToParentNode(node, lastNode); stack.push(node); } // If same indent, add as sibling else { stack.pop(); const lastNode = stack[stack.length - 1]; appendChildToParentNode(node, lastNode); stack.push(node); } currentDepth = line.indent; } return root; } function appendChildToParentNode(child, parent) { if (parent.kind === "routingDocument") { parent.children ??= []; parent.children.push(child); } else if (parent.kind === "regularDocument") { if (child.kind === "routingDocument") { throw new Error(`Regular document type "${parent.typeName}" cannot have ${child.kind} child "${child.typeName}"`); } parent.children ??= []; parent.children.push(child); } else if (parent.kind === "part") { if (child.kind !== "part") { throw new Error(`Part type "${parent.typeName}" cannot have ${child.kind} child "${child.typeName}"`); } parent.children ??= []; parent.children.push(child); } } function createNode(line) { switch (line.kind) { case "routingDocument": return { kind: "routingDocument", typeName: line.typeName, }; case "regularDocument": return { kind: "regularDocument", typeName: line.typeName, }; case "part": if (!line.listName) { throw new Error("Part node must have a listName"); } return { kind: "part", typeName: line.typeName, listName: line.listName, }; default: throw new Error(`Unknown node kind: ${line.kind}`); } } export function parseLine(input, index) { if (!input.trim()) return; const lineNumber = index + 1; // Count indentation (2 spaces per level) const indentSpaces = input.match(/^[ ]+/); const bulletIndex = indentSpaces ? indentSpaces[0].length : 0; const indent = bulletIndex / 2; const bulletChar = input.charAt(bulletIndex); if (bulletChar !== "*" && bulletChar !== "-") { throw new Error(`Missing bullet at line ${lineNumber}`); } const cleanLine = input.substring(bulletIndex + 1).trim(); // list of `article` (regular documents) const regularDocumentMatch = cleanLine.match(/^list of `([^`]+)` \(regular documents\)$/); if (regularDocumentMatch) { return { kind: "regularDocument", indent, typeName: regularDocumentMatch[1], lineNumber, }; } // list of `pageSection` (parts), list name: `partSections` const partMatch = cleanLine.match(/^list of `([^`]+)` \(parts\),? list name: `([^`]+)`$/); if (partMatch) { return { kind: "part", indent, typeName: partMatch[1], listName: partMatch[2], lineNumber, }; } // `pages` (routing document) const routingDocumentMatch = cleanLine.match(/^`([^`]+)` \(routing document\)$/); if (routingDocumentMatch) { return { kind: "routingDocument", indent, typeName: routingDocumentMatch[1], lineNumber, }; } throw new Error(`Invalid line at line ${lineNumber}`); }