Add basic support for markdown sources

This commit is contained in:
Endeavorance 2025-02-21 19:38:15 -05:00
parent 1848d3cfb6
commit 1c64a941cd
9 changed files with 118 additions and 50 deletions

View file

@ -8,14 +8,8 @@
"files": { "files": {
"ignoreUnknown": false, "ignoreUnknown": false,
"ignore": [ "ignore": [
"dist",
".next",
"public",
"*.d.ts", "*.d.ts",
"*.json", "*.json"
"build",
"*.svelte",
".svelte-kit"
] ]
}, },
"formatter": { "formatter": {
@ -39,10 +33,5 @@
"formatter": { "formatter": {
"quoteStyle": "double" "quoteStyle": "double"
} }
},
"css": {
"parser": {
"cssModules": true
}
} }
} }

View file

@ -18,6 +18,7 @@
"scripts": { "scripts": {
"fmt": "bunx --bun biome check --fix", "fmt": "bunx --bun biome check --fix",
"build": "bun build ./src/index.ts --outfile muse --compile", "build": "bun build ./src/index.ts --outfile muse --compile",
"demo": "bun run ./src/index.ts -- ./playbill/binding.yaml" "demo": "bun run ./src/index.ts -- ./playbill/binding.yaml",
"build:install": "bun run build && mv muse ~/.local/bin/muse"
} }
} }

View file

@ -5,3 +5,4 @@ author: "Endeavorance <hello@endeavorance.camp>"
version: 0.0.1 version: 0.0.1
files: files:
- ./spires/**/*.yaml - ./spires/**/*.yaml
- ./spires/**/*.md

View file

@ -0,0 +1,7 @@
---
$define: lore
id: ancients
name: Ancients
---
Ancients are things and such, so, yknow.

View file

@ -9,7 +9,7 @@ const BindingSchema = z.object({
name: z.string().default("Unnamed playbill"), name: z.string().default("Unnamed playbill"),
author: z.string().optional(), author: z.string().optional(),
version: z.string(), version: z.string(),
files: z.array(z.string()).default(["**/*.yaml"]), files: z.array(z.string()).default(["**/*.yaml", "**/*.md"]),
}); });
type Binding = z.infer<typeof BindingSchema>; type Binding = z.infer<typeof BindingSchema>;

View file

