diff --git a/plugins/include.ts b/plugins/include.ts index 06d3d3c..ab0cc28 100644 --- a/plugins/include.ts +++ b/plugins/include.ts @@ -9,6 +9,8 @@ export default function (): Plugin { }; } +const DIRECT_DATA = /["'`\w]\s+([a-z_$][^\s'"`]*)$/i; + function includeTag( env: Environment, token: Token, @@ -37,6 +39,13 @@ function includeTag( data = tagCode.slice(bracketIndex).trim(); } + // Includes data directly (e.g. {{ include "template.vto" data }}) + const directDataMatch = tagCode.match(DIRECT_DATA); + if (directDataMatch) { + data = directDataMatch[1]; + file = tagCode.slice(0, -data.length).trim(); + } + const { dataVarname } = env.options; const tmp = env.getTempVariable(); return `{ diff --git a/plugins/layout.ts b/plugins/layout.ts index 99e1e17..accbf38 100644 --- a/plugins/layout.ts +++ b/plugins/layout.ts @@ -1,4 +1,5 @@ import { SourceError } from "../core/errors.ts"; +import iterateTopLevel from "../core/js.ts"; import type { Token } from "../core/tokenizer.ts"; import type { Environment, Plugin } from "../core/environment.ts"; @@ -9,8 +10,8 @@ export default function (): Plugin { }; } -const LAYOUT_TAG = /^layout\s+([^{]+|`[^`]+`)+(?:\{([^]*)\})?$/; const SLOT_NAME = /^[a-z_]\w*$/i; +const DIRECT_DATA = /["'`\w]\s+([a-z_$][^\s'"`]*)$/i; function layoutTag( env: Environment, @@ -24,12 +25,29 @@ function layoutTag( return; } - const match = code?.match(LAYOUT_TAG); - if (!match) { - throw new SourceError("Invalid layout tag", position); + const tagCode = code.slice(6).trim(); + let file = tagCode; + let data = ""; + + // Includes { data } + if (tagCode.endsWith("}")) { + let bracketIndex = -1; + for (const [index, reason] of iterateTopLevel(tagCode)) { + if (reason == "{") bracketIndex = index; + } + if (bracketIndex == -1) { + throw new SourceError("Invalid layout tag", position); + } + file = tagCode.slice(0, bracketIndex).trim(); + data = tagCode.slice(bracketIndex).trim(); } - const [_, file, data] = match; + // Includes data directly (e.g. {{ layout "template.vto" data }}) + const directDataMatch = tagCode.match(DIRECT_DATA); + if (directDataMatch) { + data = directDataMatch[1]; + file = tagCode.slice(0, -data.length).trim(); + } const compiledFilters = env.compileFilters(tokens, "__slots.content"); const { dataVarname } = env.options; @@ -40,7 +58,7 @@ function layoutTag( return __env.run(${file}, { ...${dataVarname}, ...__slots, - ${data ?? ""} + ${data ? "..." + data : ""} }, __template.path, ${position}); })()).content;`; } diff --git a/test/include.test.ts b/test/include.test.ts index 04b0f70..c4abf59 100644 --- a/test/include.test.ts +++ b/test/include.test.ts @@ -102,6 +102,35 @@ Deno.test("Include tag (with data)", async () => { name: "world", }, }); + + await test({ + template: ` + {{ set data = { name } }} + {{ include "/my-file.vto" data }} + `, + expected: "Hello world", + includes: { + "/my-file.vto": "Hello {{ name }}", + }, + data: { + name: "world", + }, + }); + + await test({ + template: ` + {{ set data = { name } }} + {{ include "/my-file" + ext data }} + `, + expected: "Hello world", + includes: { + "/my-file.vto": "Hello {{ name }}", + }, + data: { + ext: ".vto", + name: "world", + }, + }); }); Deno.test("Include tag (with custom data)", async () => { diff --git a/test/layout.test.ts b/test/layout.test.ts index 276f5b7..3d6cba4 100644 --- a/test/layout.test.ts +++ b/test/layout.test.ts @@ -45,6 +45,29 @@ Deno.test("Layout tag (with extra data)", async () => { "/my-file.vto": "<{{ tag }}>{{ content }}{{ tag }}>", }, }); + await test({ + template: ` + {{ set data = { tag: "h1" } }} + {{ layout "/my-file.vto" data }}Hello world{{ /layout }} + `, + expected: "