1
0
silverbullet/plugs/query/materialized_queries.ts

178 lines
5.2 KiB
TypeScript
Raw Normal View History

2022-10-14 13:11:33 +00:00
import { editor } from "$sb/silverbullet-syscall/mod.ts";
import Handlebars from "handlebars";
2022-04-01 15:07:08 +00:00
2022-10-14 13:11:33 +00:00
import { markdown, space } from "$sb/silverbullet-syscall/mod.ts";
import { invokeFunction } from "$sb/silverbullet-syscall/system.ts";
import { renderQuery } from "./engine.ts";
import { parseQuery } from "./parser.ts";
import { replaceTemplateVars } from "../core/template.ts";
2022-10-14 13:11:33 +00:00
import { jsonToMDTable } from "./util.ts";
import { queryRegex } from "$sb/lib/query.ts";
import { events } from "$sb/plugos-syscall/mod.ts";
import { replaceAsync } from "$sb/lib/util.ts";
import { nodeAtPos, renderToText } from "$sb/lib/tree.ts";
import { extractMeta } from "./data.ts";
2022-03-29 15:02:28 +00:00
export async function updateMaterializedQueriesCommand() {
2022-10-14 13:11:33 +00:00
const currentPage = await editor.getCurrentPage();
await editor.save();
if (
await invokeFunction(
"server",
"updateMaterializedQueriesOnPage",
currentPage,
)
) {
2022-10-14 13:11:33 +00:00
await editor.reloadPage();
}
}
export const templateInstRegex =
2022-08-10 07:18:11 +00:00
/(<!--\s*#(use|use-verbose|include)\s+\[\[([^\]]+)\]\](.*?)-->)(.+?)(<!--\s*\/\2\s*-->)/gs;
2022-10-10 16:19:08 +00:00
function updateTemplateInstantiations(
text: string,
pageName: string,
): Promise<string> {
return replaceAsync(
text,
templateInstRegex,
2022-10-14 13:11:33 +00:00
async (fullMatch, startInst, type, template, args, _body, endInst) => {
args = args.trim();
let parsedArgs = {};
if (args) {
try {
parsedArgs = JSON.parse(args);
2022-10-14 13:11:33 +00:00
} catch {
console.error("Failed to parse template instantiation args", args);
return fullMatch;
}
}
let templateText = "";
if (template.startsWith("http://") || template.startsWith("https://")) {
try {
2022-10-14 13:11:33 +00:00
const req = await fetch(template);
templateText = await req.text();
} catch (e: any) {
templateText = `ERROR: ${e.message}`;
}
} else {
2022-10-14 13:11:33 +00:00
templateText = await space.readPage(template);
}
let newBody = templateText;
2022-08-09 13:37:47 +00:00
// if it's a template injection (not a literal "include")
2022-08-10 07:18:11 +00:00
if (type === "use" || type === "use-verbose") {
2022-10-14 13:11:33 +00:00
const tree = await markdown.parseMarkdown(templateText);
2022-08-09 13:37:47 +00:00
extractMeta(tree, ["$disableDirectives"]);
templateText = renderToText(tree);
2022-10-14 13:11:33 +00:00
const templateFn = Handlebars.compile(
replaceTemplateVars(templateText, pageName),
{ noEscape: true },
);
newBody = templateFn(parsedArgs);
}
return `${startInst}\n${newBody.trim()}\n${endInst}`;
},
);
}
2022-10-10 16:19:08 +00:00
function cleanTemplateInstantiations(text: string): Promise<string> {
2022-08-09 13:37:47 +00:00
return replaceAsync(
text,
templateInstRegex,
2022-10-10 16:19:08 +00:00
(
2022-10-14 13:11:33 +00:00
_fullMatch,
2022-10-10 16:19:08 +00:00
startInst,
type,
2022-10-14 13:11:33 +00:00
_template,
_args,
2022-10-10 16:19:08 +00:00
body,
endInst,
): Promise<string> => {
2022-08-10 07:18:11 +00:00
if (type === "use") {
2022-08-09 13:37:47 +00:00
body = body.replaceAll(
queryRegex,
(
2022-10-14 13:11:33 +00:00
_fullMatch: string,
_startQuery: string,
_query: string,
body: string,
2022-08-09 13:37:47 +00:00
) => {
return body.trim();
},
2022-08-09 13:37:47 +00:00
);
}
2022-10-10 16:19:08 +00:00
return Promise.resolve(`${startInst}${body}${endInst}`);
},
2022-08-09 13:37:47 +00:00
);
}
2022-03-29 15:02:28 +00:00
// Called from client, running on server
export async function updateMaterializedQueriesOnPage(
pageName: string,
): Promise<boolean> {
2022-10-10 16:19:08 +00:00
// console.log("Updating queries");
let text = "";
try {
2022-10-14 13:11:33 +00:00
text = await space.readPage(pageName);
} catch {
console.warn(
"Could not read page",
pageName,
"perhaps it doesn't yet exist",
);
return false;
}
let newText = await updateTemplateInstantiations(text, pageName);
2022-10-14 13:11:33 +00:00
const tree = await markdown.parseMarkdown(newText);
const metaData = extractMeta(tree, ["$disableDirectives"]);
2022-10-22 18:23:54 +00:00
// console.log("Meta data", pageName, metaData);
2022-08-09 13:37:47 +00:00
if (metaData.$disableDirectives) {
console.log("Directives disabled, skipping");
return false;
}
newText = renderToText(tree);
newText = await replaceAsync(
newText,
queryRegex,
2022-10-14 13:11:33 +00:00
async (fullMatch, startQuery, query, _body, endQuery, index) => {
const currentNode = nodeAtPos(tree, index + 1);
if (currentNode?.type !== "CommentBlock") {
// If not a comment block, it's likely a code block, ignore
return fullMatch;
}
2022-10-14 13:11:33 +00:00
const parsedQuery = parseQuery(replaceTemplateVars(query, pageName));
2022-08-10 07:18:11 +00:00
// console.log("Parsed query", parsedQuery);
// Let's dispatch an event and see what happens
2022-10-14 13:11:33 +00:00
const results = await events.dispatchEvent(
`query:${parsedQuery.table}`,
{ query: parsedQuery, pageName: pageName },
10 * 1000,
);
if (results.length === 0) {
return `${startQuery}\n${endQuery}`;
} else if (results.length === 1) {
if (parsedQuery.render) {
2022-10-14 13:11:33 +00:00
const rendered = await renderQuery(parsedQuery, results[0]);
return `${startQuery}\n${rendered.trim()}\n${endQuery}`;
} else {
return `${startQuery}\n${jsonToMDTable(results[0])}\n${endQuery}`;
}
} else {
console.error("Too many query results", results);
return fullMatch;
}
},
);
2022-08-09 13:37:47 +00:00
newText = await cleanTemplateInstantiations(newText);
if (text !== newText) {
2022-10-14 13:11:33 +00:00
await space.writePage(pageName, newText);
return true;
}
return false;
2022-03-29 15:02:28 +00:00
}