@ -67,7 +67,7 @@ async function main(): Promise<boolean> {
* Log a message if the vervose flag has been set * Log a message if the vervose flag has been set
* @param msg - The message to log * @param msg - The message to log
*/ */
function v(msg: string) { function verboseLog(msg: string) {
if (VERBOSE) { if (VERBOSE) {
console.log(chalk.dim(msg)); console.log(chalk.dim(msg));
} }
@ -75,7 +75,7 @@ async function main(): Promise<boolean> {
const bindingPath = args[0] ?? "./binding.yaml"; const bindingPath = args[0] ?? "./binding.yaml";
v(`Building Playbill with binding: ${bindingPath}`); verboseLog(`Building Playbill with binding: ${bindingPath}`);
// Check if the binding file exists // Check if the binding file exists
const bindingFileCheck = Bun.file(bindingPath); const bindingFileCheck = Bun.file(bindingPath);
@ -87,21 +87,23 @@ async function main(): Promise<boolean> {
const binding = await loadBindingFile(bindingPath); const binding = await loadBindingFile(bindingPath);
v(`↳ Binding loaded with ${binding.files.length} associated resources`); verboseLog(
`↳ Binding loaded with ${binding.files.length} associated resources`,
);
const loadedResources: AnyResource[] = []; const loadedResources: AnyResource[] = [];
v("Loading resources"); verboseLog("Loading resources");
// Load resources listed in the binding file // Load resources listed in the binding file
for (const filepath of binding.files) { for (const filepath of binding.files) {
v(`↳ Loading ${filepath}`); verboseLog(`↳ Loading ${filepath}`);
const loaded = await loadResourceFile(filepath); const loaded = await loadResourceFile(filepath);
v(` ↳ Loaded ${loaded.length} resources`); verboseLog(` ↳ Loaded ${loaded.length} resources`);
loadedResources.push(...loaded); loadedResources.push(...loaded);
} }
v("Constructing playbill"); verboseLog("Constructing playbill");
// Consjtruct the playbill object // Consjtruct the playbill object
const playbill = getEmptyPlaybill(); const playbill = getEmptyPlaybill();
@ -143,7 +145,7 @@ async function main(): Promise<boolean> {
} }
// Evaluate directives in descriptions for all resources // Evaluate directives in descriptions for all resources
v("Processing descriptions"); verboseLog("Processing descriptions");
for (const resource of loadedResources) { for (const resource of loadedResources) {
try { try {
resource.description = await processLang(resource.description, playbill); resource.description = await processLang(resource.description, playbill);
@ -161,7 +163,7 @@ async function main(): Promise<boolean> {
} }
// Validate the playbill object // Validate the playbill object
v("Validating playbill"); verboseLog("Validating playbill");
const validatedPlaybill = ValidatedPlaybillSchema.safeParse(playbill); const validatedPlaybill = ValidatedPlaybillSchema.safeParse(playbill);
if (!validatedPlaybill.success) { if (!validatedPlaybill.success) {
@ -170,7 +172,7 @@ async function main(): Promise<boolean> {
return false; return false;
} }
v("Playbill validated"); verboseLog("Playbill validated");
// If --check is set, exit here // If --check is set, exit here
if (options.check) { if (options.check) {

50
src/markdown.ts Normal file
View file

@ -0,0 +1,50 @@
import YAML from "yaml";
const FRONTMATTER_REGEX = /^---[\s\S]*?---/gm;
/**
* Attempt to parse YAML frontmatter from a mixed yaml/md doc
* @param content The raw markdown content
* @returns Any successfully parsed frontmatter
*/
export function extractFrontmatter(content: string) {
// If it does not start with `---`, it is invalid for frontmatter
if (content.trim().indexOf("---") !== 0) {
return {};
}
if (FRONTMATTER_REGEX.test(content)) {
const frontmatterString = content.match(FRONTMATTER_REGEX)?.[0] ?? "";
const cleanFrontmatter = frontmatterString.replaceAll("---", "").trim();
return YAML.parse(cleanFrontmatter);
}
return {};
}
export function extractMarkdown(content: string): string {
if (content.trim().indexOf("---") !== 0) {
return content;
}
return content.replace(FRONTMATTER_REGEX, "").trim();
}
/**
* Combine yaml frontmatter and markdown into a single string
* @param markdown The markdown to combine with frontmatter
* @param frontmatter The frontmatter shape to combine with markdown
* @returns A combined document with yaml frontmatter and markdown below
*/
export function combineMarkdownAndFrontmatter(
markdown: string,
frontmatter: unknown,
) {
const frontmatterToWrite: unknown = structuredClone(frontmatter);
return `---
${YAML.stringify(frontmatterToWrite)}
---
${markdown.trim()}
`;
}

View file

@ -108,6 +108,7 @@ export const AbilitySchema = PlaybillResourceSchema.extend({
roll: RollTypeSchema.or(TalentSchema).default("none"), roll: RollTypeSchema.or(TalentSchema).default("none"),
banes: z.array(z.string()).default([]), banes: z.array(z.string()).default([]),
boons: z.array(z.string()).default([]), boons: z.array(z.string()).default([]),
effects: AbilityEffectSchema.default({}),
}); });
export type PlaybillAbility = z.infer<typeof AbilitySchema>; export type PlaybillAbility = z.infer<typeof AbilitySchema>;

View file

@ -4,7 +4,9 @@ import {
AnyResourceSchema, AnyResourceSchema,
ResourceMap, ResourceMap,
} from "./playbill-schema"; } from "./playbill-schema";
import { extractFrontmatter, extractMarkdown } from "./markdown";
type FileFormat = "yaml" | "markdown";
export class ResourceFileError extends Error {} export class ResourceFileError extends Error {}
export class NullResourceError extends Error {} export class NullResourceError extends Error {}
@ -20,8 +22,11 @@ export async function loadResourceFile(
filePath: string, filePath: string,
): Promise<AnyResource[]> { ): Promise<AnyResource[]> {
const file = Bun.file(filePath); const file = Bun.file(filePath);
const format: FileFormat =
filePath.split(".").pop() === "md" ? "markdown" : "yaml";
const text = await file.text(); const text = await file.text();
if (format === "yaml") {
const parsedDocs = YAML.parseAllDocuments(text); const parsedDocs = YAML.parseAllDocuments(text);
if (parsedDocs.some((doc) => doc.toJS() === null)) { if (parsedDocs.some((doc) => doc.toJS() === null)) {
@ -51,3 +56,15 @@ export async function loadResourceFile(
return collection; return collection;
} }
const frontmatter = extractFrontmatter(text);
const markdown = extractMarkdown(text);
const together = {
...frontmatter,
description: markdown,
};
const parsed = AnyResourceSchema.parse(together);
return [parsed];
}