Massive restructure of plugin API
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { listEvents } from "$sb/plugos-syscall/event.ts";
|
||||
import { matchBefore } from "$sb/silverbullet-syscall/editor.ts";
|
||||
import { events } from "$sb/plugos-syscall/mod.ts";
|
||||
import { editor } from "$sb/silverbullet-syscall/mod.ts";
|
||||
|
||||
export async function queryComplete() {
|
||||
const prefix = await matchBefore("#query [\\w\\-_]*");
|
||||
const prefix = await editor.matchBefore("#query [\\w\\-_]*");
|
||||
|
||||
if (prefix) {
|
||||
const allEvents = await listEvents();
|
||||
const allEvents = await events.listEvents();
|
||||
// console.log("All events", allEvents);
|
||||
|
||||
return {
|
||||
|
||||
+23
-27
@@ -1,52 +1,48 @@
|
||||
// Index key space:
|
||||
// data:page@pos
|
||||
|
||||
import type { IndexTreeEvent } from "../../web/app_event.ts";
|
||||
import {
|
||||
batchSet,
|
||||
queryPrefix,
|
||||
} from "../../syscall/silverbullet-syscall/index.ts";
|
||||
import type { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
|
||||
import { index } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import {
|
||||
addParentPointers,
|
||||
collectNodesOfType,
|
||||
findNodeOfType,
|
||||
ParseTree,
|
||||
replaceNodesMatching,
|
||||
} from "../../common/tree.ts";
|
||||
import type { QueryProviderEvent } from "./engine.ts";
|
||||
import { applyQuery } from "./engine.ts";
|
||||
import { removeQueries } from "./util.ts";
|
||||
} from "$sb/lib/tree.ts";
|
||||
import { applyQuery, removeQueries } from "$sb/lib/query.ts";
|
||||
import * as YAML from "yaml";
|
||||
|
||||
export async function indexData({ name, tree }: IndexTreeEvent) {
|
||||
let dataObjects: { key: string; value: Object }[] = [];
|
||||
const dataObjects: { key: string; value: any }[] = [];
|
||||
|
||||
removeQueries(tree);
|
||||
|
||||
collectNodesOfType(tree, "FencedCode").forEach((t) => {
|
||||
let codeInfoNode = findNodeOfType(t, "CodeInfo");
|
||||
const codeInfoNode = findNodeOfType(t, "CodeInfo");
|
||||
if (!codeInfoNode) {
|
||||
return;
|
||||
}
|
||||
if (codeInfoNode.children![0].text !== "data") {
|
||||
return;
|
||||
}
|
||||
let codeTextNode = findNodeOfType(t, "CodeText");
|
||||
const codeTextNode = findNodeOfType(t, "CodeText");
|
||||
if (!codeTextNode) {
|
||||
// Honestly, this shouldn't happen
|
||||
return;
|
||||
}
|
||||
let codeText = codeTextNode.children![0].text!;
|
||||
const codeText = codeTextNode.children![0].text!;
|
||||
try {
|
||||
const docs = codeText.split("---").map((d) => YAML.parse(d));
|
||||
// We support multiple YAML documents in one block
|
||||
for (let doc of parseAllDocuments(codeText)) {
|
||||
if (!doc.contents) {
|
||||
for (let i = 0; i < docs.length; i++) {
|
||||
const doc = docs[i];
|
||||
if (!doc) {
|
||||
continue;
|
||||
}
|
||||
console.log(doc.contents.toJSON());
|
||||
dataObjects.push({
|
||||
key: `data:${name}@${t.from! + doc.range[0]}`,
|
||||
value: doc.contents.toJSON(),
|
||||
key: `data:${name}@${i}`,
|
||||
value: doc,
|
||||
});
|
||||
}
|
||||
// console.log("Parsed data", parsedData);
|
||||
@@ -56,7 +52,7 @@ export async function indexData({ name, tree }: IndexTreeEvent) {
|
||||
}
|
||||
});
|
||||
console.log("Found", dataObjects.length, "data objects");
|
||||
await batchSet(name, dataObjects);
|
||||
await index.batchSet(name, dataObjects);
|
||||
}
|
||||
|
||||
export function extractMeta(
|
||||
@@ -83,23 +79,23 @@ export function extractMeta(
|
||||
if (t.type !== "FencedCode") {
|
||||
return;
|
||||
}
|
||||
let codeInfoNode = findNodeOfType(t, "CodeInfo");
|
||||
const codeInfoNode = findNodeOfType(t, "CodeInfo");
|
||||
if (!codeInfoNode) {
|
||||
return;
|
||||
}
|
||||
if (codeInfoNode.children![0].text !== "meta") {
|
||||
return;
|
||||
}
|
||||
let codeTextNode = findNodeOfType(t, "CodeText");
|
||||
const codeTextNode = findNodeOfType(t, "CodeText");
|
||||
if (!codeTextNode) {
|
||||
// Honestly, this shouldn't happen
|
||||
return;
|
||||
}
|
||||
let codeText = codeTextNode.children![0].text!;
|
||||
const codeText = codeTextNode.children![0].text!;
|
||||
data = YAML.parse(codeText);
|
||||
if (removeKeys.length > 0) {
|
||||
let newData = { ...data };
|
||||
for (let key of removeKeys) {
|
||||
const newData = { ...data };
|
||||
for (const key of removeKeys) {
|
||||
delete newData[key];
|
||||
}
|
||||
codeTextNode.children![0].text = YAML.stringify(newData).trim();
|
||||
@@ -117,9 +113,9 @@ export function extractMeta(
|
||||
export async function queryProvider({
|
||||
query,
|
||||
}: QueryProviderEvent): Promise<any[]> {
|
||||
let allData: any[] = [];
|
||||
for (let { key, page, value } of await queryPrefix("data:")) {
|
||||
let [, pos] = key.split("@");
|
||||
const allData: any[] = [];
|
||||
for (const { key, page, value } of await index.queryPrefix("data:")) {
|
||||
const [, pos] = key.split("@");
|
||||
allData.push({
|
||||
...value,
|
||||
page: page,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { assertEquals } from "../../test_deps.ts";
|
||||
import { applyQuery } from "./engine.ts";
|
||||
import { applyQuery } from "$sb/lib/query.ts";
|
||||
import { parseQuery } from "./parser.ts";
|
||||
|
||||
Deno.test("Test parser", () => {
|
||||
let parsedBasicQuery = parseQuery(`page`);
|
||||
const parsedBasicQuery = parseQuery(`page`);
|
||||
assertEquals(parsedBasicQuery.table, "page");
|
||||
|
||||
let parsedQuery1 = parseQuery(
|
||||
const parsedQuery1 = parseQuery(
|
||||
`task where completed = false and dueDate <= "{{today}}" order by dueDate desc limit 5`,
|
||||
);
|
||||
assertEquals(parsedQuery1.table, "task");
|
||||
@@ -25,7 +25,7 @@ Deno.test("Test parser", () => {
|
||||
value: "{{today}}",
|
||||
});
|
||||
|
||||
let parsedQuery2 = parseQuery(`page where name =~ /interview\\/.*/"`);
|
||||
const parsedQuery2 = parseQuery(`page where name =~ /interview\\/.*/"`);
|
||||
assertEquals(parsedQuery2.table, "page");
|
||||
assertEquals(parsedQuery2.filter.length, 1);
|
||||
assertEquals(parsedQuery2.filter[0], {
|
||||
@@ -34,7 +34,7 @@ Deno.test("Test parser", () => {
|
||||
value: "interview\\/.*",
|
||||
});
|
||||
|
||||
let parsedQuery3 = parseQuery(`page where something != null`);
|
||||
const parsedQuery3 = parseQuery(`page where something != null`);
|
||||
assertEquals(parsedQuery3.table, "page");
|
||||
assertEquals(parsedQuery3.filter.length, 1);
|
||||
assertEquals(parsedQuery3.filter[0], {
|
||||
@@ -77,7 +77,7 @@ Deno.test("Test parser", () => {
|
||||
});
|
||||
|
||||
Deno.test("Test applyQuery", () => {
|
||||
let data: any[] = [
|
||||
const data: any[] = [
|
||||
{ name: "interview/My Interview", lastModified: 1 },
|
||||
{ name: "interview/My Interview 2", lastModified: 2 },
|
||||
{ name: "Pete", age: 38 },
|
||||
@@ -132,7 +132,7 @@ Deno.test("Test applyQuery", () => {
|
||||
});
|
||||
|
||||
Deno.test("Test applyQuery with multi value", () => {
|
||||
let data: any[] = [
|
||||
const data: any[] = [
|
||||
{ name: "Pete", children: ["John", "Angie"] },
|
||||
{ name: "Angie", children: ["Angie"] },
|
||||
{ name: "Steve" },
|
||||
|
||||
+16
-121
@@ -1,15 +1,10 @@
|
||||
import { collectNodesOfType, ParseTree } from "../../common/tree.ts";
|
||||
import { collectNodesOfType, ParseTree } from "$sb/lib/tree.ts";
|
||||
import Handlebars from "handlebars";
|
||||
import * as YAML from "yaml";
|
||||
|
||||
import { readPage } from "../../syscall/silverbullet-syscall/space.ts";
|
||||
import { niceDate } from "../core/dates.ts";
|
||||
import { ParsedQuery } from "./parser.ts";
|
||||
|
||||
export type QueryProviderEvent = {
|
||||
query: ParsedQuery;
|
||||
pageName: string;
|
||||
};
|
||||
import { space } from "$sb/silverbullet-syscall/mod.ts";
|
||||
import { niceDate } from "$sb/lib/dates.ts";
|
||||
import { ParsedQuery } from "$sb/lib/query.ts";
|
||||
|
||||
export function valueNodeToVal(valNode: ParseTree): any {
|
||||
switch (valNode.type) {
|
||||
@@ -21,124 +16,24 @@ export function valueNodeToVal(valNode: ParseTree): any {
|
||||
return null;
|
||||
case "Name":
|
||||
return valNode.children![0].text!;
|
||||
case "Regex":
|
||||
let val = valNode.children![0].text!;
|
||||
case "Regex": {
|
||||
const val = valNode.children![0].text!;
|
||||
return val.substring(1, val.length - 1);
|
||||
case "String":
|
||||
let stringVal = valNode.children![0].text!;
|
||||
}
|
||||
case "String": {
|
||||
const stringVal = valNode.children![0].text!;
|
||||
return stringVal.substring(1, stringVal.length - 1);
|
||||
case "PageRef":
|
||||
let pageRefVal = valNode.children![0].text!;
|
||||
}
|
||||
case "PageRef": {
|
||||
const pageRefVal = valNode.children![0].text!;
|
||||
return pageRefVal.substring(2, pageRefVal.length - 2);
|
||||
case "List":
|
||||
}
|
||||
case "List": {
|
||||
return collectNodesOfType(valNode, "Value").map((t) =>
|
||||
valueNodeToVal(t.children![0])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function applyQuery<T>(parsedQuery: ParsedQuery, records: T[]): T[] {
|
||||
let resultRecords: any[] = [];
|
||||
if (parsedQuery.filter.length === 0) {
|
||||
resultRecords = records.slice();
|
||||
} else {
|
||||
recordLoop:
|
||||
for (let record of records) {
|
||||
const recordAny: any = record;
|
||||
for (let { op, prop, value } of parsedQuery.filter) {
|
||||
switch (op) {
|
||||
case "=":
|
||||
const recordPropVal = recordAny[prop];
|
||||
if (Array.isArray(recordPropVal) && !Array.isArray(value)) {
|
||||
// Record property is an array, and value is a scalar: find the value in the array
|
||||
if (!recordPropVal.includes(value)) {
|
||||
continue recordLoop;
|
||||
}
|
||||
} else if (Array.isArray(recordPropVal) && Array.isArray(value)) {
|
||||
// Record property is an array, and value is an array: find the value in the array
|
||||
if (!recordPropVal.some((v) => value.includes(v))) {
|
||||
continue recordLoop;
|
||||
}
|
||||
} else if (!(recordPropVal == value)) {
|
||||
// Both are scalars: exact value
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
case "!=":
|
||||
if (!(recordAny[prop] != value)) {
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
case "<":
|
||||
if (!(recordAny[prop] < value)) {
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
case "<=":
|
||||
if (!(recordAny[prop] <= value)) {
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
case ">":
|
||||
if (!(recordAny[prop] > value)) {
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
case ">=":
|
||||
if (!(recordAny[prop] >= value)) {
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
case "=~":
|
||||
// TODO: Cache regexps somehow
|
||||
if (!new RegExp(value).exec(recordAny[prop])) {
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
case "!=~":
|
||||
if (new RegExp(value).exec(recordAny[prop])) {
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
case "in":
|
||||
if (!value.includes(recordAny[prop])) {
|
||||
continue recordLoop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
resultRecords.push(recordAny);
|
||||
}
|
||||
}
|
||||
// Now the sorting
|
||||
if (parsedQuery.orderBy) {
|
||||
resultRecords = resultRecords.sort((a: any, b: any) => {
|
||||
const orderBy = parsedQuery.orderBy!;
|
||||
const orderDesc = parsedQuery.orderDesc!;
|
||||
if (a[orderBy] === b[orderBy]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a[orderBy] < b[orderBy]) {
|
||||
return orderDesc ? 1 : -1;
|
||||
} else {
|
||||
return orderDesc ? -1 : 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (parsedQuery.limit) {
|
||||
resultRecords = resultRecords.slice(0, parsedQuery.limit);
|
||||
}
|
||||
if (parsedQuery.select) {
|
||||
resultRecords = resultRecords.map((rec) => {
|
||||
let newRec: any = {};
|
||||
for (let k of parsedQuery.select!) {
|
||||
newRec[k] = rec[k];
|
||||
}
|
||||
return newRec;
|
||||
});
|
||||
}
|
||||
return resultRecords;
|
||||
}
|
||||
|
||||
export async function renderQuery(
|
||||
@@ -175,9 +70,9 @@ export async function renderQuery(
|
||||
return YAML.stringify(v).trim();
|
||||
}
|
||||
});
|
||||
let { text: templateText } = await readPage(parsedQuery.render);
|
||||
let templateText = await space.readPage(parsedQuery.render);
|
||||
templateText = `{{#each .}}\n${templateText}\n{{/each}}`;
|
||||
let template = Handlebars.compile(templateText, { noEscape: true });
|
||||
const template = Handlebars.compile(templateText, { noEscape: true });
|
||||
return template(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
import {
|
||||
getCurrentPage,
|
||||
reloadPage,
|
||||
save,
|
||||
} from "$sb/silverbullet-syscall/editor.ts";
|
||||
import { editor } from "$sb/silverbullet-syscall/mod.ts";
|
||||
|
||||
import Handlebars from "handlebars";
|
||||
|
||||
import { readPage, writePage } from "$sb/silverbullet-syscall/space.ts";
|
||||
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";
|
||||
import { jsonToMDTable, queryRegex } from "./util.ts";
|
||||
import { dispatch } from "$sb/plugos-syscall/event.ts";
|
||||
import { replaceAsync } from "../lib/util.ts";
|
||||
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
|
||||
import { nodeAtPos, renderToText } from "../../common/tree.ts";
|
||||
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";
|
||||
|
||||
export async function updateMaterializedQueriesCommand() {
|
||||
const currentPage = await getCurrentPage();
|
||||
await save();
|
||||
const currentPage = await editor.getCurrentPage();
|
||||
await editor.save();
|
||||
if (
|
||||
await invokeFunction(
|
||||
"server",
|
||||
@@ -29,7 +25,7 @@ export async function updateMaterializedQueriesCommand() {
|
||||
)
|
||||
) {
|
||||
console.log("Going reload the page");
|
||||
await reloadPage();
|
||||
await editor.reloadPage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +39,13 @@ function updateTemplateInstantiations(
|
||||
return replaceAsync(
|
||||
text,
|
||||
templateInstRegex,
|
||||
async (fullMatch, startInst, type, template, args, body, endInst) => {
|
||||
async (fullMatch, startInst, type, template, args, _body, endInst) => {
|
||||
args = args.trim();
|
||||
let parsedArgs = {};
|
||||
if (args) {
|
||||
try {
|
||||
parsedArgs = JSON.parse(args);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
console.error("Failed to parse template instantiation args", args);
|
||||
return fullMatch;
|
||||
}
|
||||
@@ -57,21 +53,21 @@ function updateTemplateInstantiations(
|
||||
let templateText = "";
|
||||
if (template.startsWith("http://") || template.startsWith("https://")) {
|
||||
try {
|
||||
let req = await fetch(template);
|
||||
const req = await fetch(template);
|
||||
templateText = await req.text();
|
||||
} catch (e: any) {
|
||||
templateText = `ERROR: ${e.message}`;
|
||||
}
|
||||
} else {
|
||||
templateText = (await readPage(template)).text;
|
||||
templateText = await space.readPage(template);
|
||||
}
|
||||
let newBody = templateText;
|
||||
// if it's a template injection (not a literal "include")
|
||||
if (type === "use" || type === "use-verbose") {
|
||||
let tree = await parseMarkdown(templateText);
|
||||
const tree = await markdown.parseMarkdown(templateText);
|
||||
extractMeta(tree, ["$disableDirectives"]);
|
||||
templateText = renderToText(tree);
|
||||
let templateFn = Handlebars.compile(
|
||||
const templateFn = Handlebars.compile(
|
||||
replaceTemplateVars(templateText, pageName),
|
||||
{ noEscape: true },
|
||||
);
|
||||
@@ -87,11 +83,11 @@ function cleanTemplateInstantiations(text: string): Promise<string> {
|
||||
text,
|
||||
templateInstRegex,
|
||||
(
|
||||
fullMatch,
|
||||
_fullMatch,
|
||||
startInst,
|
||||
type,
|
||||
template,
|
||||
args,
|
||||
_template,
|
||||
_args,
|
||||
body,
|
||||
endInst,
|
||||
): Promise<string> => {
|
||||
@@ -99,9 +95,9 @@ function cleanTemplateInstantiations(text: string): Promise<string> {
|
||||
body = body.replaceAll(
|
||||
queryRegex,
|
||||
(
|
||||
fullMatch: string,
|
||||
startQuery: string,
|
||||
query: string,
|
||||
_fullMatch: string,
|
||||
_startQuery: string,
|
||||
_query: string,
|
||||
body: string,
|
||||
) => {
|
||||
return body.trim();
|
||||
@@ -120,7 +116,7 @@ export async function updateMaterializedQueriesOnPage(
|
||||
// console.log("Updating queries");
|
||||
let text = "";
|
||||
try {
|
||||
text = (await readPage(pageName)).text;
|
||||
text = await space.readPage(pageName);
|
||||
} catch {
|
||||
console.warn(
|
||||
"Could not read page",
|
||||
@@ -130,8 +126,8 @@ export async function updateMaterializedQueriesOnPage(
|
||||
return false;
|
||||
}
|
||||
let newText = await updateTemplateInstantiations(text, pageName);
|
||||
let tree = await parseMarkdown(newText);
|
||||
let metaData = extractMeta(tree, ["$disableDirectives"]);
|
||||
const tree = await markdown.parseMarkdown(newText);
|
||||
const metaData = extractMeta(tree, ["$disableDirectives"]);
|
||||
if (metaData.$disableDirectives) {
|
||||
console.log("Directives disabled, skipping");
|
||||
return false;
|
||||
@@ -141,18 +137,18 @@ export async function updateMaterializedQueriesOnPage(
|
||||
newText = await replaceAsync(
|
||||
newText,
|
||||
queryRegex,
|
||||
async (fullMatch, startQuery, query, body, endQuery, index) => {
|
||||
let currentNode = nodeAtPos(tree, index + 1);
|
||||
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;
|
||||
}
|
||||
|
||||
let parsedQuery = parseQuery(replaceTemplateVars(query, pageName));
|
||||
const parsedQuery = parseQuery(replaceTemplateVars(query, pageName));
|
||||
|
||||
// console.log("Parsed query", parsedQuery);
|
||||
// Let's dispatch an event and see what happens
|
||||
let results = await dispatch(
|
||||
const results = await events.dispatchEvent(
|
||||
`query:${parsedQuery.table}`,
|
||||
{ query: parsedQuery, pageName: pageName },
|
||||
10 * 1000,
|
||||
@@ -161,7 +157,7 @@ export async function updateMaterializedQueriesOnPage(
|
||||
return `${startQuery}\n${endQuery}`;
|
||||
} else if (results.length === 1) {
|
||||
if (parsedQuery.render) {
|
||||
let rendered = await renderQuery(parsedQuery, results[0]);
|
||||
const rendered = await renderQuery(parsedQuery, results[0]);
|
||||
return `${startQuery}\n${rendered.trim()}\n${endQuery}`;
|
||||
} else {
|
||||
return `${startQuery}\n${jsonToMDTable(results[0])}\n${endQuery}`;
|
||||
@@ -174,7 +170,7 @@ export async function updateMaterializedQueriesOnPage(
|
||||
);
|
||||
newText = await cleanTemplateInstantiations(newText);
|
||||
if (text !== newText) {
|
||||
await writePage(pageName, newText);
|
||||
await space.writePage(pageName, newText);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
+17
-36
@@ -2,35 +2,20 @@ import {
|
||||
collectNodesOfType,
|
||||
findNodeOfType,
|
||||
replaceNodesMatching,
|
||||
} from "../../common/tree.ts";
|
||||
} from "$sb/lib/tree.ts";
|
||||
import { lezerToParseTree } from "../../common/parse_tree.ts";
|
||||
import { valueNodeToVal } from "./engine.ts";
|
||||
|
||||
// @ts-ignore auto generated
|
||||
import { parser } from "./parse-query.js";
|
||||
|
||||
export type Filter = {
|
||||
op: string;
|
||||
prop: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
export type ParsedQuery = {
|
||||
table: string;
|
||||
orderBy?: string;
|
||||
orderDesc?: boolean;
|
||||
limit?: number;
|
||||
filter: Filter[];
|
||||
select?: string[];
|
||||
render?: string;
|
||||
};
|
||||
import { ParsedQuery, QueryFilter } from "$sb/lib/query.ts";
|
||||
|
||||
export function parseQuery(query: string): ParsedQuery {
|
||||
let n = lezerToParseTree(query, parser.parse(query).topNode);
|
||||
const n = lezerToParseTree(query, parser.parse(query).topNode);
|
||||
// Clean the tree a bit
|
||||
replaceNodesMatching(n, (n) => {
|
||||
if (!n.type) {
|
||||
let trimmed = n.text!.trim();
|
||||
const trimmed = n.text!.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
@@ -39,50 +24,47 @@ export function parseQuery(query: string): ParsedQuery {
|
||||
});
|
||||
|
||||
// console.log("Parsed", JSON.stringify(n, null, 2));
|
||||
let queryNode = n.children![0];
|
||||
let parsedQuery: ParsedQuery = {
|
||||
const queryNode = n.children![0];
|
||||
const parsedQuery: ParsedQuery = {
|
||||
table: queryNode.children![0].children![0].text!,
|
||||
filter: [],
|
||||
};
|
||||
let orderByNode = findNodeOfType(queryNode, "OrderClause");
|
||||
const orderByNode = findNodeOfType(queryNode, "OrderClause");
|
||||
if (orderByNode) {
|
||||
let nameNode = findNodeOfType(orderByNode, "Name");
|
||||
const nameNode = findNodeOfType(orderByNode, "Name");
|
||||
parsedQuery.orderBy = nameNode!.children![0].text!;
|
||||
let orderNode = findNodeOfType(orderByNode, "Order");
|
||||
const orderNode = findNodeOfType(orderByNode, "Order");
|
||||
parsedQuery.orderDesc = orderNode
|
||||
? orderNode.children![0].text! === "desc"
|
||||
: false;
|
||||
}
|
||||
let limitNode = findNodeOfType(queryNode, "LimitClause");
|
||||
const limitNode = findNodeOfType(queryNode, "LimitClause");
|
||||
if (limitNode) {
|
||||
let nameNode = findNodeOfType(limitNode, "Number");
|
||||
const nameNode = findNodeOfType(limitNode, "Number");
|
||||
parsedQuery.limit = valueNodeToVal(nameNode!);
|
||||
}
|
||||
|
||||
let filterNodes = collectNodesOfType(queryNode, "FilterExpr");
|
||||
for (let filterNode of filterNodes) {
|
||||
const filterNodes = collectNodesOfType(queryNode, "FilterExpr");
|
||||
for (const filterNode of filterNodes) {
|
||||
let val: any = undefined;
|
||||
let valNode = filterNode.children![2].children![0];
|
||||
const valNode = filterNode.children![2].children![0];
|
||||
val = valueNodeToVal(valNode);
|
||||
let f: Filter = {
|
||||
const f: QueryFilter = {
|
||||
prop: filterNode.children![0].children![0].text!,
|
||||
op: filterNode.children![1].text!,
|
||||
value: val,
|
||||
};
|
||||
parsedQuery.filter.push(f);
|
||||
}
|
||||
let selectNode = findNodeOfType(queryNode, "SelectClause");
|
||||
const selectNode = findNodeOfType(queryNode, "SelectClause");
|
||||
if (selectNode) {
|
||||
// console.log("Select node", JSON.stringify(selectNode));
|
||||
parsedQuery.select = [];
|
||||
collectNodesOfType(selectNode, "Name").forEach((t) => {
|
||||
parsedQuery.select!.push(t.children![0].text!);
|
||||
});
|
||||
// let nameNode = findNodeOfType(selectNode, "Number");
|
||||
// parsedQuery.limit = +nameNode!.children![0].text!;
|
||||
}
|
||||
|
||||
let renderNode = findNodeOfType(queryNode, "RenderClause");
|
||||
const renderNode = findNodeOfType(queryNode, "RenderClause");
|
||||
if (renderNode) {
|
||||
let renderNameNode = findNodeOfType(renderNode, "PageRef");
|
||||
if (!renderNameNode) {
|
||||
@@ -91,6 +73,5 @@ export function parseQuery(query: string): ParsedQuery {
|
||||
parsedQuery.render = valueNodeToVal(renderNameNode!);
|
||||
}
|
||||
|
||||
// console.log(JSON.stringify(queryNode, null, 2));
|
||||
return parsedQuery;
|
||||
}
|
||||
|
||||
+3
-51
@@ -1,58 +1,10 @@
|
||||
import {
|
||||
addParentPointers,
|
||||
collectNodesMatching,
|
||||
ParseTree,
|
||||
renderToText,
|
||||
} from "../../common/tree.ts";
|
||||
|
||||
export const queryRegex =
|
||||
/(<!--\s*#query\s+(.+?)-->)(.+?)(<!--\s*\/query\s*-->)/gs;
|
||||
|
||||
export const directiveStartRegex = /<!--\s*#([\w\-]+)\s+(.+?)-->/s;
|
||||
|
||||
export const directiveEndRegex = /<!--\s*\/([\w\-]+)\s*-->/s;
|
||||
|
||||
export function removeQueries(pt: ParseTree) {
|
||||
addParentPointers(pt);
|
||||
collectNodesMatching(pt, (t) => {
|
||||
if (t.type !== "CommentBlock") {
|
||||
return false;
|
||||
}
|
||||
let text = t.children![0].text!;
|
||||
let match = directiveStartRegex.exec(text);
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
let directiveType = match[1];
|
||||
let parentChildren = t.parent!.children!;
|
||||
let index = parentChildren.indexOf(t);
|
||||
let nodesToReplace: ParseTree[] = [];
|
||||
for (let i = index + 1; i < parentChildren.length; i++) {
|
||||
let n = parentChildren[i];
|
||||
if (n.type === "CommentBlock") {
|
||||
let text = n.children![0].text!;
|
||||
let match = directiveEndRegex.exec(text);
|
||||
if (match && match[1] === directiveType) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
nodesToReplace.push(n);
|
||||
}
|
||||
let renderedText = nodesToReplace.map(renderToText).join("");
|
||||
parentChildren.splice(index + 1, nodesToReplace.length, {
|
||||
text: new Array(renderedText.length + 1).join(" "),
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
const maxWidth = 70;
|
||||
// Nicely format an array of JSON objects as a Markdown table
|
||||
export function jsonToMDTable(
|
||||
jsonArray: any[],
|
||||
valueTransformer: (k: string, v: any) => string = (k, v) => "" + v,
|
||||
): string {
|
||||
let fieldWidths = new Map<string, number>();
|
||||
const fieldWidths = new Map<string, number>();
|
||||
for (let entry of jsonArray) {
|
||||
for (let k of Object.keys(entry)) {
|
||||
let fieldWidth = fieldWidths.get(k);
|
||||
@@ -70,8 +22,8 @@ export function jsonToMDTable(
|
||||
fullWidth += v + 1;
|
||||
}
|
||||
|
||||
let headerList = [...fieldWidths.keys()];
|
||||
let lines = [];
|
||||
const headerList = [...fieldWidths.keys()];
|
||||
const lines = [];
|
||||
lines.push(
|
||||
"|" +
|
||||
headerList
|
||||
|
||||
Reference in New Issue
Block a user