refactor: improve spark table lookup and error reporting
Replace the heuristic-based slug parsing with a more robust combined-slug lookup to correctly handle page names containing hyphens. Added `scanSparkTables` to provide better error messages when a table is not found.
This commit is contained in:
parent
9a312f76ad
commit
e801ac9b5f
|
|
@ -16,7 +16,11 @@ import { z } from "zod";
|
||||||
import { For } from "solid-js";
|
import { For } from "solid-js";
|
||||||
import { registerMessageType } from "../registry";
|
import { registerMessageType } from "../registry";
|
||||||
import { rollFormula } from "../../md-commander/hooks";
|
import { rollFormula } from "../../md-commander/hooks";
|
||||||
import { findSparkTable, rollSparkTable } from "../../utils/spark-table";
|
import {
|
||||||
|
findSparkTableByCombinedSlug,
|
||||||
|
rollSparkTable,
|
||||||
|
scanSparkTables,
|
||||||
|
} from "../../utils/spark-table";
|
||||||
import { getIndexedData } from "../../../data-loader/file-index";
|
import { getIndexedData } from "../../../data-loader/file-index";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -72,28 +76,28 @@ export async function resolveSparkPayload(raw: {
|
||||||
key: string;
|
key: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
}): Promise<SparkPayload> {
|
}): Promise<SparkPayload> {
|
||||||
// key is "pageName-columnSlug". The page name is the last segment of
|
const mdPath = `/${raw.filePath.replace(/^\//, "")}.md`;
|
||||||
// filePath (e.g. "01-dry-dock" from "/ayi-games/.../01-dry-dock").
|
const pageName =
|
||||||
// Strip it from the key to recover the column slug.
|
raw.filePath.replace(/^\//, "").split("/").filter(Boolean).pop() ||
|
||||||
const pathParts = raw.filePath.replace(/^\//, "").split("/");
|
raw.filePath;
|
||||||
const pageName = pathParts[pathParts.length - 1] || raw.filePath;
|
|
||||||
const columnSlug =
|
|
||||||
raw.key.length > pageName.length + 1 && raw.key.startsWith(pageName + "-")
|
|
||||||
? raw.key.slice(pageName.length + 1)
|
|
||||||
: raw.key;
|
|
||||||
|
|
||||||
const filePath = `/${raw.filePath.replace(/^\//, "")}`;
|
|
||||||
|
|
||||||
let content: string;
|
let content: string;
|
||||||
try {
|
try {
|
||||||
content = await getIndexedData(filePath);
|
content = await getIndexedData(mdPath);
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(`Failed to load file: "${filePath}"`);
|
throw new Error(`Failed to load file: "${mdPath}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = findSparkTable(content, columnSlug);
|
const meta = findSparkTableByCombinedSlug(content, raw.key, pageName);
|
||||||
if (!meta) {
|
if (!meta) {
|
||||||
throw new Error(`Spark table "${columnSlug}" not found in "${filePath}"`);
|
const available = scanSparkTables(content);
|
||||||
|
const names =
|
||||||
|
available.length > 0
|
||||||
|
? available.map((t) => `${pageName}-${t.slug}`).join(", ")
|
||||||
|
: "(none)";
|
||||||
|
throw new Error(
|
||||||
|
`Spark table "${raw.key}" not found in "${mdPath}". Available: ${names}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sparkResult = rollSparkTable(meta);
|
const sparkResult = rollSparkTable(meta);
|
||||||
|
|
|
||||||
|
|
@ -148,18 +148,44 @@ export function findSparkTable(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a spark table by its combined slug (`pageName-columnSlug`).
|
||||||
|
* Iterates every spark table in the content, constructs the combined slug,
|
||||||
|
* and returns the first match. Avoids the need to guess where the page
|
||||||
|
* name ends and the column slug begins (page names may contain `-`).
|
||||||
|
*/
|
||||||
|
export function findSparkTableByCombinedSlug(
|
||||||
|
markdown: string,
|
||||||
|
combinedSlug: string,
|
||||||
|
pageName: string,
|
||||||
|
): SparkTableMeta | null {
|
||||||
|
const tables = parseMarkdownTables(markdown);
|
||||||
|
for (const table of tables) {
|
||||||
|
if (!isSparkTable(table)) continue;
|
||||||
|
const colSlug = sparkTableSlug(table);
|
||||||
|
const candidate = `${pageName}-${colSlug}`;
|
||||||
|
if (candidate === combinedSlug) {
|
||||||
|
return {
|
||||||
|
notation: table.headers[0],
|
||||||
|
slug: colSlug,
|
||||||
|
dataHeaders: table.headers.slice(1),
|
||||||
|
rows: table.rows,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/** Scan all spark tables in a markdown file and return their metadata */
|
/** Scan all spark tables in a markdown file and return their metadata */
|
||||||
export function scanSparkTables(
|
export function scanSparkTables(
|
||||||
markdown: string,
|
markdown: string,
|
||||||
): Omit<SparkTableMeta, "rows">[] {
|
): Omit<SparkTableMeta, "rows">[] {
|
||||||
const tables = parseMarkdownTables(markdown);
|
const tables = parseMarkdownTables(markdown);
|
||||||
return tables
|
return tables.filter(isSparkTable).map((table) => ({
|
||||||
.filter(isSparkTable)
|
notation: table.headers[0],
|
||||||
.map((table) => ({
|
slug: sparkTableSlug(table),
|
||||||
notation: table.headers[0],
|
dataHeaders: table.headers.slice(1),
|
||||||
slug: sparkTableSlug(table),
|
}));
|
||||||
dataHeaders: table.headers.slice(1),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -170,9 +196,7 @@ export function scanSparkTables(
|
||||||
* Roll a spark table: for each data column, roll the dice formula and look
|
* Roll a spark table: for each data column, roll the dice formula and look
|
||||||
* up the corresponding row value.
|
* up the corresponding row value.
|
||||||
*/
|
*/
|
||||||
export function rollSparkTable(
|
export function rollSparkTable(meta: SparkTableMeta): SparkTableResult {
|
||||||
meta: SparkTableMeta,
|
|
||||||
): SparkTableResult {
|
|
||||||
const slugger = new Slugger();
|
const slugger = new Slugger();
|
||||||
const columns: SparkTableColumn[] = [];
|
const columns: SparkTableColumn[] = [];
|
||||||
|
|
||||||
|
|
@ -183,11 +207,17 @@ export function rollSparkTable(
|
||||||
const roll = rollFormula(meta.notation);
|
const roll = rollFormula(meta.notation);
|
||||||
const rolledValue = roll.result.total;
|
const rolledValue = roll.result.total;
|
||||||
|
|
||||||
// Find the row matching the rolled value (1-based dice result)
|
// Find the row matching the rolled value — compare as integers,
|
||||||
|
// falling back to string comparison.
|
||||||
let value = `(no row for ${rolledValue})`;
|
let value = `(no row for ${rolledValue})`;
|
||||||
for (const row of meta.rows) {
|
for (const row of meta.rows) {
|
||||||
const diceCell = row[0] ?? "";
|
const diceCell = row[0] ?? "";
|
||||||
if (String(rolledValue) === diceCell.trim()) {
|
const cellNum = parseInt(diceCell.trim(), 10);
|
||||||
|
const match =
|
||||||
|
!isNaN(cellNum) && rolledValue === cellNum
|
||||||
|
? true
|
||||||
|
: String(rolledValue) === diceCell.trim();
|
||||||
|
if (match) {
|
||||||
value = row[colIdx + 1] ?? "";
|
value = row[colIdx + 1] ?? "";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue