242 lines
6.1 KiB
TypeScript
242 lines
6.1 KiB
TypeScript
import type { AST } from "../../plug-api/lib/tree.ts";
|
|
import { evalQueryExpression } from "@silverbulletmd/silverbullet/lib/query_expression";
|
|
import { expressionToKvQueryExpression } from "../../plug-api/lib/parse_query.ts";
|
|
import type { FunctionMap } from "../../plug-api/types.ts";
|
|
import { jsonToMDTable } from "../../plugs/template/util.ts";
|
|
|
|
export async function renderTemplate(
|
|
ast: AST,
|
|
value: any,
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
): Promise<string> {
|
|
const [_, ...elements] = ast;
|
|
const renderedElements = await Promise.all(
|
|
elements.map((e) =>
|
|
renderTemplateElement(e, value, variables, functionMap)
|
|
),
|
|
);
|
|
return renderedElements.join("");
|
|
}
|
|
|
|
async function renderTemplateElement(
|
|
ast: AST,
|
|
value: any,
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
): Promise<string> {
|
|
const [type, ...children] = ast;
|
|
switch (type) {
|
|
case "TemplateElement":
|
|
return (await Promise.all(
|
|
children.map((c) =>
|
|
renderTemplateElement(c, value, variables, functionMap)
|
|
),
|
|
)).join("");
|
|
case "ExpressionDirective":
|
|
return await renderExpressionDirective(
|
|
ast,
|
|
value,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
case "EachDirective":
|
|
return await renderEachDirective(
|
|
ast,
|
|
value,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
case "EachVarDirective":
|
|
return await renderEachVarDirective(
|
|
ast,
|
|
value,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
case "IfDirective":
|
|
return await renderIfDirective(ast, value, variables, functionMap);
|
|
case "LetDirective":
|
|
return await renderLetDirective(ast, value, variables, functionMap);
|
|
case "Text":
|
|
return children[0] as string;
|
|
default:
|
|
throw new Error(`Unknown template element type ${type}`);
|
|
}
|
|
}
|
|
|
|
async function renderExpressionDirective(
|
|
ast: AST,
|
|
value: any,
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
): Promise<string> {
|
|
const [_, expression] = ast;
|
|
const expr = expressionToKvQueryExpression(expression);
|
|
const result = await evalQueryExpression(
|
|
expr,
|
|
value,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
return renderExpressionResult(result);
|
|
}
|
|
|
|
export function renderExpressionResult(result: any): string {
|
|
if (
|
|
Array.isArray(result) && result.length > 0 && typeof result[0] === "object"
|
|
) {
|
|
// If result is an array of objects, render as a markdown table
|
|
try {
|
|
return jsonToMDTable(result);
|
|
} catch (e: any) {
|
|
console.error(
|
|
`Error rendering expression directive: ${e.message} for value ${
|
|
JSON.stringify(result)
|
|
}`,
|
|
);
|
|
return JSON.stringify(result);
|
|
}
|
|
} else if (typeof result === "object" && result.constructor === Object) {
|
|
// if result is a plain object, render as a markdown table
|
|
return jsonToMDTable([result]);
|
|
} else if (Array.isArray(result)) {
|
|
// Not-object array
|
|
return JSON.stringify(result);
|
|
} else {
|
|
return "" + result;
|
|
}
|
|
}
|
|
|
|
async function renderEachVarDirective(
|
|
ast: AST,
|
|
value: any[],
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
): Promise<string> {
|
|
const [_eachVarDirective, name, expression, template] = ast;
|
|
const expr = expressionToKvQueryExpression(expression);
|
|
const values = await evalQueryExpression(
|
|
expr,
|
|
value,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
if (!Array.isArray(values)) {
|
|
throw new Error(
|
|
`Expecting a list expression for #each var directive, instead got ${values}`,
|
|
);
|
|
}
|
|
const resultPieces: string[] = [];
|
|
for (const itemValue of values) {
|
|
const localVariables = { ...variables, [name as any]: itemValue };
|
|
try {
|
|
resultPieces.push(
|
|
await renderTemplate(
|
|
template,
|
|
value,
|
|
localVariables,
|
|
functionMap,
|
|
),
|
|
);
|
|
} catch (e: any) {
|
|
throw new Error(
|
|
`Error rendering #each directive: ${e.message} for item ${
|
|
JSON.stringify(itemValue)
|
|
}`,
|
|
);
|
|
}
|
|
}
|
|
return resultPieces.join("");
|
|
}
|
|
|
|
async function renderEachDirective(
|
|
ast: AST,
|
|
value: any[],
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
): Promise<string> {
|
|
const [_eachDirective, expression, template] = ast;
|
|
const expr = expressionToKvQueryExpression(expression);
|
|
const values = await evalQueryExpression(
|
|
expr,
|
|
value,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
if (!Array.isArray(values)) {
|
|
throw new Error(
|
|
`Expecting a list expression for #each directive, instead got ${values}`,
|
|
);
|
|
}
|
|
const resultPieces: string[] = [];
|
|
for (const itemValue of values) {
|
|
try {
|
|
resultPieces.push(
|
|
await renderTemplate(
|
|
template,
|
|
itemValue,
|
|
variables,
|
|
functionMap,
|
|
),
|
|
);
|
|
} catch (e: any) {
|
|
throw new Error(
|
|
`Error rendering #each directive: ${e.message} for item ${
|
|
JSON.stringify(itemValue)
|
|
}`,
|
|
);
|
|
}
|
|
}
|
|
return resultPieces.join("");
|
|
}
|
|
|
|
async function renderIfDirective(
|
|
ast: AST,
|
|
value: any,
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
) {
|
|
const [_, expression, trueTemplate, falseTemplate] = ast;
|
|
const expr = expressionToKvQueryExpression(expression);
|
|
const condVal = await evalQueryExpression(
|
|
expr,
|
|
value,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
if (
|
|
!Array.isArray(condVal) && condVal ||
|
|
(Array.isArray(condVal) && condVal.length > 0)
|
|
) {
|
|
return renderTemplate(trueTemplate, value, variables, functionMap);
|
|
} else {
|
|
return falseTemplate
|
|
? renderTemplate(falseTemplate, value, variables, functionMap)
|
|
: "";
|
|
}
|
|
}
|
|
|
|
async function renderLetDirective(
|
|
ast: AST,
|
|
value: any,
|
|
variables: Record<string, any>,
|
|
functionMap: FunctionMap,
|
|
) {
|
|
const [_letDirective, name, expression, template] = ast;
|
|
const expr = expressionToKvQueryExpression(expression);
|
|
const val = await evalQueryExpression(
|
|
expr,
|
|
value,
|
|
variables,
|
|
functionMap,
|
|
);
|
|
const newVariables = { ...variables, [name as any]: val };
|
|
return await renderTemplate(
|
|
template,
|
|
value,
|
|
newVariables,
|
|
functionMap,
|
|
);
|
|
}
|