Add basic support for markdown sources
This commit is contained in:
parent
1848d3cfb6
commit
1c64a941cd
9 changed files with 118 additions and 50 deletions
13
biome.json
13
biome.json
|
@ -8,14 +8,8 @@
|
|||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": [
|
||||
"dist",
|
||||
".next",
|
||||
"public",
|
||||
"*.d.ts",
|
||||
"*.json",
|
||||
"build",
|
||||
"*.svelte",
|
||||
".svelte-kit"
|
||||
"*.json"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
|
@ -39,10 +33,5 @@
|
|||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
},
|
||||
"css": {
|
||||
"parser": {
|
||||
"cssModules": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"scripts": {
|
||||
"fmt": "bunx --bun biome check --fix",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,3 +5,4 @@ author: "Endeavorance <hello@endeavorance.camp>"
|
|||
version: 0.0.1
|
||||
files:
|
||||
- ./spires/**/*.yaml
|
||||
- ./spires/**/*.md
|
||||
|
|
7
playbill/spires/lore/ancients.md
Normal file
7
playbill/spires/lore/ancients.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
$define: lore
|
||||
id: ancients
|
||||
name: Ancients
|
||||
---
|
||||
|
||||
Ancients are things and such, so, yknow.
|
|
@ -9,7 +9,7 @@ const BindingSchema = z.object({
|
|||
name: z.string().default("Unnamed playbill"),
|
||||
author: z.string().optional(),
|
||||
version: z.string(),
|
||||
files: z.array(z.string()).default(["**/*.yaml"]),
|
||||
files: z.array(z.string()).default(["**/*.yaml", "**/*.md"]),
|
||||
});
|
||||
|
||||
type Binding = z.infer<typeof BindingSchema>;
|
||||
|
|
24
src/index.ts
24
src/index.ts
|
@ -10,7 +10,7 @@ import {
|
|||
import { loadResourceFile } from "./resource";
|
||||
import { usage } from "./usage";
|
||||
|
||||
class CLIError extends Error { }
|
||||
class CLIError extends Error {}
|
||||
|
||||
async function main(): Promise<boolean> {
|
||||
// Parse command line arguments
|
||||
|
@ -67,7 +67,7 @@ async function main(): Promise<boolean> {
|
|||
* Log a message if the vervose flag has been set
|
||||
* @param msg - The message to log
|
||||
*/
|
||||
function v(msg: string) {
|
||||
function verboseLog(msg: string) {
|
||||
if (VERBOSE) {
|
||||
console.log(chalk.dim(msg));
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ async function main(): Promise<boolean> {
|
|||
|
||||
const bindingPath = args[0] ?? "./binding.yaml";
|
||||
|
||||
v(`Building Playbill with binding: ${bindingPath}`);
|
||||
verboseLog(`Building Playbill with binding: ${bindingPath}`);
|
||||
|
||||
// Check if the binding file exists
|
||||
const bindingFileCheck = Bun.file(bindingPath);
|
||||
|
@ -87,21 +87,23 @@ async function main(): Promise<boolean> {
|
|||
|
||||
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[] = [];
|
||||
|
||||
v("Loading resources");
|
||||
verboseLog("Loading resources");
|
||||
|
||||
// Load resources listed in the binding file
|
||||
for (const filepath of binding.files) {
|
||||
v(`↳ Loading ${filepath}`);
|
||||
verboseLog(`↳ Loading ${filepath}`);
|
||||
const loaded = await loadResourceFile(filepath);
|
||||
v(` ↳ Loaded ${loaded.length} resources`);
|
||||
verboseLog(` ↳ Loaded ${loaded.length} resources`);
|
||||
loadedResources.push(...loaded);
|
||||
}
|
||||
|
||||
v("Constructing playbill");
|
||||
verboseLog("Constructing playbill");
|
||||
|
||||
// Consjtruct the playbill object
|
||||
const playbill = getEmptyPlaybill();
|
||||
|
@ -143,7 +145,7 @@ async function main(): Promise<boolean> {
|
|||
}
|
||||
|
||||
// Evaluate directives in descriptions for all resources
|
||||
v("Processing descriptions");
|
||||
verboseLog("Processing descriptions");
|
||||
for (const resource of loadedResources) {
|
||||
try {
|
||||
resource.description = await processLang(resource.description, playbill);
|
||||
|
@ -161,7 +163,7 @@ async function main(): Promise<boolean> {
|
|||
}
|
||||
|
||||
// Validate the playbill object
|
||||
v("Validating playbill");
|
||||
verboseLog("Validating playbill");
|
||||
const validatedPlaybill = ValidatedPlaybillSchema.safeParse(playbill);
|
||||
|
||||
if (!validatedPlaybill.success) {
|
||||
|
@ -170,7 +172,7 @@ async function main(): Promise<boolean> {
|
|||
return false;
|
||||
}
|
||||
|
||||
v("Playbill validated");
|
||||
verboseLog("Playbill validated");
|
||||
|
||||
// If --check is set, exit here
|
||||
if (options.check) {
|
||||
|
|
50
src/markdown.ts
Normal file
50
src/markdown.ts
Normal 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()}
|
||||
`;
|
||||
}
|
|
@ -108,6 +108,7 @@ export const AbilitySchema = PlaybillResourceSchema.extend({
|
|||
roll: RollTypeSchema.or(TalentSchema).default("none"),
|
||||
banes: z.array(z.string()).default([]),
|
||||
boons: z.array(z.string()).default([]),
|
||||
effects: AbilityEffectSchema.default({}),
|
||||
});
|
||||
|
||||
export type PlaybillAbility = z.infer<typeof AbilitySchema>;
|
||||
|
|
|
@ -4,9 +4,11 @@ import {
|
|||
AnyResourceSchema,
|
||||
ResourceMap,
|
||||
} from "./playbill-schema";
|
||||
import { extractFrontmatter, extractMarkdown } from "./markdown";
|
||||
|
||||
export class ResourceFileError extends Error { }
|
||||
export class NullResourceError extends Error { }
|
||||
type FileFormat = "yaml" | "markdown";
|
||||
export class ResourceFileError extends Error {}
|
||||
export class NullResourceError extends Error {}
|
||||
|
||||
/**
|
||||
* Load and process all documents in a yaml file at the given path
|
||||
|
@ -20,34 +22,49 @@ export async function loadResourceFile(
|
|||
filePath: string,
|
||||
): Promise<AnyResource[]> {
|
||||
const file = Bun.file(filePath);
|
||||
const format: FileFormat =
|
||||
filePath.split(".").pop() === "md" ? "markdown" : "yaml";
|
||||
const text = await file.text();
|
||||
|
||||
const parsedDocs = YAML.parseAllDocuments(text);
|
||||
if (format === "yaml") {
|
||||
const parsedDocs = YAML.parseAllDocuments(text);
|
||||
|
||||
if (parsedDocs.some((doc) => doc.toJS() === null)) {
|
||||
throw new NullResourceError(`Null resource defined in ${filePath}`);
|
||||
}
|
||||
|
||||
const errors = parsedDocs.flatMap((doc) => doc.errors);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ResourceFileError(`Error parsing ${filePath}: ${errors}`);
|
||||
}
|
||||
|
||||
const collection: AnyResource[] = [];
|
||||
|
||||
for (const doc of parsedDocs) {
|
||||
if (doc.errors.length > 0) {
|
||||
throw new ResourceFileError(`Error parsing ${filePath}: ${doc.errors}`);
|
||||
if (parsedDocs.some((doc) => doc.toJS() === null)) {
|
||||
throw new NullResourceError(`Null resource defined in ${filePath}`);
|
||||
}
|
||||
|
||||
const raw = doc.toJS();
|
||||
const parsed = AnyResourceSchema.parse(raw);
|
||||
const type = parsed.$define;
|
||||
const schemaToUse = ResourceMap[type];
|
||||
const validated = schemaToUse.parse(parsed);
|
||||
collection.push(validated);
|
||||
const errors = parsedDocs.flatMap((doc) => doc.errors);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ResourceFileError(`Error parsing ${filePath}: ${errors}`);
|
||||
}
|
||||
|
||||
const collection: AnyResource[] = [];
|
||||
|
||||
for (const doc of parsedDocs) {
|
||||
if (doc.errors.length > 0) {
|
||||
throw new ResourceFileError(`Error parsing ${filePath}: ${doc.errors}`);
|
||||
}
|
||||
|
||||
const raw = doc.toJS();
|
||||
const parsed = AnyResourceSchema.parse(raw);
|
||||
const type = parsed.$define;
|
||||
const schemaToUse = ResourceMap[type];
|
||||
const validated = schemaToUse.parse(parsed);
|
||||
collection.push(validated);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
return collection;
|
||||
const frontmatter = extractFrontmatter(text);
|
||||
const markdown = extractMarkdown(text);
|
||||
|
||||
const together = {
|
||||
...frontmatter,
|
||||
description: markdown,
|
||||
};
|
||||
|
||||
const parsed = AnyResourceSchema.parse(together);
|
||||
return [parsed];
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue