tabletop-commander/lib/tcmd/TokenIdentifiers.ts

387 lines
8.5 KiB
TypeScript

type TokenIdentifier = {
rx: RegExp;
parse: (s: string) => Token;
search?: (s: string, start: number, end: number) => {
start: number;
end: number;
text: string;
lastIndex: number;
};
};
export const TokenIdentifiers = new Map<
string,
TokenIdentifier
>();
// TokenIdentifiers.set("p", {
// rx: /\n{2,}((?:.|\n)*?)\n{2,}/g,
// parse(s) {
// const [_, content] = s.match(new RegExp(this.rx, ""))!;
// return {
// // content,
// content,
// raw: s,
// metadata: {},
// type: "p",
// uuid: crypto.randomUUID(),
// };
// },
// });
const rendersContentOnly = true;
const rendersChildrenOnly = true;
TokenIdentifiers.set("grid", {
search(s, start, end) {
const rx = /(?<!\/)(?:\[\])+/g;
const closeRx = /\/\[\]/g;
return search(s, start, end, rx, closeRx);
},
rx: /(?<!\/)(?:\[\])+\n+((?:.|\n)*?)\n+\/\[\]/g,
parse(s) {
const rx = /((?:\[\])+)\n+([\s\S]*)\n+\/\[\]/;
const [_, columns, content] = s.match(rx) ||
["", "..", "Unable to parse grid"];
return {
content,
raw: s,
metadata: {
columns: (columns.length / 2).toString(),
},
type: "grid",
uuid: crypto.randomUUID(),
rendersChildrenOnly,
};
},
});
TokenIdentifiers.set("card", {
rx: /\[{2}\n+([\s\S]*?)\n+\]{2}/g,
search(s, start, end) {
const rx = /\[\[/g;
const crx = /\]\]/g;
return search(s, start, end, rx, crx);
},
parse(s) {
const rx = /\[{2}\n+([\s\S]*)\n+\]{2}/;
const [_, content] = s.match(rx) || ["", "Unable to parse card"];
return {
content: content.trim(),
raw: s,
metadata: {},
type: "card",
uuid: crypto.randomUUID(),
};
},
});
TokenIdentifiers.set("code", {
rx: /`{3}\n+((?:.|\n)*?)\n+`{3}/g,
parse(s) {
return {
content: s.match(new RegExp(this.rx, ""))?.at(1) ||
"Unable to parse code",
raw: s,
metadata: {},
type: "code",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("list", {
rx: /^\s*-\s([\s\S]*?)\n\n/gm,
parse(s) {
return {
content: s.match(new RegExp(this.rx, ""))?.at(1) ||
"Unable to parse list",
raw: s,
metadata: {
initialDepth: s.replace("\n", "").split("-").at(0)?.length.toString() ||
"1",
},
type: "list",
uuid: crypto.randomUUID(),
rendersChildrenOnly,
};
},
});
TokenIdentifiers.set("list-item", {
rx: /^\s*-\s(.*?)$/gm,
parse(s) {
return {
content: s.match(new RegExp(this.rx, ""))?.at(1) ||
"Unable to parse list-item",
raw: s,
metadata: {
initialDepth: s.replace("\n", "").split("-").at(0)?.length.toString() ||
"1",
},
type: "list-item",
uuid: crypto.randomUUID(),
};
},
});
TokenIdentifiers.set("heading", {
rx: /^#+\s(.*?)$/gm,
parse(s) {
return {
content: s.match(new RegExp(this.rx, ""))?.at(1) ||
"Unable to parse heading",
raw: s,
metadata: {
strength: s.match(/#/g)?.length.toString() || "1",
},
type: "heading",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("image", {
rx: /\!\[(.*?)\]\((.*?)\)/g,
parse(s) {
const [_, title, src] = s.match(new RegExp(this.rx, ""))!;
return {
// content: inline,
content: title.trim(),
raw: s,
metadata: {
src,
},
type: "image",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("anchor", {
rx: /(?<![\!^])\[(.*?)\]\((.*?)\)/g,
parse(s) {
let preset, [_, title, href] = s.match(new RegExp(this.rx, ""))!;
const match = title.match(/`{3}(cta|button)?(.*)/);
if (match) {
[_, preset, title] = match;
}
const classes = {
button: "btn-primary inline-block",
cta: "btn-secondary inline-block uppercase",
};
return {
// content: inline,
content: title.trim(),
raw: s,
metadata: {
href,
classes: classes[preset as keyof typeof classes],
},
type: "anchor",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("inline-code", {
rx: /\s?`(.{3,}?|[a-z0-9]*?)`[^`a-z0-9\n]/gi,
parse(s) {
return {
// content: inline,
content: s.match(new RegExp(this.rx, "i"))?.at(1) ||
"Unable to parse inline-code",
raw: s,
metadata: {},
type: "inline-code",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("bold", {
rx: /\*{2}(.*?)\*{2}/g,
parse(s) {
return {
// content: inline,
content: s.match(new RegExp(this.rx, "i"))?.at(1) ||
"Unable to parse bold",
raw: s,
metadata: {},
type: "bold",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("italic", {
rx: /(?<!\*)\*([^\*]+?)\*(?!\*)/g,
parse(s) {
return {
// content: inline,
content: s.match(new RegExp(this.rx, "i"))?.at(1) ||
"Unable to parse italic",
raw: s,
metadata: {},
type: "italic",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("popover", {
rx: /\^\[(.*?)\]\<<(.*?)\>>/g,
parse(s) {
const [_, title, content] = s.match(new RegExp(this.rx, ""))!;
return {
// content,
content,
raw: s,
metadata: { title },
type: "popover",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("accordion", {
rx: /\[accordion(\s.*?)?]\n+((?:.|\n)*?)\n+\[\/accordion\]/g,
parse(s) {
const [_, title, content] = s.match(new RegExp(this.rx, ""))!;
return {
// content,
content,
raw: s,
metadata: { title },
type: "accordion",
uuid: crypto.randomUUID(),
};
},
});
TokenIdentifiers.set("p", {
// rx: /(?<=\n)\n?([\s\S]*?)\n(?=\n)/g,
rx: /(?<=\n\n)([\s\S]*?)(?=\n\n)/g,
parse(s) {
// const [_, content] = s.match(new RegExp(this.rx, ""))!;
return {
// content,
content: s,
raw: s,
metadata: {},
type: "p",
uuid: crypto.randomUUID(),
};
},
});
TokenIdentifiers.set("hr", {
rx: /^-{3,}$/gm,
parse(s) {
return {
content: s,
raw: s,
metadata: {},
type: "hr",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
TokenIdentifiers.set("comment", {
rx: /<!--[\s\S]+?-->/g,
parse(s) {
return {
content: "",
metadata: { comment: s },
raw: "",
type: "comment",
uuid: crypto.randomUUID(),
rendersContentOnly,
};
},
});
function findMatchingClosedParenthesis(
str: string,
openRegex: RegExp,
closedRegex: RegExp,
): number | null {
let openings = 0;
let closings = 0;
openRegex = new RegExp(openRegex, "g");
closedRegex = new RegExp(closedRegex, "g");
let lastOpeningSuccessIndex = 0;
let lastClosingSuccessIndex = 0;
do {
const openingMatch = openRegex.exec(str);
const closingMatch = closedRegex.exec(str);
if ((openingMatch && !closingMatch)) {
throw Error("Things have gone horribly wrong");
}
// if ((!openingMatch && closingMatch) || (!openingMatch && !closingMatch)) break;
if (
openingMatch && closingMatch && openingMatch.index < closingMatch.index
) {
openings++;
lastOpeningSuccessIndex = openingMatch.index + openingMatch[0].length;
closedRegex.lastIndex = lastClosingSuccessIndex;
} else if (
(!openingMatch && closingMatch) ||
(openingMatch && closingMatch && openingMatch.index > closingMatch.index)
) {
closings++;
lastClosingSuccessIndex = closingMatch.index + closingMatch[0].length;
openRegex.lastIndex = lastOpeningSuccessIndex;
} else {
return closingMatch?.index ?? null;
}
} while (openings > closings);
return closedRegex.lastIndex;
}
interface SearchResult {
start: number;
end: number;
text: string;
lastIndex: number;
}
function search(
s: string,
start: number,
end: number,
openRx: RegExp,
closeRx: RegExp,
): SearchResult {
const oldEnd = end;
const newEnd = findMatchingClosedParenthesis(
s,
// s.substring(0, end - start),
openRx,
closeRx,
);
if (newEnd === null) throw Error("There was an issue finding a closing tag");
end = newEnd + start;
return {
start,
end,
text: s.substring(0, newEnd),
lastIndex: oldEnd === end ? end : start + s.match(openRx)![0].length,
};
}