Skip to content

Commit 072e4fd

Browse files
authored
Implicit default block for {embed} (#419)
1 parent e163c57 commit 072e4fd

3 files changed

Lines changed: 253 additions & 10 deletions

File tree

src/Latte/Essential/Nodes/EmbedNode.php

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@
88
namespace Latte\Essential\Nodes;
99

1010
use Latte\CompileException;
11+
use Latte\Compiler\Block;
1112
use Latte\Compiler\Nodes\FragmentNode;
1213
use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
1314
use Latte\Compiler\Nodes\Php\ExpressionNode;
15+
use Latte\Compiler\Nodes\Php\ModifierNode;
1416
use Latte\Compiler\Nodes\Php\Scalar\StringNode;
1517
use Latte\Compiler\Nodes\StatementNode;
1618
use Latte\Compiler\Nodes\TextNode;
1719
use Latte\Compiler\PrintContext;
1820
use Latte\Compiler\Tag;
1921
use Latte\Compiler\TemplateParser;
20-
use function count, preg_match;
22+
use Latte\Compiler\Token;
23+
use function array_pop, array_shift, count, end, preg_match, trim;
2124

2225

2326
/**
@@ -54,10 +57,42 @@ public static function create(Tag $tag, TemplateParser $parser): \Generator
5457
$parser->blocks[$parser->blockLayer] = [];
5558
[$node->blocks] = yield;
5659

60+
// Content not wrapped in a {block} becomes the implicit {block default} block.
61+
$kept = $default = [];
5762
foreach ($node->blocks->children as $child) {
58-
if (!$child instanceof ImportNode && !$child instanceof BlockNode && !$child instanceof TextNode) {
59-
throw new CompileException('Unexpected content inside {embed} tags.', $child->position);
63+
if ($child instanceof ImportNode || $child instanceof BlockNode) {
64+
$kept[] = $child;
65+
} else {
66+
$default[] = $child;
67+
}
68+
}
69+
70+
// ignore whitespace-only text surrounding the blocks, but keep it in between
71+
while ($default && $default[0] instanceof TextNode && trim($default[0]->content) === '') {
72+
array_shift($default);
73+
}
74+
while ($default && ($last = end($default)) instanceof TextNode && trim($last->content) === '') {
75+
array_pop($default);
76+
}
77+
78+
if ($default) {
79+
if (isset($parser->blocks[$node->layer]['default'])) {
80+
throw new CompileException(
81+
'Cannot combine loose content with an explicit {block default} inside {embed}; both define the default block.',
82+
$default[0]->position ?? $tag->position,
83+
);
6084
}
85+
86+
$blockTag = new Tag('block', [new Token(Token::End, '', $tag->position)], $tag->position, prefix: $tag->prefix);
87+
$block = new Block(new StringNode('default'), $node->layer, $blockTag);
88+
$parser->checkBlockIsUnique($block);
89+
$blockNode = new BlockNode;
90+
$blockNode->block = $block;
91+
$blockNode->modifier = new ModifierNode([], position: $tag->position);
92+
$blockNode->content = new FragmentNode($default);
93+
$blockNode->position = $tag->position;
94+
$kept[] = $blockNode;
95+
$node->blocks->children = $kept;
6196
}
6297

6398
$parser->blockLayer = $prevIndex;

tests/tags/embed.block.phpt

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,83 @@ testTemplate(
424424
);
425425

426426

427+
testTemplate(
428+
'default block: works together with named blocks',
429+
[
430+
'main' => <<<'XX'
431+
432+
{embed embed}custom body{block title}custom title{/block}{/embed}
433+
434+
{define embed}title={block title}fallback title{/block} body={block default}fallback body{/block}{/define}
435+
436+
XX,
437+
],
438+
<<<'XX'
439+
440+
title=custom title body=custom body
441+
442+
XX,
443+
);
444+
445+
446+
testTemplate(
447+
'default block: self-closing caller keeps the fallback',
448+
[
449+
'main' => <<<'XX'
450+
451+
{embed embed/}
452+
453+
{define embed}body={block default}fallback body{/block}{/define}
454+
455+
XX,
456+
],
457+
<<<'XX'
458+
459+
body=fallback body
460+
461+
XX,
462+
);
463+
464+
465+
testTemplate(
466+
'default block: empty caller keeps the fallback',
467+
[
468+
'main' => <<<'XX'
469+
470+
{embed embed}{/embed}
471+
472+
{define embed}body={block default}fallback body{/block}{/define}
473+
474+
XX,
475+
],
476+
<<<'XX'
477+
478+
body=fallback body
479+
480+
XX,
481+
);
482+
483+
484+
testTemplate(
485+
'default block: default content sees caller variables',
486+
[
487+
'main' => <<<'XX'
488+
489+
{var $greeting = 'Hello'}
490+
{embed embed}{var $name = 'world'}{$greeting} {$name}{if true}!{/if}{/embed}
491+
492+
{define embed}body={block default}fallback body{/block}{/define}
493+
494+
XX,
495+
],
496+
<<<'XX'
497+
498+
body=Hello world!
499+
500+
XX,
501+
);
502+
503+
427504
$latte = createLatte();
428505
Assert::exception(
429506
fn() => $latte->renderToString('{embed block (null)/}'),

tests/tags/embed.file.phpt

Lines changed: 138 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,6 @@ function testTemplate(string $title, array $templates, string $exp = '')
1717
}
1818

1919

20-
Assert::exception(function () {
21-
testTemplate('unexpected content', [
22-
'main' => '{embed "embed.latte"} {$a} {/embed}',
23-
]);
24-
}, Latte\CompileException::class, 'Unexpected content inside {embed} tags (on line 1 at column 23)');
25-
26-
2720
testTemplate('keyword file', [
2821
'main' => '{embed file embed}{/embed}',
2922
'embed' => 'embed',
@@ -919,6 +912,144 @@ testTemplate(
919912
);
920913

921914

915+
testTemplate(
916+
'default block: caller content overrides the fallback',
917+
[
918+
'main' => <<<'XX'
919+
{embed "embed.latte"}custom body{/embed}
920+
XX,
921+
'embed.latte' => <<<'XX'
922+
start
923+
{block default}fallback body{/block}
924+
end
925+
XX,
926+
],
927+
<<<'XX'
928+
start
929+
custom body
930+
end
931+
XX,
932+
);
933+
934+
935+
testTemplate(
936+
'default block: empty caller keeps the fallback',
937+
[
938+
'main' => <<<'XX'
939+
{embed "embed.latte"}{/embed}
940+
XX,
941+
'embed.latte' => <<<'XX'
942+
start
943+
{block default}fallback body{/block}
944+
end
945+
XX,
946+
],
947+
<<<'XX'
948+
start
949+
fallback body
950+
end
951+
XX,
952+
);
953+
954+
955+
testTemplate(
956+
'default block: self-closing caller keeps the fallback',
957+
[
958+
'main' => <<<'XX'
959+
{embed "embed.latte"/}
960+
XX,
961+
'embed.latte' => <<<'XX'
962+
start
963+
{block default}fallback body{/block}
964+
end
965+
XX,
966+
],
967+
<<<'XX'
968+
start
969+
fallback body
970+
end
971+
XX,
972+
);
973+
974+
975+
testTemplate(
976+
'default block: whitespace-only caller keeps the fallback',
977+
[
978+
'main' => <<<'XX'
979+
{embed "embed.latte"} {/embed}
980+
XX,
981+
'embed.latte' => <<<'XX'
982+
start
983+
{block default}fallback body{/block}
984+
end
985+
XX,
986+
],
987+
<<<'XX'
988+
start
989+
fallback body
990+
end
991+
XX,
992+
);
993+
994+
995+
testTemplate(
996+
'default block: default content sees caller variables',
997+
[
998+
'main' => <<<'XX'
999+
{var $greeting = 'Hello'}{embed "embed.latte"}{var $name = 'world'}{$greeting} {$name}{if true}!{/if}{/embed}
1000+
XX,
1001+
'embed.latte' => <<<'XX'
1002+
start
1003+
{block default}fallback body{/block}
1004+
end
1005+
XX,
1006+
],
1007+
<<<'XX'
1008+
start
1009+
Hello world!
1010+
end
1011+
XX,
1012+
);
1013+
1014+
1015+
testTemplate(
1016+
'default block: works together with named blocks',
1017+
[
1018+
'main' => <<<'XX'
1019+
{embed "embed.latte"}custom body{block title}custom title{/block}{/embed}
1020+
XX,
1021+
'embed.latte' => <<<'XX'
1022+
title={block title}fallback title{/block}
1023+
body={block default}fallback body{/block}
1024+
XX,
1025+
],
1026+
<<<'XX'
1027+
title=custom title
1028+
body=custom body
1029+
XX,
1030+
);
1031+
1032+
1033+
testTemplate(
1034+
'default block: caller content ignored without a placeholder',
1035+
[
1036+
'main' => <<<'XX'
1037+
{embed "embed.latte"}custom body{/embed}
1038+
XX,
1039+
'embed.latte' => 'no placeholder here',
1040+
],
1041+
'no placeholder here',
1042+
);
1043+
1044+
1045+
Assert::exception(function () {
1046+
testTemplate('default block: loose content mixed with explicit block', [
1047+
'main' => '{embed "embed.latte"}custom body{block default}explicit body{/block}{/embed}',
1048+
'embed.latte' => '[{block default}x{/block}]',
1049+
]);
1050+
}, Latte\CompileException::class, 'Cannot combine loose content with an explicit {block default} inside {embed}; both define the default block (on line 1 at column 22)');
1051+
1052+
9221053
$latte = createLatte();
9231054
Assert::exception(
9241055
fn() => $latte->renderToString('{embed (null)/}'),

0 commit comments

Comments
 (0)