publish(task 3): frontmatter-parser mit yaml + body-split
parseFrontmatter(md) trennt yaml-frontmatter vom markdown-body via regex, parst yaml mit @std/yaml. Frontmatter-interface enthält nostr-publish-konvention: optionale images-liste mit file/role/alt/ license/authors plus Author-interface (name/url/orcid). 4 tests grün. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1e4359aab6
commit
178016f0f4
|
|
@ -0,0 +1,47 @@
|
|||
import { parse as parseYaml } from '@std/yaml'
|
||||
|
||||
export interface Author {
|
||||
name: string
|
||||
url?: string
|
||||
orcid?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export interface ImageEntry {
|
||||
file: string
|
||||
role?: 'cover'
|
||||
alt: string
|
||||
caption?: string
|
||||
license: string | 'UNKNOWN'
|
||||
authors: Author[] | 'UNKNOWN'
|
||||
source_url?: string
|
||||
modifications?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export interface Frontmatter {
|
||||
title: string
|
||||
slug: string
|
||||
date: Date
|
||||
description?: string
|
||||
image?: string
|
||||
cover?: { image?: string; alt?: string; caption?: string }
|
||||
tags?: string[]
|
||||
draft?: boolean
|
||||
license?: string
|
||||
authors?: Author[]
|
||||
images?: ImageEntry[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export function parseFrontmatter(md: string): { fm: Frontmatter; body: string } {
|
||||
const match = md.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/)
|
||||
if (!match) {
|
||||
throw new Error('Frontmatter: no leading --- / --- block found')
|
||||
}
|
||||
const fm = parseYaml(match[1]) as Frontmatter
|
||||
if (!fm || typeof fm !== 'object') {
|
||||
throw new Error('Frontmatter: YAML did not produce an object')
|
||||
}
|
||||
return { fm, body: match[2] }
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
layout: post
|
||||
title: "Sample Title"
|
||||
slug: "sample-slug"
|
||||
description: "A short summary"
|
||||
image: cover.png
|
||||
cover:
|
||||
image: cover.png
|
||||
alt: "Alt text"
|
||||
date: 2024-01-15
|
||||
tags: ["Foo", "Bar"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
Body content here.
|
||||
|
||||

|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { assertEquals, assertThrows } from '@std/assert'
|
||||
import { parseFrontmatter } from '../src/core/frontmatter.ts'
|
||||
|
||||
Deno.test('parseFrontmatter: zerlegt Frontmatter und Body', async () => {
|
||||
const md = await Deno.readTextFile('./tests/fixtures/sample-post.md')
|
||||
const { fm, body } = parseFrontmatter(md)
|
||||
assertEquals(fm.title, 'Sample Title')
|
||||
assertEquals(fm.slug, 'sample-slug')
|
||||
assertEquals(fm.date instanceof Date, true)
|
||||
assertEquals(fm.tags, ['Foo', 'Bar'])
|
||||
assertEquals(fm.cover?.image, 'cover.png')
|
||||
assertEquals(body.trim().startsWith('Body content here.'), true)
|
||||
})
|
||||
|
||||
Deno.test('parseFrontmatter: wirft bei fehlendem Frontmatter', () => {
|
||||
assertThrows(() => parseFrontmatter('no frontmatter here'), Error, 'Frontmatter')
|
||||
})
|
||||
|
||||
Deno.test('parseFrontmatter: wirft bei unvollständigem Frontmatter', () => {
|
||||
assertThrows(() => parseFrontmatter('---\ntitle: x\n'), Error, 'Frontmatter')
|
||||
})
|
||||
|
||||
Deno.test('parseFrontmatter: erhält Leerzeichen in String-Werten', () => {
|
||||
const md = '---\ntitle: "Hello World"\nslug: "h-w"\ndate: 2024-01-01\n---\n\nbody'
|
||||
const { fm } = parseFrontmatter(md)
|
||||
assertEquals(fm.title, 'Hello World')
|
||||
})
|
||||
Loading…
Reference in New Issue