1
0
silverbullet/packages/plugs/query/materialized_queries.ts

141 lines
4.1 KiB
TypeScript
Raw Normal View History

import {
getCurrentPage,
reloadPage,
2022-04-25 08:33:38 +00:00
save,
2022-04-25 09:24:13 +00:00
} from "@silverbulletmd/plugos-silverbullet-syscall/editor";
import Handlebars from "handlebars";
2022-04-01 15:07:08 +00:00
2022-04-25 09:24:13 +00:00
import {
readPage,
writePage,
} from "@silverbulletmd/plugos-silverbullet-syscall/space";
import { invokeFunction } from "@silverbulletmd/plugos-silverbullet-syscall/system";
2022-07-06 10:18:47 +00:00
import { renderQuery } from "./engine";
import { parseQuery } from "./parser";
import { replaceTemplateVars } from "../core/template";
import { jsonToMDTable, queryRegex } from "./util";
2022-04-25 08:33:38 +00:00
import { dispatch } from "@plugos/plugos-syscall/event";
import { replaceAsync } from "../lib/util";
import { parseMarkdown } from "@silverbulletmd/plugos-silverbullet-syscall/markdown";
import { nodeAtPos } from "@silverbulletmd/common/tree";
2022-03-29 15:02:28 +00:00
export async function updateMaterializedQueriesCommand() {
2022-04-01 15:07:08 +00:00
const currentPage = await getCurrentPage();
await save();
if (
await invokeFunction(
"server",
"updateMaterializedQueriesOnPage",
currentPage
)
) {
await reloadPage();
}
}
export const templateInstRegex =
/(<!--\s*#(template|include)\s+"([^"]+)"(.+?)-->)(.+?)(<!--\s*\/\2\s*-->)/gs;
async function updateTemplateInstantiations(
text: string,
pageName: string
): Promise<string> {
return replaceAsync(
text,
templateInstRegex,
async (fullMatch, startInst, type, template, args, body, endInst) => {
args = args.trim();
let parsedArgs = {};
if (args) {
try {
parsedArgs = JSON.parse(args);
} catch (e) {
console.error("Failed to parse template instantiation args", args);
return fullMatch;
}
}
let templateText = "";
if (template.startsWith("http://") || template.startsWith("https://")) {
try {
let req = await fetch(template);
templateText = await req.text();
} catch (e: any) {
templateText = `ERROR: ${e.message}`;
}
} else {
templateText = (await readPage(template)).text;
}
let newBody = templateText;
// if it's a template (note a literal "include")
if (type === "template") {
let templateFn = Handlebars.compile(
replaceTemplateVars(templateText, pageName),
{ noEscape: true }
);
newBody = templateFn(parsedArgs);
}
return `${startInst}\n${newBody.trim()}\n${endInst}`;
}
);
}
2022-03-29 15:02:28 +00:00
// Called from client, running on server
export async function updateMaterializedQueriesOnPage(
pageName: string
): Promise<boolean> {
let text = "";
try {
text = (await readPage(pageName)).text;
} catch {
console.warn(
"Could not read page",
pageName,
"perhaps it doesn't yet exist"
);
return false;
}
let newText = await updateTemplateInstantiations(text, pageName);
let tree = await parseMarkdown(newText);
newText = await replaceAsync(
newText,
queryRegex,
async (fullMatch, startQuery, query, body, endQuery, index) => {
let currentNode = nodeAtPos(tree, index + 1);
if (currentNode?.type !== "CommentBlock") {
// If not a comment block, it's likely a code block, ignore
return fullMatch;
}
// console.log("Text slice", newText.substring(index, index + 100));
let parsedQuery = parseQuery(replaceTemplateVars(query, pageName));
console.log("Parsed query", parsedQuery);
// Let's dispatch an event and see what happens
let results = await dispatch(
`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) {
let 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-03-29 15:02:28 +00:00
}
);
if (text !== newText) {
await writePage(pageName, newText);
return true;
}
return false;
2022-03-29 15:02:28 +00:00
}