diff --git a/src/cli/completions/declare-parser.ts b/src/cli/completions/declare-parser.ts index e836add..e718fef 100644 --- a/src/cli/completions/declare-parser.ts +++ b/src/cli/completions/declare-parser.ts @@ -1,21 +1,28 @@ /** - * Declare parser — parses ```csv role=declare code blocks from markdown. + * Declare parser — parses ```yaml role=declare code blocks from markdown. * - * Shared between CLI completions scanner and browser-side completions. + * Shared between the CLI scanner/validator and the browser-side journal. * - * Format (columns can be in any order; `tag` and `threshold` are optional): - * tag,threshold,key,expr - * ,,$hp,$con*5+$mod_hp ← variable declaration - * ,,$ac,10+$dex ← variable declaration - * #warrior,2,$mod_hp,20 ← tag modifier (threshold 2) - * #warrior,,$mod_str,1 ← tag modifier (threshold defaults to 1) + * The body is YAML with two optional maps (`declareSchema` below): * - * When `tag` is empty: $key is a reactively computed variable. - * When `tag` is present: when #tag met (source's tagmap value >= threshold), - * $key gets expr added to its base. + * vars: + * $hp: $con*5 + $mod_hp + * $ac: 10 + $dex + * tags: + * warrior: + * 1: + * $mod_hp: 20 + * 2: + * $mod_str: 1 + * + * `vars` entries are reactively computed variables. `tags` entries are + * modifiers: when a source variable's tag map holds `#warrior` at a count + * >= the threshold key, each `$target` gains the modifier value. Tag names + * may be written with or without the leading `#`. */ -import { parse } from "csv-parse/browser/esm/sync"; +import yaml from "js-yaml"; +import { z } from "zod"; // --------------------------------------------------------------------------- // Types @@ -38,65 +45,134 @@ export interface DeclareResult { tagModifiers: TagModifier[]; } +// --------------------------------------------------------------------------- +// Schema +// --------------------------------------------------------------------------- + +/** A `$name` variable key. */ +const VAR_NAME = /^\$[A-Za-z_][A-Za-z0-9_]*$/; +/** A tag name, optionally carrying its leading `#`. */ +const TAG_NAME = /^#?[A-Za-z_][A-Za-z0-9_-]*$/; +/** Threshold map keys are positive integers (1, 2, 3, …). */ +const THRESHOLD = /^[1-9][0-9]*$/; + /** - * Parse a single ```csv role=declare block body. + * Expression text. YAML resolves bare numbers to `number`, so both strings and + * numbers are accepted and normalized to a trimmed string. A missing value + * loads as `null` and becomes empty text, which the refine rejects. Anything + * else (boolean, date) is a mistake in the source. Unknown references inside + * the expression are resolved by the reactivity engine, not here. */ -export function parseDeclareCsv(csv: string, source: string): DeclareResult { - const trimmed = csv.trim(); +const exprSchema = z + .union([z.string(), z.number(), z.null()]) + .transform((value) => (value === null ? "" : String(value).trim())) + .refine((value) => value.length > 0, "expression must not be empty"); + +/** A map whose keys must look like `$name`. */ +function varMap(message: string) { + return z.record(z.string(), exprSchema).superRefine((obj, ctx) => { + for (const key of Object.keys(obj)) { + if (!VAR_NAME.test(key)) { + ctx.addIssue({ code: "custom", message, path: [key] }); + } + } + }); +} + +/** `threshold -> { $target: expression }`. */ +const thresholdMap = z + .record( + z.string(), + varMap("tag modifier targets must start with $, e.g. `$mod_hp`"), + ) + .superRefine((obj, ctx) => { + for (const key of Object.keys(obj)) { + if (!THRESHOLD.test(key)) { + ctx.addIssue({ + code: "custom", + message: "threshold must be a positive integer", + path: [key], + }); + } + } + }); + +/** The accepted `role=declare` shape. Exported for tests and reuse. */ +export const declareSchema = z.strictObject({ + vars: varMap("variable names must start with $, e.g. `$hp`").optional(), + tags: z + .record(z.string(), thresholdMap) + .superRefine((obj, ctx) => { + for (const key of Object.keys(obj)) { + if (!TAG_NAME.test(key)) { + ctx.addIssue({ + code: "custom", + message: "tag names may not contain spaces or punctuation", + path: [key], + }); + } + } + }) + .optional(), +}); + +// --------------------------------------------------------------------------- +// Parsing +// --------------------------------------------------------------------------- + +/** + * Parse a single ```yaml role=declare block body. + * + * Throws with a `path: message` summary when the body is not valid YAML or + * does not match the schema. Callers attach the file/line context. + */ +export function parseDeclare(body: string): DeclareResult { + const trimmed = body.trim(); if (!trimmed) return { variables: [], tagModifiers: [] }; - // Validate that required columns exist (order-agnostic, tag is optional) - const firstLine = trimmed.split(/\r?\n/)[0]; - const headers = firstLine.split(",").map((h) => h.trim().toLowerCase()); - if (!headers.includes("key") || !headers.includes("expr")) { + let data: unknown; + try { + data = yaml.load(trimmed); + } catch (error) { throw new Error( - `${source}: role=declare blocks must have "key" and "expr" columns. Got: ${headers.join(",")}`, + `role=declare body is not valid YAML: ${errorText(error)}`, ); } - const records = parse(trimmed, { - columns: true, - trim: true, - skipEmptyLines: true, - }) as Array<{ tag?: string; threshold?: string; key: string; expr: string }>; + const parsed = declareSchema.safeParse(data ?? {}); + if (!parsed.success) { + throw new Error(`role=declare: ${formatIssues(parsed.error)}`); + } const variables: VarDeclaration[] = []; + for (const [key, expression] of Object.entries(parsed.data.vars ?? {})) { + variables.push({ key, expression }); + } + const tagModifiers: TagModifier[] = []; - - for (const row of records) { - const tag = row.tag ?? ""; - const key = row.key ?? ""; - const expr = row.expr ?? ""; - - if (!key || !expr) { - console.warn(`${source}: skipping row with empty key or expr`); - continue; - } - - if (tag) { - // Tag modifier - if (!tag.startsWith("#")) { - throw new Error(`${source}: tag must start with #, got "${tag}"`); + for (const [name, thresholds] of Object.entries(parsed.data.tags ?? {})) { + const tag = name.startsWith("#") ? name : `#${name}`; + for (const [rawThreshold, targets] of Object.entries(thresholds)) { + const threshold = Number(rawThreshold); + for (const [target, expression] of Object.entries(targets)) { + tagModifiers.push({ tag, target, expression, threshold }); } - if (!key.startsWith("$")) { - throw new Error(`${source}: key must start with $, got "${key}"`); - } - const thresholdRaw = row.threshold?.trim() ?? ""; - const threshold = thresholdRaw ? parseInt(thresholdRaw, 10) : 1; - if (isNaN(threshold) || threshold < 1) { - throw new Error( - `${source}: threshold must be a positive integer, got "${row.threshold}"`, - ); - } - tagModifiers.push({ tag, target: key, expression: expr, threshold }); - } else { - // Variable declaration - if (!key.startsWith("$")) { - throw new Error(`${source}: key must start with $, got "${key}"`); - } - variables.push({ key, expression: expr }); } } return { variables, tagModifiers }; } + +function formatIssues(error: z.ZodError): string { + return error.issues + .map((issue) => + issue.path.length + ? `${issue.path.join(".")}: ${issue.message}` + : issue.message, + ) + .join("; "); +} + +function errorText(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/src/cli/completions/variable-system.test.ts b/src/cli/completions/variable-system.test.ts index ee0b3ff..9e73dc3 100644 --- a/src/cli/completions/variable-system.test.ts +++ b/src/cli/completions/variable-system.test.ts @@ -1,6 +1,6 @@ /** * Tests for the variable/tag system: - * - declare-parser (CSV parsing) + * - declare-parser (YAML parsing) * - block-scanner (attribute parsing) * - variable-expression (expression evaluation) * - var-reactivity (dependency graph, cascade, tag activation) @@ -27,7 +27,7 @@ jest.mock("github-slugger", () => { // Imports // --------------------------------------------------------------------------- -import { parseDeclareCsv } from "./declare-parser"; +import { parseDeclare } from "./declare-parser"; import { parseBlockAttrs } from "./block-scanner"; import { evaluateExpression, exprValueToString } from "../../components/journal/variable-expression"; import { @@ -63,154 +63,154 @@ function store(entries: Record = {}): Record { // declare-parser // --------------------------------------------------------------------------- -describe("parseDeclareCsv", () => { +describe("parseDeclare", () => { test("parses variable declarations", () => { - const csv = `tag,key,expr -,$hp,$con*5+$mod_hp -,$ac,10+$dex`; - const result = parseDeclareCsv(csv, "test.md"); - expect(result.variables).toHaveLength(2); - expect(result.variables[0]).toEqual({ - key: "$hp", - expression: "$con*5+$mod_hp", - }); - expect(result.variables[1]).toEqual({ key: "$ac", expression: "10+$dex" }); + const result = parseDeclare(` +vars: + $hp: $con*5 + $mod_hp + $ac: 10 + $dex +`); + expect(result.variables).toEqual([ + { key: "$hp", expression: "$con*5 + $mod_hp" }, + { key: "$ac", expression: "10 + $dex" }, + ]); expect(result.tagModifiers).toHaveLength(0); }); - test("parses tag modifiers", () => { - const csv = `tag,key,expr -#warrior,$mod_hp,20 -#mage,$mod_mp,15`; - const result = parseDeclareCsv(csv, "test.md"); + test("parses tag modifiers with thresholds", () => { + const result = parseDeclare(` +tags: + warrior: + 2: + $mod_hp: 20 + mage: + 1: + $mod_mp: 15 +`); expect(result.variables).toHaveLength(0); - expect(result.tagModifiers).toHaveLength(2); + expect(result.tagModifiers).toEqual([ + { tag: "#warrior", target: "$mod_hp", expression: "20", threshold: 2 }, + { tag: "#mage", target: "$mod_mp", expression: "15", threshold: 1 }, + ]); + }); + + test("multiple thresholds for one tag stack in ascending order", () => { + const result = parseDeclare(` +tags: + warrior: + 3: + $mod_hp: 20 + 1: + $mod_hp: 10 +`); + expect(result.tagModifiers).toEqual([ + { tag: "#warrior", target: "$mod_hp", expression: "10", threshold: 1 }, + { tag: "#warrior", target: "$mod_hp", expression: "20", threshold: 3 }, + ]); + }); + + test("one threshold can carry multiple targets", () => { + const result = parseDeclare(` +tags: + warrior: + 1: + $mod_hp: 20 + $mod_str: 1 +`); + expect(result.tagModifiers).toEqual([ + { tag: "#warrior", target: "$mod_hp", expression: "20", threshold: 1 }, + { tag: "#warrior", target: "$mod_str", expression: "1", threshold: 1 }, + ]); + }); + + test("accepts a tag name written with #", () => { + const result = parseDeclare('tags:\n "#warrior":\n 1:\n $mod_hp: 20\n'); expect(result.tagModifiers[0]).toEqual({ tag: "#warrior", target: "$mod_hp", expression: "20", threshold: 1, }); - expect(result.tagModifiers[1]).toEqual({ - tag: "#mage", - target: "$mod_mp", - expression: "15", - threshold: 1, - }); }); - test("parses tag modifiers with threshold", () => { - const csv = `tag,threshold,key,expr -#warrior,2,$mod_hp,20 -#mage,,$mod_mp,15`; - const result = parseDeclareCsv(csv, "test.md"); - expect(result.tagModifiers).toHaveLength(2); - expect(result.tagModifiers[0]).toEqual({ - tag: "#warrior", - target: "$mod_hp", - expression: "20", - threshold: 2, - }); - expect(result.tagModifiers[1]).toEqual({ - tag: "#mage", - target: "$mod_mp", - expression: "15", - threshold: 1, - }); + test("normalizes numeric values to expression text", () => { + const result = parseDeclare("vars:\n $con: 10\n $dex: 12\n"); + expect(result.variables).toEqual([ + { key: "$con", expression: "10" }, + { key: "$dex", expression: "12" }, + ]); }); - test("rejects invalid threshold", () => { - const csv = `tag,threshold,key,expr -#warrior,0,$mod_hp,20`; - expect(() => parseDeclareCsv(csv, "test.md")).toThrow( - "threshold must be a positive integer", - ); + test("YAML numeric forms are resolved before stringification", () => { + const result = parseDeclare("vars:\n $a: 0x1F\n $b: 1e3\n"); + expect(result.variables).toEqual([ + { key: "$a", expression: "31" }, + { key: "$b", expression: "1000" }, + ]); }); test("parses mixed declarations and tag modifiers", () => { - const csv = `tag,key,expr -,$hp,$con*5 -#warrior,$mod_hp,20 -,$ac,10+$dex`; - const result = parseDeclareCsv(csv, "test.md"); + const result = parseDeclare(` +vars: + $hp: $con*5 + $ac: 10+$dex +tags: + warrior: + 1: + $mod_hp: 20 +`); expect(result.variables).toHaveLength(2); expect(result.tagModifiers).toHaveLength(1); }); - test("rejects missing key column", () => { - const csv = `tag,expr -,$con*5`; - expect(() => parseDeclareCsv(csv, "test.md")).toThrow( - 'role=declare blocks must have "key" and "expr" columns', + test("handles empty, whitespace, and comment-only bodies", () => { + for (const body of ["", " \n ", "# just a comment\n"]) { + expect(parseDeclare(body)).toEqual({ variables: [], tagModifiers: [] }); + } + }); + + test("rejects invalid YAML", () => { + expect(() => parseDeclare("vars: [unclosed\n")).toThrow("not valid YAML"); + }); + + test("rejects duplicate keys", () => { + expect(() => parseDeclare("vars:\n $hp: 1\n $hp: 2\n")).toThrow( + "duplicated mapping key", ); }); - test("rejects missing expr column", () => { - const csv = `tag,key -,$hp`; - expect(() => parseDeclareCsv(csv, "test.md")).toThrow( - 'role=declare blocks must have "key" and "expr" columns', + test("rejects unknown top-level keys", () => { + expect(() => parseDeclare("variable:\n $hp: 1\n")).toThrow( + "Unrecognized key", ); }); - test("rejects tag without # prefix", () => { - const csv = `tag,key,expr -warrior,$mod_hp,20`; - expect(() => parseDeclareCsv(csv, "test.md")).toThrow( - "tag must start with #", + test("rejects a variable name without $", () => { + expect(() => parseDeclare("vars:\n hp: 1\n")).toThrow( + "variable names must start with $", ); }); - test("rejects key without $ prefix in declaration", () => { - const csv = `tag,key,expr -,hp,$con*5`; - expect(() => parseDeclareCsv(csv, "test.md")).toThrow( - "key must start with $", + test("rejects a tag modifier target without $", () => { + expect(() => + parseDeclare("tags:\n warrior:\n 1:\n mod_hp: 20\n"), + ).toThrow("tag modifier targets must start with $"); + }); + + test("rejects a threshold that is not a positive integer", () => { + expect(() => + parseDeclare("tags:\n warrior:\n 0:\n $mod_hp: 20\n"), + ).toThrow("threshold must be a positive integer"); + }); + + test("rejects an empty expression", () => { + expect(() => parseDeclare("vars:\n $hp:\n")).toThrow( + "expression must not be empty", ); }); - test("rejects key without $ prefix in tag modifier", () => { - const csv = `tag,key,expr -#warrior,mod_hp,20`; - expect(() => parseDeclareCsv(csv, "test.md")).toThrow( - "key must start with $", - ); - }); - - test("skips rows with empty key or expr (warns, doesn't throw)", () => { - const csv = `tag,key,expr -,$hp,$con*5 -,,`; - const result = parseDeclareCsv(csv, "test.md"); - expect(result.variables).toHaveLength(1); - }); - - test("handles empty CSV", () => { - const result = parseDeclareCsv("", "test.md"); - expect(result.variables).toHaveLength(0); - expect(result.tagModifiers).toHaveLength(0); - }); - - test("handles whitespace-only CSV", () => { - const result = parseDeclareCsv(" \n ", "test.md"); - expect(result.variables).toHaveLength(0); - expect(result.tagModifiers).toHaveLength(0); - }); - - test("columns can be in any order", () => { - const csv = `expr,key,tag -$con*5,$hp, -20,$mod_hp,#warrior`; - const result = parseDeclareCsv(csv, "test.md"); - expect(result.variables).toHaveLength(1); - expect(result.variables[0]).toEqual({ key: "$hp", expression: "$con*5" }); - expect(result.tagModifiers).toHaveLength(1); - expect(result.tagModifiers[0]).toEqual({ - tag: "#warrior", - target: "$mod_hp", - expression: "20", - threshold: 1, - }); + test("rejects non-numeric, non-string values", () => { + expect(() => parseDeclare("vars:\n $hp: true\n")).toThrow(); }); }); @@ -229,13 +229,13 @@ describe("parseBlockAttrs", () => { }); test("parses quoted values", () => { - const attrs = parseBlockAttrs('csv id="my stats" role=declare'); + const attrs = parseBlockAttrs('yaml id="my stats" role=declare'); expect(attrs.id).toBe("my stats"); expect(attrs.role).toBe("declare"); }); test("collects unknown attributes in extra", () => { - const attrs = parseBlockAttrs("csv role=declare foo=bar baz=42"); + const attrs = parseBlockAttrs("yaml role=declare foo=bar baz=42"); expect(attrs.extra).toEqual({ foo: "bar", baz: "42" }); }); diff --git a/src/cli/content-registry.ts b/src/cli/content-registry.ts index 9cf2b56..9e6651e 100644 --- a/src/cli/content-registry.ts +++ b/src/cli/content-registry.ts @@ -17,7 +17,7 @@ import Slugger from "github-slugger"; import { - parseDeclareCsv, + parseDeclare, type VarDeclaration, type TagModifier, } from "./completions/declare-parser.js"; @@ -471,7 +471,7 @@ export function deriveBlocks(registry: ContentRegistry): { for (const entry of Object.values(store)) { if (entry.kind !== "declare") continue; try { - const result = parseDeclareCsv(entry.body, docPath); + const result = parseDeclare(entry.body); declarations.push(...result.variables); tagModifiers.push(...result.tagModifiers); } catch (e) { diff --git a/src/cli/validate/checks/roles.ts b/src/cli/validate/checks/roles.ts index 3664858..dc52707 100644 --- a/src/cli/validate/checks/roles.ts +++ b/src/cli/validate/checks/roles.ts @@ -2,8 +2,7 @@ * Code-block role checks. */ -import { parse } from "csv-parse/browser/esm/sync"; -import { parseDeclareCsv } from "../../completions/declare-parser.js"; +import { parseDeclare, type DeclareResult } from "../../completions/declare-parser.js"; import { diagnosticAt } from "../diagnostics.js"; import { ELEMENT_NAMES, KNOWN_ROLES } from "../known.js"; import type { RawFence } from "../scan.js"; @@ -60,8 +59,25 @@ function checkSparkTable(fence: RawFence, ctx: CheckContext): void { function checkDeclare(fence: RawFence, ctx: CheckContext): void { const { source, file, sink } = ctx; + + if (/^csv$/i.test(fence.lang)) { + sink.report( + diagnosticAt( + file, + source, + fence.offset, + "TTTK-R003", + "error", + "role=declare blocks are YAML, not CSV", + "use ```yaml role=declare with `vars:` and `tags:` maps", + ), + ); + return; + } + + let result: DeclareResult; try { - parseDeclareCsv(fence.body, file); + result = parseDeclare(fence.body); } catch (error) { sink.report( diagnosticAt( @@ -71,51 +87,22 @@ function checkDeclare(fence: RawFence, ctx: CheckContext): void { "TTTK-R003", "error", error instanceof Error ? error.message : String(error), - "role=declare needs `key` and `expr` columns; `tag` and `threshold` are optional", + "role=declare is YAML: `vars:` maps `$name` to an expression; `tags:` maps a tag name to threshold maps", ), ); return; } - checkDeclareRows(fence, ctx); -} - -/** - * R004 — `parseDeclareCsv` silently skips rows with an empty `key` or `expr`. - * Re-parse with line info so the skipped row can be localized. - */ -function checkDeclareRows(fence: RawFence, ctx: CheckContext): void { - const { source, file, sink } = ctx; - let records: Array<{ record: Record; info: { lines: number } }>; - try { - records = parse(fence.body, { - columns: true, - trim: true, - skipEmptyLines: true, - info: true, - }) as unknown as typeof records; - } catch { - return; // R003 already reported the parse failure - } - if (records.length === 0) return; - - const headers = Object.keys(records[0].record).map((h) => h.toLowerCase()); - if (!headers.includes("key") || !headers.includes("expr")) return; - - const baseLine = source.position(fence.bodyOffset).line; - for (const { record, info } of records) { - const key = record.key ?? ""; - const expr = record.expr ?? ""; - if (key && expr) continue; + if (result.variables.length === 0 && result.tagModifiers.length === 0) { sink.report( diagnosticAt( file, source, - source.lineStart(baseLine + info.lines - 1), + fence.offset, "TTTK-R004", "warning", - `declare row skipped: ${!key ? "empty key" : "empty expr"}`, - "every declare row needs both a key and an expression", + "role=declare block declares nothing", + "add a `vars:` or `tags:` mapping; the block has no effect", ), ); } diff --git a/src/cli/validate/known.ts b/src/cli/validate/known.ts index 5b961f6..96b0b0e 100644 --- a/src/cli/validate/known.ts +++ b/src/cli/validate/known.ts @@ -232,12 +232,12 @@ export const ROLE_SPECS: readonly RoleSpec[] = [ }, { role: "declare", - info: "csv role=declare", + info: "yaml role=declare", summary: "Reactive variable / tag-modifier definitions.", details: - "Columns (any order): `tag`, `threshold`, `key`, `expr`. A row with an empty `tag` declares a variable; a row with a `#tag` applies a modifier when that tag is active.", + "YAML with two optional maps. `vars:` maps a `$name` to its expression. `tags:` maps a tag name to a map of thresholds to `$target` modifiers: at that tag count the target gains the value, so sibling thresholds stack. Tag names may be written with or without the leading `#`. YAML resolves bare numbers to numbers and everything else to strings; both become expression text.", examples: [ - "```csv role=declare\ntag,threshold,key,expr\n,,$hp,$con*5+$mod_hp\n#warrior,2,$mod_hp,20\n```", + "```yaml role=declare\nvars:\n $hp: $con*5 + $mod_hp\n $ac: 10 + $dex\ntags:\n warrior:\n 1:\n $mod_hp: 20\n 2:\n $mod_str: 1\n```", ], }, { diff --git a/src/cli/validate/validate.test.ts b/src/cli/validate/validate.test.ts index 93323c1..6446bd9 100644 --- a/src/cli/validate/validate.test.ts +++ b/src/cli/validate/validate.test.ts @@ -65,14 +65,26 @@ describe("runCheck", () => { expect(report.diagnostics[0].line).toBe(1); }); - test("R003 flags a declare block missing key/expr columns", () => { + test("R003 flags a declare block with an invalid shape", () => { const dir = fixture({ - "doc.md": "```csv role=declare\nfoo,bar\n1,2\n```\n", + "doc.md": "```yaml role=declare\nvars:\n hp: 1\n```\n", }); expect(codes(runCheck(dir))).toContain("TTTK-R003"); }); + test("R003 flags a CSV role=declare block with a migration hint", () => { + const dir = fixture({ + "doc.md": "```csv role=declare\ntag,key,expr\n,,$hp,1\n```\n", + }); + + const report = runCheck(dir); + const diagnostic = report.diagnostics.find((d) => d.code === "TTTK-R003"); + + expect(diagnostic).toBeDefined(); + expect(diagnostic!.message).toContain("YAML, not CSV"); + }); + test("R005 flags invalid YAML in a role=tag block", () => { const dir = fixture({ "doc.md": "```yaml role=tag\ntag: [unclosed\n```\n", @@ -168,16 +180,16 @@ describe("runCheck", () => { expect(codes(runCheck(dir))).not.toContain("TTTK-D002"); }); - test("R004 flags a declare row with an empty key or expr", () => { + test("R004 flags a declare block that declares nothing", () => { const dir = fixture({ - "doc.md": "```csv role=declare\nkey,expr\n$hp,1\n,2\n```\n", + "doc.md": "```yaml role=declare\n# nothing here\n```\n", }); const report = runCheck(dir); const diagnostic = report.diagnostics.find((d) => d.code === "TTTK-R004"); expect(diagnostic).toBeDefined(); - expect(diagnostic!.line).toBe(4); + expect(diagnostic!.line).toBe(1); }); test("V001 flags a CSV that fails to parse", () => { diff --git a/src/components/journal/declare-parser.ts b/src/components/journal/declare-parser.ts index bec508f..57dd6b8 100644 --- a/src/components/journal/declare-parser.ts +++ b/src/components/journal/declare-parser.ts @@ -3,7 +3,8 @@ */ export { - parseDeclareCsv, + parseDeclare, + declareSchema, type VarDeclaration, type TagModifier, type DeclareResult, diff --git a/src/components/journal/index.ts b/src/components/journal/index.ts index 76ef653..836445b 100644 --- a/src/components/journal/index.ts +++ b/src/components/journal/index.ts @@ -56,7 +56,7 @@ export type { ParsedInput, CompletionItem } from "./command-parser"; export { buildCompletions } from "./command-completions"; export type { CompletionsContext } from "./command-completions"; export { VariableView } from "./VariableView"; -export { parseDeclareCsv } from "./declare-parser"; +export { parseDeclare, declareSchema } from "./declare-parser"; export type { VarDeclaration, TagModifier } from "./declare-parser"; export { evaluateExpression, expressionIsTag, exprValueToString } from "./variable-expression"; export type { EvalContext, EvalResult, ExprValue } from "./variable-expression";