From 879e508d950d82adf72e7fd1390ee12c40e255f2 Mon Sep 17 00:00:00 2001 From: Endeavorance Date: Sat, 22 Feb 2025 14:30:43 -0500 Subject: [PATCH] Move playbill to its own repo --- biome.json | 5 +- bun.lock | 13 +- package.json | 3 +- src/index.ts | 11 +- src/lang.ts | 107 ------------ src/playbill-schema.ts | 365 ----------------------------------------- src/resource.ts | 4 +- src/usage.ts | 2 +- 8 files changed, 20 insertions(+), 490 deletions(-) delete mode 100644 src/lang.ts delete mode 100644 src/playbill-schema.ts diff --git a/biome.json b/biome.json index 5335960..cc381ce 100644 --- a/biome.json +++ b/biome.json @@ -7,10 +7,7 @@ }, "files": { "ignoreUnknown": false, - "ignore": [ - "*.d.ts", - "*.json" - ] + "ignore": ["*.d.ts", "*.json"] }, "formatter": { "enabled": true, diff --git a/bun.lock b/bun.lock index 373f671..73c6b29 100644 --- a/bun.lock +++ b/bun.lock @@ -2,8 +2,9 @@ "lockfileVersion": 1, "workspaces": { "": { - "name": "lang", + "name": "@proscenium/muse", "dependencies": { + "@proscenium/playbill": "0.0.1", "chalk": "^5.4.1", "yaml": "^2.7.0", "zod": "^3.24.1", @@ -36,13 +37,15 @@ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], - "@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="], + "@proscenium/playbill": ["@proscenium/playbill@0.0.1", "https://git.astral.camp/api/packages/proscenium/npm/%40proscenium%2Fplaybill/-/0.0.1/playbill-0.0.1.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-uvv2Bt1+rjzuXl5y2t2C4KGgVda3orTZMVzb6gNAzStzKutiR3xJZkw2ULukYWWGyZ9BzzkRClSKVYka2Z3Png=="], - "@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], + "@types/bun": ["@types/bun@1.2.3", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="], + + "@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="], "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], - "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], + "bun-types": ["bun-types@1.2.3", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-P7AeyTseLKAvgaZqQrvp3RqFM3yN9PlcLuSTe7SoJOfZkER73mLdT2vEQi8U64S1YvM/ldcNiQjn0Sn7H9lGgg=="], "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], @@ -52,6 +55,6 @@ "yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="], - "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], } } diff --git a/package.json b/package.json index 5670f65..eb735b7 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "dependencies": { "chalk": "^5.4.1", "yaml": "^2.7.0", - "zod": "^3.24.1" + "zod": "^3.24.1", + "@proscenium/playbill": "0.0.1" }, "scripts": { "fmt": "bunx --bun biome check --fix", diff --git a/src/index.ts b/src/index.ts index b0c17ef..77e3b0e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,13 @@ import { parseArgs } from "node:util"; -import chalk from "chalk"; -import { loadBindingFile } from "./binding"; -import { DirectiveError, processLang } from "./lang"; import { type AnyResource, + DirectiveError, ValidatedPlaybillSchema, + compileScript, getEmptyPlaybill, -} from "./playbill-schema"; +} from "@proscenium/playbill"; +import chalk from "chalk"; +import { loadBindingFile } from "./binding"; import { loadResourceFile } from "./resource"; import { usage } from "./usage"; @@ -148,7 +149,7 @@ async function main(): Promise { verboseLog("Processing descriptions"); for (const resource of loadedResources) { try { - resource.description = await processLang(resource.description, playbill); + resource.description = compileScript(resource.description, playbill); } catch (error) { if (error instanceof DirectiveError) { console.error( diff --git a/src/lang.ts b/src/lang.ts deleted file mode 100644 index 58ba167..0000000 --- a/src/lang.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { z } from "zod"; -import { StatsSchema, type Playbill } from "./playbill-schema"; - -const DirectiveSchema = z.enum(["dmg", "ability", "stat", "rule"] as const); -type DirectiveName = z.infer; -type DirectiveHandler = (playbill: Playbill, ...args: string[]) => string; - -export class DirectiveError extends Error { } - -const ProsceniumLangDirectives: Record = { - dmg: (_, amount = "1", type = "phy") => { - const DAMAGE_TYPES = ["phy", "arc", "physical", "arcane"]; - if (!DAMAGE_TYPES.includes(type)) { - throw new DirectiveError(`Unknown damage type: ${type}`); - } - - const finalizedType = type.slice(0, 3); - return `${amount}`; - }, - - ability: (playbill: Playbill, ref: string) => { - if (typeof ref !== "string") { - throw new DirectiveError("Invalid ability reference"); - } - - const foundAbility = playbill.abilities.find((a) => a.id === ref); - - if (foundAbility === undefined) { - throw new DirectiveError(`Unknown ability reference: ${ref}`); - } - - return `${foundAbility.name}`; - }, - - rule: (playbill: Playbill, ref: string) => { - if (typeof ref !== "string") { - throw new DirectiveError("Invalid rule reference"); - } - - const foundRule = playbill.rules.find((r) => r.id === ref); - - if (foundRule === undefined) { - throw new DirectiveError(`Unknown ability reference: ${ref}`); - } - - return `${foundRule.name}`; - }, - stat: (_, stat: string, amount = "") => { - const lower = stat.toLowerCase(); - const parsed = StatsSchema.safeParse(lower); - - if (!parsed.success) { - throw new DirectiveError(`Unknown stat: ${stat}`); - } - - if (amount !== "") { - const amountAsNum = Number.parseInt(amount, 10); - if (Number.isNaN(amountAsNum)) { - throw new DirectiveError(`Invalid amount: ${amount}`); - } - return `${amount} ${stat.toUpperCase()}`; - } - - return `${stat.toUpperCase()}`; - }, -}; - -function processCommands(text: string, playbill: Playbill): string { - const commandPattern = /\{(\w+):([^}]+)\}/g; - - return text.replace(commandPattern, (_match, command, args) => { - const directive = DirectiveSchema.parse(command); - const handler = ProsceniumLangDirectives[directive]; - const parsedArgs = []; - let currentArg = ""; - let inQuotes = false; - - for (let i = 0; i < args.length; i++) { - const char = args[i]; - if (char === '"' && (i === 0 || args[i - 1] !== "\\")) { - inQuotes = !inQuotes; - } else if (char === "," && !inQuotes) { - parsedArgs.push(currentArg.trim().replace(/^"|"$/g, "")); - currentArg = ""; - } else { - currentArg += char; - } - } - parsedArgs.push(currentArg.trim().replace(/^"|"$/g, "")); - - return handler(playbill, ...parsedArgs); - }); -} - -export async function processLang( - input: string, - playbill: Playbill, -): Promise { - const lines = input.split("\n"); - const processedLines: string[] = []; - for (const line of lines) { - const processed = processCommands(line, playbill); - processedLines.push(processed); - } - - return processedLines.join("\n"); -} diff --git a/src/playbill-schema.ts b/src/playbill-schema.ts deleted file mode 100644 index 589d193..0000000 --- a/src/playbill-schema.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { z } from "zod"; - -/* - * TYPE REFERENCES - * ------------------------ - * Ability N/A - * Tag N/A - * Lore N/A - * Rule N/A - * Method Ability - * Item Tag - * Species Ability - * Entity Ability, Species - */ - -// String shapes -export const IDSchema = z.string().regex(/[a-z\-]+/); -export const NameSchema = z.string().max(40).default("Unnamed"); -export const DescriptionSchema = z.string().default("No description"); - -// Constants -export const STAT_ABBREVIATIONS = ["ap", "ep", "hp", "xp"] as const; -export const ITEM_TYPES = ["trinket", "worn", "wielded"] as const; -export const ROLL_TYPES = ["none", "attack", "fate"] as const; -export const TALENTS = [ - "muscle", - "knowledge", - "focus", - "charm", - "cunning", - "spark", -] as const; -export const ABILITY_TYPES = ["action", "cue", "trait"] as const; -export const TALENT_PROWESS = ["novice", "adept", "master"] as const; - -// Enums -export const StatsSchema = z.enum(STAT_ABBREVIATIONS); -export const ItemTypeSchema = z.enum(ITEM_TYPES); -export const RollTypeSchema = z.enum(ROLL_TYPES); -export const AbilityTypeSchema = z.enum(ABILITY_TYPES); -export const TalentSchema = z.enum(TALENTS); -export const TalentProwessSchema = z.enum(TALENT_PROWESS); - -const ResourceTypes = [ - "ability", - "species", - "entity", - "item", - "tag", - "lore", - "method", - "rule", - "playbill", -] as const; -const ResourceTypeSchema = z.enum(ResourceTypes); -type ResourceType = z.infer; - -// Inner utility shapes -export const StatSpreadSchema = z.object({ - ap: z.number().default(0), - ep: z.number().default(0), - hp: z.number().default(0), - xp: z.number().default(0), -}); - -export const TalentSpreadSchema = z.object({ - muscle: TalentProwessSchema.default("novice"), - knowledge: TalentProwessSchema.default("novice"), - focus: TalentProwessSchema.default("novice"), - charm: TalentProwessSchema.default("novice"), - cunning: TalentProwessSchema.default("novice"), - spark: TalentProwessSchema.default("novice"), -}); - -export const PlaybillResourceSchema = z.object({ - $define: ResourceTypeSchema, - id: IDSchema, - name: NameSchema, - description: DescriptionSchema, -}); - -// An object with optional properties to define passive effects -// offered from an ability. These are aggregated by the game -// engine at runtime to determine the final stats of a character. -export const AbilityEffectSchema = z.object({ - bonusMaxHp: z.number().optional(), - bonusMaxAp: z.number().optional(), - bonusMaxEp: z.number().optional(), - muscleProwess: TalentProwessSchema.optional(), - knowledgeProwess: TalentProwessSchema.optional(), - focusProwess: TalentProwessSchema.optional(), - charmProwess: TalentProwessSchema.optional(), - cunningProwess: TalentProwessSchema.optional(), - sparkProwess: TalentProwessSchema.optional(), -}); - -export const BaggageSchema = z.object({ - title: z.string(), - description: z.string(), -}); - -// Playbill component shapes -export const AbilitySchema = PlaybillResourceSchema.extend({ - $define: z.literal("ability"), - name: NameSchema.default("Unnamed Ability"), - type: AbilityTypeSchema, - costs: StatSpreadSchema.default({}), - 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; - -export const EntitySchema = PlaybillResourceSchema.extend({ - $define: z.literal("entity"), - name: NameSchema.default("Unnamed Species"), - species: z.string(IDSchema), - abilities: z.array(IDSchema), - stats: StatSpreadSchema, - talents: TalentSpreadSchema, - damage: z.number().default(0), - baggage: z.array(BaggageSchema).default([]), -}); - -export const ItemTagSchema = PlaybillResourceSchema.extend({ - $define: z.literal("tag"), - name: NameSchema.default("Unnamed Item Tag"), - icon: z.string().url(), -}); - -export const ItemSchema = PlaybillResourceSchema.extend({ - $define: z.literal("item"), - name: NameSchema.default("Unnamed Item"), - type: ItemTypeSchema, - slots: z.number().min(0).max(5).default(0), - tags: z.array(IDSchema).default([]), -}); - -export const LoreSchema = PlaybillResourceSchema.extend({ - $define: z.literal("lore"), - name: NameSchema.default("Unnamed Lore Entry"), - categories: z.array(z.string()).default([]), -}); - -export const MethodSchema = PlaybillResourceSchema.extend({ - $define: z.literal("method"), - name: NameSchema.default("Unnamed Method"), - curator: NameSchema, - abilities: z.array(z.array(IDSchema)).default([]), -}); - -export const SpeciesSchema = PlaybillResourceSchema.extend({ - $define: z.literal("species"), - name: NameSchema.default("Unnamed Species"), - abilities: z.array(IDSchema).default([]), - bonuses: AbilityEffectSchema.default({}), -}); - -export const RuleSchema = PlaybillResourceSchema.extend({ - $define: z.literal("rule"), - overrule: z.string().optional(), -}); - -// Full Playbill Schema -export const PlaybillSchema = PlaybillResourceSchema.extend({ - $define: z.literal("playbill"), - version: z.string().default("0"), - name: NameSchema.default("Unnamed Playbill"), - author: z.string().default("Anonymous"), - - abilities: z.array(AbilitySchema).default([]), - entities: z.array(EntitySchema).default([]), - items: z.array(ItemSchema).default([]), - tags: z.array(ItemTagSchema).default([]), - lore: z.array(LoreSchema).default([]), - methods: z.array(MethodSchema).default([]), - species: z.array(SpeciesSchema).default([]), - rules: z.array(RuleSchema).default([]), -}); - -export const ValidatedPlaybillSchema = PlaybillSchema.superRefine( - (val, ctx) => { - // For each ability, ensure unique IDs - const abilityIds = new Set(); - for (const ability of val.abilities) { - if (abilityIds.has(ability.id)) { - ctx.addIssue({ - message: `Ability ${ability.id} has a duplicate ID`, - code: z.ZodIssueCode.custom, - }); - } - abilityIds.add(ability.id); - } - - // For each method, ensure its abilities exist - // Also ensure unique IDs - const methodIds = new Set(); - for (const method of val.methods) { - if (methodIds.has(method.id)) { - ctx.addIssue({ - message: `Method ${method.id} has a duplicate ID`, - code: z.ZodIssueCode.custom, - }); - } - - methodIds.add(method.id); - const methodAbilityIds = method.abilities.flat(); - - for (const abilityId of methodAbilityIds) { - if (!val.abilities.some((a) => a.id === abilityId)) { - ctx.addIssue({ - message: `Method ${method.id} references undefined ability ${abilityId}`, - code: z.ZodIssueCode.custom, - }); - } - } - } - - // For each species, ensure its abilities and IDs - const speciesIds = new Set(); - for (const species of val.species) { - if (speciesIds.has(species.id)) { - ctx.addIssue({ - message: `Species ${species.id} has a duplicate ID`, - code: z.ZodIssueCode.custom, - }); - } - - speciesIds.add(species.id); - - const speciesAbilityIds = species.abilities.flat(); - - for (const abilityId of speciesAbilityIds) { - if (!val.abilities.some((a) => a.id === abilityId)) { - ctx.addIssue({ - message: `Species ${species.id} references undefined ability ${abilityId}`, - code: z.ZodIssueCode.custom, - }); - } - } - } - - // For each entity, ensure its species and known abilities exist - const entityIds = new Set(); - for (const entity of val.entities) { - if (entityIds.has(entity.id)) { - ctx.addIssue({ - message: `Entity ${entity.id} has a duplicate ID`, - code: z.ZodIssueCode.custom, - }); - } - entityIds.add(entity.id); - - for (const abilityId of entity.abilities) { - if (!val.abilities.some((a) => a.id === abilityId)) { - ctx.addIssue({ - message: `Entity ${entity.id} references undefined ability ${abilityId}`, - code: z.ZodIssueCode.custom, - }); - } - } - - if (!val.species.some((s) => s.id === entity.species)) { - ctx.addIssue({ - message: `Entity ${entity.id} references undefined species ${entity.species}`, - code: z.ZodIssueCode.custom, - }); - } - } - - // For each item, ensure its tags exist - const itemIds = new Set(); - for (const item of val.items) { - if (itemIds.has(item.id)) { - ctx.addIssue({ - message: `Item ${item.id} has a duplicate ID`, - code: z.ZodIssueCode.custom, - }); - } - itemIds.add(item.id); - for (const tag of item.tags) { - if (!val.tags.some((t) => t.id === tag)) { - ctx.addIssue({ - message: `Item ${item.id} references undefined tag ${tag}`, - code: z.ZodIssueCode.custom, - }); - } - } - } - - // For each tag, ensure unique IDs - const tagIds = new Set(); - for (const tag of val.tags) { - if (tagIds.has(tag.id)) { - ctx.addIssue({ - message: `Tag ${tag.id} has a duplicate ID`, - code: z.ZodIssueCode.custom, - }); - } - tagIds.add(tag.id); - } - - // For each rule, ensure unique IDs - const ruleIds = new Set(); - for (const rule of val.rules) { - if (ruleIds.has(rule.id)) { - ctx.addIssue({ - message: `Rule ${rule.id} has a duplicate ID`, - code: z.ZodIssueCode.custom, - }); - } - ruleIds.add(rule.id); - } - - // For each lore, ensure unique IDs - const loreIds = new Set(); - for (const lore of val.lore) { - if (loreIds.has(lore.id)) { - ctx.addIssue({ - message: `Lore ${lore.id} has a duplicate ID`, - code: z.ZodIssueCode.custom, - }); - } - loreIds.add(lore.id); - } - }, -).brand("ValidatedPlaybill"); - -export type Playbill = z.infer; -export type ValidatedPlaybill = z.infer; - -export const ResourceMap = { - ability: AbilitySchema, - species: SpeciesSchema, - entity: EntitySchema, - item: ItemSchema, - tag: ItemTagSchema, - method: MethodSchema, - lore: LoreSchema, - rule: RuleSchema, - playbill: PlaybillSchema, -} as const; - -export const AnyResourceSchema = z.discriminatedUnion("$define", [ - AbilitySchema, - SpeciesSchema, - EntitySchema, - ItemSchema, - ItemTagSchema, - MethodSchema, - LoreSchema, - RuleSchema, - PlaybillSchema, -]); - -export type AnyResource = z.infer; - -export function getEmptyPlaybill(): Playbill { - return PlaybillSchema.parse({ - $define: "playbill", - id: "empty", - }); -} diff --git a/src/resource.ts b/src/resource.ts index a33a730..aa90a72 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -1,9 +1,9 @@ -import YAML from "yaml"; import { type AnyResource, AnyResourceSchema, ResourceMap, -} from "./playbill-schema"; +} from "@proscenium/playbill"; +import YAML from "yaml"; import { extractFrontmatter, extractMarkdown } from "./markdown"; type FileFormat = "yaml" | "markdown"; diff --git a/src/usage.ts b/src/usage.ts index dd339ae..a9a5a1a 100644 --- a/src/usage.ts +++ b/src/usage.ts @@ -1,5 +1,5 @@ -import { version } from "../package.json" with { type: "json" }; import chalk from "chalk"; +import { version } from "../package.json" with { type: "json" }; export const usage = ` ${chalk.bold("muse")} - Compile and validate Playbills for Proscenium