From e108ca16e0694b995226d9f94ae8c184a4be604a Mon Sep 17 00:00:00 2001 From: Ethan J Lewis Date: Sat, 9 May 2026 09:13:51 -0500 Subject: [PATCH] =?UTF-8?q?feat(mystery):=20parseItem=20=E2=80=94=20markdo?= =?UTF-8?q?wn=20to=20typed=20Item?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/world/loader.test.ts | 55 +++++++++++++++++++++++++++++++++++++++- src/world/loader.ts | 22 ++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/world/loader.test.ts b/src/world/loader.test.ts index e6114da..56b677d 100644 --- a/src/world/loader.test.ts +++ b/src/world/loader.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { parseRoom } from './loader' +import { parseRoom, parseItem } from './loader' const FOYER_MD = `--- id: foyer @@ -161,3 +161,56 @@ items: [] expect(() => parseRoom(md, 'rooms/r.md')).toThrow(/exitDRequires is set but exitDLockedText is missing/) }) }) + +// Add to the bottom of loader.test.ts + +const LAMP_MD = `--- +id: lamp +names: [lamp, oil lamp, torch] +short: an oil lamp +takeable: true +initialState: + lit: false +--- + +An iron oil lamp with a glass chimney. Currently unlit. +` + +describe('parseItem', () => { + it('parses an item with state', () => { + const item = parseItem(LAMP_MD, 'items/lamp.md') + expect(item).toEqual({ + id: 'lamp', + names: ['lamp', 'oil lamp', 'torch'], + short: 'an oil lamp', + long: 'An iron oil lamp with a glass chimney. Currently unlit.', + initialState: { lit: false }, + takeable: true, + }) + }) + + it('uses empty initialState when omitted', () => { + const md = `--- +id: x +names: [x] +short: x +takeable: false +--- + +The long description. +` + const item = parseItem(md, 'items/x.md') + expect(item.initialState).toEqual({}) + }) + + it('throws when body is empty', () => { + const md = `--- +id: x +names: [x] +short: x +takeable: false +--- +` + expect(() => parseItem(md, 'items/x.md')).toThrow(/empty long description/i) + }) +}) diff --git a/src/world/loader.ts b/src/world/loader.ts index 100469e..86ff6e6 100644 --- a/src/world/loader.ts +++ b/src/world/loader.ts @@ -1,7 +1,7 @@ import matter from 'gray-matter' -import type { Room, RoomDescriptions } from './types' +import type { Room, RoomDescriptions, Item } from './types' import type { Direction } from '../engine/types' -import { roomFrontmatterSchema } from './schema' +import { roomFrontmatterSchema, itemFrontmatterSchema } from './schema' const WIKILINK = /^\[\[([^\]|]+)(?:\|[^\]]*)?\]\]$/ @@ -97,3 +97,21 @@ export function parseRoom(raw: string, sourcePath: string): Room { if (fm.safe) room.safe = fm.safe return room } + +export function parseItem(raw: string, sourcePath: string): Item { + const parsed = matter(raw) + const frontmatter = stripWikilink(parsed.data) as Record + const fm = itemFrontmatterSchema.parse(frontmatter) + const long = parsed.content.trim() + if (long.length === 0) { + throw new Error(`${sourcePath}: empty long description`) + } + return { + id: fm.id, + names: fm.names, + short: fm.short, + long, + initialState: fm.initialState, + takeable: fm.takeable, + } +}