UNPKG

textadv

Version:

Text Adventures generator from Markdown files

192 lines 7.06 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import * as fs from 'fs'; import * as YAML from 'yaml'; import path from 'path'; import { Project, Type } from './types.js'; import { removeDiacritics } from './utils.js'; import { parseMarkdown } from './md-parser.js'; function parse(ast, project, basePath) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { let inMeta = false; let childIndex = 0; let node = project; if (ast.type !== 'root') throw new Error('expected root node as parameter'); for (const child of ast.children) { ++childIndex; if (child.type === 'thematicBreak' && childIndex === 1) { inMeta = true; continue; } if (child.type === 'heading' && child.depth === 1) { // project data const { id, name } = parseTitle(child.children[0].value); project.id = id; project.name = name; node = project; } if (child.type === 'heading' && child.depth === 2) { if (inMeta) { inMeta = false; project.meta = parseMeta(child.children[0].value); continue; } const { id, name, type } = parseTitle(child.children[0].value); if (!type) { throw new Error(`Missing heading type on "${name}"`); } const room = project.addChild(type, id); room.name = name; node = room; } if (child.type === 'paragraph') { if (((_a = child.children[0]) === null || _a === void 0 ? void 0 : _a.type) === 'link') { const linkType = ((_b = child.children[0]) === null || _b === void 0 ? void 0 : _b.children[0]).value; switch (linkType) { case 'extends': const url = (_c = child.children[0]) === null || _c === void 0 ? void 0 : _c.url; const newPath = path.join(basePath, url); yield parseFile(newPath, project); break; default: throw new Error(`Invalid link type "${linkType}"`); } } // otherwise, read as text const text = child.children[0].value; if (text) { node.intro.push(text); } } if (child.type === 'list') { const codes = child.children.map((item) => parseCode(item)); node.onInput.push(...codes); } } return project; }); } function parseCode(item) { const paragraph = item.children[0]; if (paragraph.type !== 'paragraph') throw new Error('expected paragraph type'); if (paragraph.children[0].type !== 'text') throw new Error('expected text type'); const itemText = paragraph.children[0].value; const codesList = item.children[1]; if (codesList.type !== 'list') throw new Error('expected list type'); const ops = []; for (const codesListItem of codesList.children) { const codeItemParagraph = codesListItem.children[0]; if (codeItemParagraph.type !== 'paragraph') throw new Error('expected paragraph type'); if (codeItemParagraph.children[0].type !== 'text') throw new Error('expected text type'); const codesListItemText = codeItemParagraph.children[0].value.replace(/""/g, '\\"').trim(); try { ops.push(parseOp(codesListItemText)); } catch (ex) { console.error(`Error parsing codes: ${codesListItemText}`); throw ex; } } return { on: itemText, ops, done: true // TODO: find a way to express "not done" on the markdown }; } function parseTitle(title) { let type, m; title = title.trim(); if (m = title.match(/^📍(.+)$/)) { type = Type.location; title = m[1].trim(); } else if (m = title.match(/^📦(.+)$/)) { type = Type.object; title = m[1].trim(); } if (m = title.match(/^([^\[]+)\[([^\]]+)\]$/)) { return { type, name: m[1], id: m[2], }; } return { type, name: title, id: removeDiacritics(title).toLocaleLowerCase().replace(/[^a-z0-9]+/g, '-') }; } function parseOp(text) { try { const jsonParsed = JSON.parse(text); if (typeof (jsonParsed) === 'string') { return { cmd: 'print', params: [jsonParsed] }; } } catch (err) { // fallthrough } const [cmd, ...params] = text.split(/ +/g); if (!["print", "goto", "set", "clear", "zero", "notzero", "continue", "check-room"].includes(cmd)) { throw new Error(`Invalid command "${cmd}"`); } return { cmd: cmd, params, }; } export function validateProject(project) { const errors = []; const rooms = project.getChildrenByType(Type.location); function validateCodes(codes) { for (const code of codes) { for (const op of code.ops) { if (typeof (op) === 'string') continue; if (op.cmd === 'goto') { if (!rooms[op.params[0]]) { errors.push(`Room "${op.params[0]}" not found`); } } } } } validateCodes(project.onInput); for (const room of project.children) { validateCodes(room.onInput); } return errors; } export function parseFile(filePath, project) { return __awaiter(this, void 0, void 0, function* () { const basePath = path.dirname(filePath); const src = fs.readFileSync(filePath, 'utf8'); const mdAST = parseMarkdown(src); return { project: yield parse(mdAST, project !== null && project !== void 0 ? project : new Project(), basePath), mdAST }; }); } function parseMeta(value) { return YAML.parse(value); } //# sourceMappingURL=parser.js.map