| 49 | } |
| 50 | |
| 51 | function parseFrontmatter(input: string): { |
| 52 | data: Record<string, unknown>; |
| 53 | body: string; |
| 54 | } { |
| 55 | const content = input.startsWith("\uFEFF") ? input.slice(1) : input; |
| 56 | if (!content.startsWith("---\n") && !content.startsWith("---\r\n")) { |
| 57 | return { data: {}, body: content }; |
| 58 | } |
| 59 | |
| 60 | const endIndex = content.indexOf("\n---\n", 4); |
| 61 | if (endIndex === -1) { |
| 62 | return { data: {}, body: content }; |
| 63 | } |
| 64 | |
| 65 | const frontmatter = content.slice(4, endIndex); |
| 66 | const body = content.slice(endIndex + 5); |
| 67 | const data: Record<string, unknown> = {}; |
| 68 | let currentArrayKey: string | null = null; |
| 69 | let currentObjectKey: string | null = null; |
| 70 | const currentObject: Record<string, unknown> = {}; |
| 71 | |
| 72 | for (const rawLine of frontmatter.split(/\r?\n/)) { |
| 73 | if (!rawLine.trim()) continue; |
| 74 | |
| 75 | if (currentObjectKey) { |
| 76 | const subMatch = rawLine.match(/^\s{2,}(\w+):\s*(.+)$/); |
| 77 | if (subMatch) { |
| 78 | currentObject[subMatch[1]] = subMatch[2].replace(/^["']|["']$/g, ""); |
| 79 | continue; |
| 80 | } |
| 81 | data[currentObjectKey] = { ...currentObject }; |
| 82 | currentObjectKey = null; |
| 83 | for (const k of Object.keys(currentObject)) delete currentObject[k]; |
| 84 | } |
| 85 | |
| 86 | if (currentArrayKey && rawLine.match(/^\s+-\s+/)) { |
| 87 | const value = rawLine.replace(/^\s+-\s+/, "").replace(/^["']|["']$/g, ""); |
| 88 | (data[currentArrayKey] as string[]).push(value); |
| 89 | continue; |
| 90 | } |
| 91 | |
| 92 | const arrayKeyMatch = rawLine.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*$/); |
| 93 | if (arrayKeyMatch) { |
| 94 | currentArrayKey = arrayKeyMatch[1]; |
| 95 | data[currentArrayKey] = []; |
| 96 | continue; |
| 97 | } |
| 98 | |
| 99 | const kvMatch = rawLine.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s+(.+)$/); |
| 100 | if (kvMatch) { |
| 101 | currentArrayKey = null; |
| 102 | data[kvMatch[1]] = kvMatch[2].replace(/^["']|["']$/g, ""); |
| 103 | continue; |
| 104 | } |
| 105 | |
| 106 | const objectKeyMatch = rawLine.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*$/); |
| 107 | if (objectKeyMatch && !currentArrayKey) { |
| 108 | currentObjectKey = objectKeyMatch[1]; |