From 178016f0f4ee3687f6827d0aafb4993ba1033eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lohrer?= Date: Sat, 18 Apr 2026 05:22:41 +0200 Subject: [PATCH] publish(task 3): frontmatter-parser mit yaml + body-split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- publish/src/core/frontmatter.ts | 47 +++++++++++++++++++++++++++ publish/tests/fixtures/sample-post.md | 17 ++++++++++ publish/tests/frontmatter_test.ts | 27 +++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 publish/src/core/frontmatter.ts create mode 100644 publish/tests/fixtures/sample-post.md create mode 100644 publish/tests/frontmatter_test.ts diff --git a/publish/src/core/frontmatter.ts b/publish/src/core/frontmatter.ts new file mode 100644 index 0000000..2e49ff7 --- /dev/null +++ b/publish/src/core/frontmatter.ts @@ -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] } +} diff --git a/publish/tests/fixtures/sample-post.md b/publish/tests/fixtures/sample-post.md new file mode 100644 index 0000000..252b854 --- /dev/null +++ b/publish/tests/fixtures/sample-post.md @@ -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. + +![pic](image1.jpg) diff --git a/publish/tests/frontmatter_test.ts b/publish/tests/frontmatter_test.ts new file mode 100644 index 0000000..bf01b8f --- /dev/null +++ b/publish/tests/frontmatter_test.ts @@ -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') +})