2022-07-06 10:18:47 +00:00
|
|
|
import { collectNodesOfType, ParseTree } from "@silverbulletmd/common/tree";
|
2022-04-28 09:55:38 +00:00
|
|
|
import Handlebars from "handlebars";
|
|
|
|
import YAML from "yaml";
|
2022-04-11 18:34:09 +00:00
|
|
|
|
2022-04-28 09:55:38 +00:00
|
|
|
import { readPage } from "@silverbulletmd/plugos-silverbullet-syscall/space";
|
|
|
|
import { niceDate } from "../core/dates";
|
2022-07-06 10:18:47 +00:00
|
|
|
import { ParsedQuery } from "./parser";
|
2022-04-11 18:34:09 +00:00
|
|
|
|
2022-04-19 14:54:47 +00:00
|
|
|
export type QueryProviderEvent = {
|
|
|
|
query: ParsedQuery;
|
|
|
|
pageName: string;
|
|
|
|
};
|
|
|
|
|
2022-07-06 10:18:47 +00:00
|
|
|
export function valueNodeToVal(valNode: ParseTree): any {
|
2022-04-28 09:55:38 +00:00
|
|
|
switch (valNode.type) {
|
|
|
|
case "Number":
|
|
|
|
return +valNode.children![0].text!;
|
|
|
|
case "Bool":
|
|
|
|
return valNode.children![0].text! === "true";
|
|
|
|
case "Null":
|
|
|
|
return null;
|
|
|
|
case "Name":
|
|
|
|
return valNode.children![0].text!;
|
|
|
|
case "Regex":
|
|
|
|
let val = valNode.children![0].text!;
|
|
|
|
return val.substring(1, val.length - 1);
|
|
|
|
case "String":
|
|
|
|
let stringVal = valNode.children![0].text!;
|
|
|
|
return stringVal.substring(1, stringVal.length - 1);
|
|
|
|
case "List":
|
|
|
|
return collectNodesOfType(valNode, "Value").map((t) =>
|
|
|
|
valueNodeToVal(t.children![0])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-12 11:33:07 +00:00
|
|
|
export function applyQuery<T>(parsedQuery: ParsedQuery, records: T[]): T[] {
|
2022-04-11 18:34:09 +00:00
|
|
|
let resultRecords: any[] = [];
|
|
|
|
if (parsedQuery.filter.length === 0) {
|
|
|
|
resultRecords = records.slice();
|
|
|
|
} else {
|
|
|
|
recordLoop: for (let record of records) {
|
2022-04-12 11:33:07 +00:00
|
|
|
const recordAny: any = record;
|
2022-04-11 18:34:09 +00:00
|
|
|
for (let { op, prop, value } of parsedQuery.filter) {
|
|
|
|
switch (op) {
|
|
|
|
case "=":
|
2022-07-04 09:31:39 +00:00
|
|
|
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
|
2022-04-11 18:34:09 +00:00
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "!=":
|
2022-04-13 12:46:52 +00:00
|
|
|
if (!(recordAny[prop] != value)) {
|
2022-04-11 18:34:09 +00:00
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "<":
|
2022-04-12 11:33:07 +00:00
|
|
|
if (!(recordAny[prop] < value)) {
|
2022-04-11 18:34:09 +00:00
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "<=":
|
2022-04-12 11:33:07 +00:00
|
|
|
if (!(recordAny[prop] <= value)) {
|
2022-04-11 18:34:09 +00:00
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ">":
|
2022-04-12 11:33:07 +00:00
|
|
|
if (!(recordAny[prop] > value)) {
|
2022-04-11 18:34:09 +00:00
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ">=":
|
2022-04-12 11:33:07 +00:00
|
|
|
if (!(recordAny[prop] >= value)) {
|
2022-04-11 18:34:09 +00:00
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
2022-04-12 11:33:07 +00:00
|
|
|
case "=~":
|
2022-04-19 14:54:47 +00:00
|
|
|
// TODO: Cache regexps somehow
|
|
|
|
if (!new RegExp(value).exec(recordAny[prop])) {
|
2022-04-11 18:34:09 +00:00
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
2022-04-13 12:46:52 +00:00
|
|
|
case "!=~":
|
2022-04-19 14:54:47 +00:00
|
|
|
if (new RegExp(value).exec(recordAny[prop])) {
|
2022-04-13 12:46:52 +00:00
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
2022-04-28 09:55:38 +00:00
|
|
|
case "in":
|
|
|
|
if (!value.includes(recordAny[prop])) {
|
|
|
|
continue recordLoop;
|
|
|
|
}
|
|
|
|
break;
|
2022-04-11 18:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-12 11:33:07 +00:00
|
|
|
resultRecords.push(recordAny);
|
2022-04-11 18:34:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// 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);
|
|
|
|
}
|
2022-04-21 09:46:33 +00:00
|
|
|
if (parsedQuery.select) {
|
|
|
|
resultRecords = resultRecords.map((rec) => {
|
|
|
|
let newRec: any = {};
|
|
|
|
for (let k of parsedQuery.select!) {
|
|
|
|
newRec[k] = rec[k];
|
|
|
|
}
|
|
|
|
return newRec;
|
|
|
|
});
|
|
|
|
}
|
2022-04-11 18:34:09 +00:00
|
|
|
return resultRecords;
|
|
|
|
}
|
2022-04-28 09:55:38 +00:00
|
|
|
|
|
|
|
export async function renderQuery(
|
|
|
|
parsedQuery: ParsedQuery,
|
|
|
|
data: any[]
|
|
|
|
): Promise<string> {
|
|
|
|
if (parsedQuery.render) {
|
|
|
|
Handlebars.registerHelper("json", (v) => JSON.stringify(v));
|
|
|
|
Handlebars.registerHelper("niceDate", (ts) => niceDate(new Date(ts)));
|
2022-07-18 14:45:04 +00:00
|
|
|
Handlebars.registerHelper("prefixLines", (v: string, prefix) =>
|
|
|
|
v
|
|
|
|
.split("\n")
|
|
|
|
.map((l) => prefix + l)
|
|
|
|
.join("\n")
|
|
|
|
);
|
|
|
|
|
|
|
|
Handlebars.registerHelper("substring", (s, from, to, elipsis = "") =>
|
|
|
|
s.length > to - from ? s.substring(from, to) + elipsis : s
|
|
|
|
);
|
|
|
|
|
2022-04-28 09:55:38 +00:00
|
|
|
Handlebars.registerHelper("yaml", (v, prefix) => {
|
|
|
|
if (typeof prefix === "string") {
|
|
|
|
let yaml = YAML.stringify(v)
|
|
|
|
.split("\n")
|
|
|
|
.join("\n" + prefix)
|
|
|
|
.trim();
|
|
|
|
if (Array.isArray(v)) {
|
|
|
|
return "\n" + prefix + yaml;
|
|
|
|
} else {
|
|
|
|
return yaml;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return YAML.stringify(v).trim();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let { text: templateText } = await readPage(parsedQuery.render);
|
2022-07-01 13:40:01 +00:00
|
|
|
templateText = `{{#each .}}\n${templateText}\n{{/each}}`;
|
2022-04-28 09:55:38 +00:00
|
|
|
let template = Handlebars.compile(templateText, { noEscape: true });
|
|
|
|
return template(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return "ERROR";
|
|
|
|
}
|