pull/679/head
Zef Hemel 2024-02-03 15:28:24 +01:00
parent 9bb72d01b2
commit d0ebda0d7a
6 changed files with 144 additions and 35 deletions

View File

@ -135,11 +135,12 @@ const TemplateDirective: MarkdownConfig = {
{ name: "TemplateExpressionDirective" },
{ name: "TemplateIfStartDirective", style: ct.DirectiveTag },
{ name: "TemplateEachStartDirective", style: ct.DirectiveTag },
{ name: "TemplateEachVarStartDirective", style: ct.DirectiveTag },
{ name: "TemplateLetStartDirective", style: ct.DirectiveTag },
{ name: "TemplateIfEndDirective", style: ct.DirectiveTag },
{ name: "TemplateEachEndDirective", style: ct.DirectiveTag },
{ name: "TemplateLetEndDirective", style: ct.DirectiveTag },
{ name: "TemplateLetVar", style: t.variableName },
{ name: "TemplateVar", style: t.variableName },
{ name: "TemplateDirectiveMark", style: ct.DirectiveMarkTag },
],
parseInline: [
@ -182,6 +183,64 @@ const TemplateDirective: MarkdownConfig = {
const endPos = pos + valueLength + 1;
let bodyEl: any;
// Is this an let block directive?
const openLetBlockMatch = /^(\s*#let\s*)(@\w+)(\s*=\s*)(.+)$/s.exec(
bodyText,
);
if (openLetBlockMatch) {
const [_, directiveStart, varName, eq, expr] = openLetBlockMatch;
const parsedExpression = highlightingExpressionParser.parse(
expr,
);
bodyEl = cx.elt(
"TemplateLetStartDirective",
pos + 2,
endPos - 2,
[
cx.elt(
"TemplateVar",
pos + 2 + directiveStart.length,
pos + 2 + directiveStart.length + varName.length,
),
cx.elt(
parsedExpression,
pos + 2 + directiveStart.length + varName.length + eq.length,
),
],
);
}
if (!bodyEl) {
// Is this an #each @p = block directive?
const openEachVariableBlockMatch =
/^(\s*#each\s*)(@\w+)(\s*in\s*)(.+)$/s.exec(
bodyText,
);
if (openEachVariableBlockMatch) {
const [_, directiveStart, varName, eq, expr] =
openEachVariableBlockMatch;
const parsedExpression = highlightingExpressionParser.parse(
expr,
);
bodyEl = cx.elt(
"TemplateEachVarStartDirective",
pos + 2,
endPos - 2,
[
cx.elt(
"TemplateVar",
pos + 2 + directiveStart.length,
pos + 2 + directiveStart.length + varName.length,
),
cx.elt(
parsedExpression,
pos + 2 + directiveStart.length + varName.length + eq.length,
),
],
);
}
}
if (!bodyEl) {
// Is this an open block directive?
const openBlockMatch = /^(\s*#(if|each)\s*)(.+)$/s.exec(bodyText);
if (openBlockMatch) {
@ -199,34 +258,6 @@ const TemplateDirective: MarkdownConfig = {
[cx.elt(parsedExpression, pos + 2 + directiveStart.length)],
);
}
if (!bodyEl) {
// Is this an open block directive?
const openLetBlockMatch = /^(\s*#let\s*)(@\w+)(\s*=\s*)(.+)$/s.exec(
bodyText,
);
if (openLetBlockMatch) {
const [_, directiveStart, varName, eq, expr] = openLetBlockMatch;
const parsedExpression = highlightingExpressionParser.parse(
expr,
);
bodyEl = cx.elt(
"TemplateLetStartDirective",
pos + 2,
endPos - 2,
[
cx.elt(
"TemplateLetVar",
pos + 2 + directiveStart.length,
pos + 2 + directiveStart.length + varName.length,
),
cx.elt(
parsedExpression,
pos + 2 + directiveStart.length + varName.length + eq.length,
),
],
);
}
}
if (!bodyEl) {

View File

@ -135,6 +135,14 @@ Deno.test("Test template", async () => {
"1\n2\n3\n",
);
assertEquals(
await parseAndRender(
"{{#each @v in [1, 2, 3]}}\n{{@v}}\n{{/each}}",
true,
),
"1\n2\n3\n",
);
function parseAndRender(template: string, value: any): Promise<string> {
const parsedTemplate = parseTemplate(template);
return renderTemplate(parsedTemplate, value, variables, functionMap);

View File

@ -2,10 +2,7 @@ import { AST } from "$sb/lib/tree.ts";
import { evalQueryExpression } from "$sb/lib/query_expression.ts";
import { expressionToKvQueryExpression } from "$sb/lib/parse-query.ts";
import { FunctionMap } from "$sb/types.ts";
import {
jsonObjectToMDTable,
jsonToMDTable,
} from "../../plugs/template/util.ts";
import { jsonToMDTable } from "../../plugs/template/util.ts";
export async function renderTemplate(
ast: AST,
@ -50,6 +47,13 @@ async function renderTemplateElement(
variables,
functionMap,
);
case "EachVarDirective":
return await renderEachVarDirective(
ast,
value,
variables,
functionMap,
);
case "IfDirective":
return await renderIfDirective(ast, value, variables, functionMap);
case "LetDirective":
@ -101,6 +105,48 @@ async function renderExpressionDirective(
}
}
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[],
@ -182,11 +228,11 @@ async function renderLetDirective(
variables,
functionMap,
);
const newGlobalVariables = { ...variables, [name as any]: val };
const newVariables = { ...variables, [name as any]: val };
return await renderTemplate(
template,
value,
newGlobalVariables,
newVariables,
functionMap,
);
}

View File

@ -75,6 +75,10 @@ function processTree(tree: AST): AST {
const body = bodyElements.map(processTree);
switch (blockType) {
case "each": {
const eachExpr = blockTextContent.trim();
const eachVarMatch = eachExpr.match(/@(\w+)\s*in\s*(.+)$/s);
if (!eachVarMatch) {
// Not a each var declaration, just an expression
const expressionTree = parseTreeToAST(parse(
expressionLanguage,
blockTextContent.trim(),
@ -85,6 +89,18 @@ function processTree(tree: AST): AST {
...stripInitialNewline(body),
]];
}
// This is a #each @p = version
const expressionTree = parseTreeToAST(parse(
expressionLanguage,
eachVarMatch[2],
));
return [
"EachVarDirective",
eachVarMatch[1],
expressionTree[1],
["Template", ...stripInitialNewline(body)],
];
}
case "let": {
const letExpr = blockTextContent.trim();
const letMatch = letExpr.match(/@(\w+)\s*=\s*(.+)$/s);
@ -156,7 +172,8 @@ function stripInitialNewline(body: any[]) {
// After each block directive, strip the next newline
if (
["IfDirective", "EachDirective", "LetDirective"].includes(el[1][0])
["IfDirective", "EachDirective", "EachVarDirective", "LetDirective"]
.includes(el[1][0])
) {
// console.log("Got a block directive, consider stripping the next one", el);
stripNext = true;

View File

@ -4,7 +4,6 @@ import { events } from "$sb/syscalls.ts";
import { QueryProviderEvent } from "$sb/app_event.ts";
import { resolvePath } from "$sb/lib/resolve.ts";
import { renderQueryTemplate } from "../template/util.ts";
import { parse } from "../../common/markdown_parser/parse_tree.ts";
export async function query(
query: string,

View File

@ -60,14 +60,22 @@ You can also add an optional `else` clause:
```
# each directive
To iterate over a collection use an `#each` directive. On each iteration, the current item that is iterated over will be set as the active object (accessible via `.` and its attributes via the `attribute` syntax):
To iterate over a collection use an `#each` directive. There are two variants of `#each`, one with and one without variable assignment:
* `#each @varname in expression` repeats the body of this directive assigning every value to `@varname` one by one
* `#each expression` repeats the body of this directive assigning every value to `.` one by one.
```template
Counting to 3:
Counting to 3 with a variable name:
{{#each [1, 2, 3]}}
* {{.}}
{{/each}}
And using a variable name iterator:
{{#each @v in [1, 2, 3]}}
* {{@v}}
{{/each}}
Iterating over the three last modified pages:
{{#each {page order by lastModified desc limit 3}}}
* {{name}}