Move rendering to playbill

This commit is contained in:
Endeavorance 2025-03-11 20:41:05 -04:00
parent 2e0d4b45ea
commit d550f057c5
5 changed files with 42 additions and 414 deletions

View file

@ -1,15 +1,16 @@
import { watch } from "node:fs/promises";
import {
ValidatedPlaybillSchema,
serializePlaybill,
renderPlaybillToHTML,
renderPlaybillToJSON,
} from "@proscenium/playbill";
import chalk from "chalk";
import { loadFromBinding, resolveBindingPath } from "#filetypes/binding";
import { CLIError, MuseError } from "#lib/errors";
import { renderAsHTML } from "#renderers/html";
import { loadFromBinding, resolveBindingPath } from "#lib/binding";
import { CLIError, FileNotFoundError, MuseError } from "#lib/errors";
import { type CLIArguments, parseCLIArguments } from "#util/args";
import { getAbsoluteDirname } from "#util/files";
import { version } from "../package.json" with { type: "json" };
import path from "node:path";
const usage = `
${chalk.bold("muse")} - Compile and validate Playbills for Proscenium
@ -53,15 +54,33 @@ async function processBinding({ inputFilePath, options }: CLIArguments) {
return ExitCode.Success;
}
let css = "";
if (binding.html?.styles) {
for (const style of binding.html.styles) {
const cssFilePath = path.resolve(binding.bindingFileDirname, style);
const cssFile = Bun.file(cssFilePath);
const cssFileExists = await cssFile.exists();
if (cssFileExists) {
css += `${await cssFile.text()}\n`;
} else {
throw new FileNotFoundError(cssFilePath);
}
}
}
// -- SERIALIZE USING RENDERER --//
let serializedPlaybill = "";
switch (options.renderer) {
case "json":
serializedPlaybill = serializePlaybill(validatedPlaybill.data);
serializedPlaybill = await renderPlaybillToJSON(validatedPlaybill.data);
break;
case "html":
serializedPlaybill = await renderAsHTML(validatedPlaybill.data, binding);
serializedPlaybill = await renderPlaybillToHTML(validatedPlaybill.data, {
styles: css,
});
break;
default:
throw new CLIError(`Unknown renderer: ${options.renderer}`);

View file

@ -1,8 +1,8 @@
import path from "node:path";
import {
PlaybillSchema,
type Playbill,
type UnknownResource,
getEmptyPlaybill,
} from "@proscenium/playbill";
import { Glob } from "bun";
import z from "zod";
@ -115,7 +115,15 @@ export async function loadFromBinding(
}
// -- COMPILE PLAYBILL FROM RESOURCES --//
const playbill = basePlaybill ?? getEmptyPlaybill();
const playbill =
basePlaybill ??
PlaybillSchema.parse({
$define: "playbill",
id: "blank",
name: "Unnamed playbill",
description: "Unnamed Playbill",
version: "0.0.1",
});
playbill.id = binding.id;
playbill.name = binding.name;

View file

@ -1,183 +0,0 @@
import path from "node:path";
import type {
Ability,
Guide,
Method,
Rule,
ValidatedPlaybill,
} from "@proscenium/playbill";
import type { PlaybillBinding } from "lib/binding";
import { markdownToHtml } from "lib/markdown";
import capitalize from "lodash-es/capitalize";
import rehypeFormat from "rehype-format";
import rehypeParse from "rehype-parse";
import rehypeStringify from "rehype-stringify";
import { unified } from "unified";
import { FileNotFoundError } from "#lib/errors";
async function renderGuide(guide: Guide): Promise<string> {
const html = await markdownToHtml(guide.description);
return `<section class="guide flow">
<h2>${guide.name}</h2>
${html}
</section>`;
}
async function renderRule(rule: Rule): Promise<string> {
const html = await markdownToHtml(rule.description);
return `<section class="rule flow">
<h2>${rule.name}</h2>
${html}
</section>`;
}
async function renderAbility(ability: Ability): Promise<string> {
const html = await markdownToHtml(ability.description);
const costs: string[] = [];
if (ability.ap > 0) {
costs.push(`<li>AP Cost: ${ability.ap}</li>`);
}
if (ability.hp > 0) {
costs.push(`<li>HP Cost: ${ability.hp}</li>`);
}
if (ability.ep > 0) {
costs.push(`<li>EP Cost: ${ability.ep}</li>`);
}
const costList = costs.length > 0 ? `<ul>${costs.join("\n")}</ul>` : "";
return `
<div class="ability flow">
<h4>${ability.name}</h4>
<small>${ability.type} / ${ability.xp} XP</small>
${costList}
${html}
</div>`.trim();
}
async function renderMethod(method: Method, playbill: ValidatedPlaybill) {
const descriptionHTML = await markdownToHtml(method.description);
let ranksHTML = "";
let rankNumber = 1;
for (const rank of method.abilities) {
ranksHTML += `<h3>Rank ${rankNumber}</h3><div class="method-rank flow">`;
for (const ability of rank) {
const html = await renderAbility(playbill.ability[ability]);
ranksHTML += html;
}
ranksHTML += "</div>";
rankNumber++;
}
return `<section class="method flow">
<h2>${method.curator}'s Method of ${method.name}</h2>
${descriptionHTML}
${ranksHTML}
</section>`;
}
async function renderItemTags(playbill: ValidatedPlaybill) {
let tagsTableHTML = `
<table>
<thead>
<tr>
<th>Tag</th>
<th>Item Types</th>
<th>Description</th>
</tr>
</thead>
<tbody>
`.trim();
for (const tag of Object.values(playbill.tag)) {
tagsTableHTML += `
<tr>
<td>${tag.name}</td>
<td>${tag.types.map(capitalize).join(", ")}</td>
<td>${await markdownToHtml(tag.description)}</td>
</tr>
`.trim();
}
tagsTableHTML += "</tbody></table>";
return `<section class="tags flow"><h2>Item Tags</h2>${tagsTableHTML}</section>`;
}
async function renderDocument(
playbill: ValidatedPlaybill,
binding: PlaybillBinding,
): Promise<string> {
const guides = await Promise.all(
Object.values(playbill.guide).map(renderGuide),
);
const methods = await Promise.all(
Object.values(playbill.method).map((method) => {
return renderMethod(method, playbill);
}),
);
const rules = await Promise.all(Object.values(playbill.rule).map(renderRule));
let css = "";
if (binding.html?.styles) {
for (const style of binding.html.styles) {
const cssFilePath = path.resolve(binding.bindingFileDirname, style);
const cssFile = Bun.file(cssFilePath);
const cssFileExists = await cssFile.exists();
if (cssFileExists) {
css += `<style>
${await cssFile.text()}
</style>`;
} else {
throw new FileNotFoundError(cssFilePath);
}
}
}
return `
<!doctype html>
<html lang="en">
<head>
${css}
</head>
<body>
<article>
<h1>${playbill.name}</h1>
<small>By ${playbill.author}</small>
<blockquote>${playbill.description}</blockquote>
${guides.join("\n")}
${methods.join("\n")}
${await renderItemTags(playbill)}
${rules.join("\n")}
</article>
</body>
</html>
`.trim();
}
export async function renderAsHTML(
playbill: ValidatedPlaybill,
binding: PlaybillBinding,
): Promise<string> {
const doc = await renderDocument(playbill, binding);
const formatted = await unified()
.use(rehypeParse)
.use(rehypeFormat)
.use(rehypeStringify)
.process(doc);
return String(formatted);
}