youfoundjk_TeXcore/test_helpers/FileBuilder.ts

201 lines
4.8 KiB
TypeScript
Raw Permalink Normal View History

import { CachedMetadata, ListItemCache, Loc, Pos } from 'obsidian';
2026-06-04 16:43:52 +00:00
type ListItem = {
type: 'item';
text: string;
checkbox: 'x' | ' ' | undefined;
2026-06-04 16:43:52 +00:00
};
type ListBlock = {
type: 'list';
items: (ListItem | ListBlock)[];
2026-06-04 16:43:52 +00:00
};
type ListBlockItem = {
type: 'listitem';
text: string;
checkbox: ' ' | 'x' | undefined;
depth: number;
parent: number;
2026-06-04 16:43:52 +00:00
};
const makeListItems = (
list: ListBlock,
startingLine: number,
depth: number = 0
2026-06-04 16:43:52 +00:00
): ListBlockItem[] => {
if (startingLine === 0) {
startingLine = 1;
}
const lines: ListBlockItem[] = [];
for (const i of list.items) {
if (i.type === 'item') {
lines.push({
type: 'listitem',
text: i.text,
checkbox: i.checkbox,
depth,
parent: depth === 0 ? -startingLine : startingLine
});
} else {
lines.push(...makeListItems(i, startingLine + lines.length - 1, depth + 1));
2026-06-04 16:43:52 +00:00
}
}
return lines;
2026-06-04 16:43:52 +00:00
};
type FileBlock =
| {
type: 'heading';
text: string;
level: number;
2026-06-04 16:43:52 +00:00
}
| ListBlock
| { type: 'frontmatter'; key: string; text: unknown }
| { type: 'text'; text: string };
const makeFile = (lines: FileBlock[], tabChars = ' '.repeat(4)): [string, CachedMetadata] => {
let lineNum = 0;
let content = '';
const appendLine = (line: string): Pos => {
const start: Loc = { line: lineNum, col: 0, offset: content.length };
content += `${line}\n`;
const end: Loc = {
line: lineNum++,
col: line.length,
offset: content.length - 1
2026-06-04 16:43:52 +00:00
};
return { start, end };
};
const meta: CachedMetadata = {};
const frontmatter = lines.flatMap(l => (l.type === 'frontmatter' ? l : []));
if (frontmatter.length > 0) {
const data: Record<string, unknown> = {};
const { start } = appendLine('---');
for (const elt of frontmatter) {
if (Array.isArray(elt.text)) {
appendLine(`${elt.key}: [${(elt.text as unknown[]).join(',')}]`);
} else {
appendLine(`${elt.key}: ${String(elt.text)}`);
}
data[elt.key] = elt.text;
2026-06-04 16:43:52 +00:00
}
const { end } = appendLine('---');
meta.frontmatter = {
position: { start, end },
...data
};
}
const blocks = lines.flatMap(l => (l.type !== 'frontmatter' ? l : []));
for (const block of blocks) {
switch (block.type) {
case 'heading': {
if (!meta.headings) {
meta.headings = [];
}
const position = appendLine('#'.repeat(block.level) + ' ' + block.text);
meta.headings.push({
position,
heading: block.text,
level: block.level
});
continue;
}
case 'list': {
if (!meta.listItems) {
meta.listItems = [];
2026-06-04 16:43:52 +00:00
}
for (const item of makeListItems(block, lineNum)) {
const indent = tabChars.repeat(item.depth);
const position = appendLine(
indent + '- ' + (item.checkbox ? `[${item.checkbox}] ` : '') + item.text
);
if (indent.length > 0) {
position.start.col += indent.length - 2;
position.start.offset += indent.length - 2;
}
const listItem: ListItemCache = {
position,
parent: item.parent
};
if (item.checkbox) {
listItem['task'] = item.checkbox;
}
meta.listItems.push(listItem);
}
continue;
}
case 'text':
appendLine(block.text);
2026-06-04 16:43:52 +00:00
}
}
2026-06-04 16:43:52 +00:00
return [content, meta];
2026-06-04 16:43:52 +00:00
};
export class ListBuilder {
items: (ListItem | ListBlock)[] = [];
2026-06-04 16:43:52 +00:00
constructor(items: (ListItem | ListBlock)[] = []) {
this.items = items;
}
2026-06-04 16:43:52 +00:00
item(text: string, checkbox?: boolean) {
const i: ListItem = {
type: 'item',
text,
checkbox: checkbox === true ? 'x' : checkbox === false ? ' ' : undefined
};
return new ListBuilder([...this.items, i]);
}
2026-06-04 16:43:52 +00:00
list(lb: ListBuilder) {
return new ListBuilder([...this.items, lb.done()]);
}
2026-06-04 16:43:52 +00:00
done(): ListBlock {
return { type: 'list', items: this.items };
}
2026-06-04 16:43:52 +00:00
}
export class FileBuilder {
private lines: FileBlock[];
2026-06-04 16:43:52 +00:00
constructor(lines: FileBlock[] = []) {
this.lines = lines;
}
2026-06-04 16:43:52 +00:00
frontmatter(frontmatter: Record<string, unknown>): FileBuilder {
const frontmatterLines = Object.entries(frontmatter).map(
([k, v]): FileBlock => ({ type: 'frontmatter', key: k, text: v })
);
return new FileBuilder([...this.lines, ...frontmatterLines]);
}
2026-06-04 16:43:52 +00:00
heading(level: number, text: string): FileBuilder {
return new FileBuilder([...this.lines, { type: 'heading', level, text }]);
}
2026-06-04 16:43:52 +00:00
text(text: string): FileBuilder {
return new FileBuilder([...this.lines, { type: 'text', text }]);
}
2026-06-04 16:43:52 +00:00
list(list: ListBuilder): FileBuilder {
return new FileBuilder([...this.lines, list.done()]);
}
2026-06-04 16:43:52 +00:00
done() {
return makeFile(this.lines);
}
2026-06-04 16:43:52 +00:00
}