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": {
|
"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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
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"),
|
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>;
|
||||||
|
|
24
src/index.ts
24
src/index.ts
|
@ -10,7 +10,7 @@ import {
|
||||||
import { loadResourceFile } from "./resource";
|
import { loadResourceFile } from "./resource";
|
||||||
import { usage } from "./usage";
|
import { usage } from "./usage";
|
||||||
|
|
||||||
class CLIError extends Error { }
|
class CLIError extends Error {}
|
||||||
|
|
||||||
async function main(): Promise<boolean> {
|
async function main(): Promise<boolean> {
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
|
@ -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
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"),
|
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>;
|
||||||
|
|
|
@ -4,9 +4,11 @@ import {
|
||||||
AnyResourceSchema,
|
AnyResourceSchema,
|
||||||
ResourceMap,
|
ResourceMap,
|
||||||
} from "./playbill-schema";
|
} from "./playbill-schema";
|
||||||
|
import { extractFrontmatter, extractMarkdown } from "./markdown";
|
||||||
|
|
||||||
export class ResourceFileError extends Error { }
|
type FileFormat = "yaml" | "markdown";
|
||||||
export class NullResourceError extends Error { }
|
export class ResourceFileError extends Error {}
|
||||||
|
export class NullResourceError extends Error {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load and process all documents in a yaml file at the given path
|
* Load and process all documents in a yaml file at the given path
|
||||||
|
@ -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)) {
|
||||||
|
@ -50,4 +55,16 @@ 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];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue