ttrpg-tools/src/cli/inline-blocks.ts

69 lines
1.9 KiB
TypeScript
Raw Normal View History

import { posix } from "path";
/**
* Regex to match a fenced code block with a `file="..."` attribute, e.g.:
*
* ```csv file="card-data.csv"
* name, cost, effect
* Fireball, 1, Blast em
* ```
*
* ```yaml file="monsters.yaml"
* goblin:
* hp: 7
* ```
*
* The language identifier is optional only `file="..."` matters.
*
* Group 1: the info string after the opening fence (e.g. `csv file="card-data.csv"`)
* Group 2: the code block body
*/
const INLINE_FILE_BLOCK_RE = /^```\w*\s+file="([^"]+)"(?:\s+\w+="[^"]*")*\s*\n([\s\S]*?)```\s*$/gm;
/**
* Process fenced code blocks that contain a `file="..."` attribute.
*
* For each block like:
* ```lang file="card-data.csv"
* content
* ```
*
* 1. Strips the block from the returned text (so it doesn't render)
* 2. Injects a synthetic entry into `index` at the resolved file path
*
* @param content - Raw file content
* @param fileRelativePath - Normalized path of the containing file (e.g. "/rules/combat.md")
* @param index - The content index to inject synthetic entries into
* @returns Content with inline blocks removed
*/
export function extractInlineBlocks(
content: string,
fileRelativePath: string,
index: Record<string, string>,
): string {
const matches: { filename: string; body: string }[] = [];
let match: RegExpExecArray | null;
INLINE_FILE_BLOCK_RE.lastIndex = 0;
while ((match = INLINE_FILE_BLOCK_RE.exec(content)) !== null) {
matches.push({ filename: match[1], body: match[2] });
}
if (matches.length === 0) return content;
const stripped = content.replace(INLINE_FILE_BLOCK_RE, "");
for (const { filename, body } of matches) {
const fileDir = posix.dirname(fileRelativePath);
const resolvedPath = posix.join(fileDir, filename);
index[resolvedPath] = body;
console.log(
`[inline] Extracted ${resolvedPath} from ${fileRelativePath}`,
);
}
return stripped;
}