Skip to content

Commit 8cab2ea

Browse files
authored
Add compose block (#50)
1 parent ef201ef commit 8cab2ea

10 files changed

+258
-22
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,43 @@ Then `a.tpl` renders like:
386386
this is hello the base world template
387387
```
388388

389+
#### Template compose
390+
391+
This includes a template, but also defines extra blocks for the template
392+
to overrule the blocks in the template.
393+
394+
It is like a nameless `{% overrules %}` template, directly defined in the
395+
template text at the spot of the include.
396+
397+
398+
```django
399+
{% compose "a.tpl" what="moon" %}
400+
{% block a %}{{ what }}{% endblock %}
401+
{% endcompose %}
402+
```
403+
404+
And a.tpl is like:
405+
406+
```django
407+
Hello {% block a %}world{% endblock %}, and bye.
408+
```
409+
410+
Then the above renders:
411+
412+
```
413+
Hello moon, and bye.
414+
```
415+
416+
There is also a `catcompose` to use a `catinclude`:
417+
418+
```django
419+
{% catcompose "a.tpl" id what="moon" %}
420+
{% block a %}{{ what }}{% endblock %}
421+
{% endcompose %}
422+
423+
```
424+
425+
389426
#### If tag
390427

391428
Conditionally show or hide parts of a template:

rebar.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
{<<"qdate_localtime">>,{pkg,<<"qdate_localtime">>,<<"1.2.0">>},0},
44
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},2},
55
{<<"tls_certificate_check">>,
6-
{pkg,<<"tls_certificate_check">>,<<"1.20.0">>},
6+
{pkg,<<"tls_certificate_check">>,<<"1.21.0">>},
77
1},
8-
{<<"zotonic_stdlib">>,{pkg,<<"zotonic_stdlib">>,<<"1.15.0">>},0}]}.
8+
{<<"zotonic_stdlib">>,{pkg,<<"zotonic_stdlib">>,<<"1.20.0">>},0}]}.
99
[
1010
{pkg_hash,[
1111
{<<"cowlib">>, <<"A9FA9A625F1D2025FE6B462CB865881329B5CAFF8F1854D1CBC9F9533F00E1E1">>},
1212
{<<"qdate_localtime">>, <<"644ADE4C7F7EAC765E2048DFA714D78EA86BAF5255FE46279B2EAC5729760A07">>},
1313
{<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>},
14-
{<<"tls_certificate_check">>, <<"1AC0C53F95E201FEB8D398EF9D764AE74175231289D89F166BA88A7F50CD8E73">>},
15-
{<<"zotonic_stdlib">>, <<"0876BB82B9CFDE331DC758C8A7818181BB5654F137723DF137DE53C25AD3DD1D">>}]},
14+
{<<"tls_certificate_check">>, <<"042AB2C0C860652BC5CF69C94E3A31F96676D14682E22EC7813BD173CEFF1788">>},
15+
{<<"zotonic_stdlib">>, <<"51A484CF4B692042A5A251510010E00FF419CFF6C85E094DD3E00E283817B53D">>}]},
1616
{pkg_hash_ext,[
1717
{<<"cowlib">>, <<"163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0">>},
1818
{<<"qdate_localtime">>, <<"98A538A5B6046B8652DFC5630B030D0414A1B31D0130C81FA6B88B5C1E625109">>},
1919
{<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>},
20-
{<<"tls_certificate_check">>, <<"AB57B74B1A63DC5775650699A3EC032EC0065005EFF1F020818742B7312A8426">>},
21-
{<<"zotonic_stdlib">>, <<"1864A96F2B5AE278DC52607F89CC90BF492713E7C3AC8EADDC1BA863E06E5921">>}]}
20+
{<<"tls_certificate_check">>, <<"6CEE6CFFC35A390840D48D463541D50746A7B0E421ACAADB833CFC7961E490E7">>},
21+
{<<"zotonic_stdlib">>, <<"C8651604BB9165EC4A6F9EAB1FB1A4D5BE5174C2E3D02815F76EB87CBBC495F2">>}]}
2222
].

src/template_compiler.erl

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
%% @author Marc Worrell <marc@worrell.nl>
2-
%% @copyright 2016-2023 Marc Worrell
2+
%% @copyright 2016-2024 Marc Worrell
33
%% @doc Main template compiler entry points.
44
%% @end
55

6-
%% Copyright 2016-2023 Marc Worrell
6+
%% Copyright 2016-2024 Marc Worrell
77
%%
88
%% Licensed under the Apache License, Version 2.0 (the "License");
99
%% you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222

2323
-export([
2424
render/4,
25+
render/5,
2526
render_block/5,
2627
lookup/3,
2728
flush/0,
@@ -31,7 +32,8 @@
3132
compile_binary/4,
3233
get_option/2,
3334
is_template_module/1,
34-
translations/1
35+
translations/1,
36+
compile_blocks/2
3537
]).
3638

3739
-include_lib("syntax_tools/include/merl.hrl").
@@ -92,12 +94,19 @@
9294
%% returns the rendering result.
9395
-spec render(Template :: template(), Vars :: map() | list(), Options :: options(), Context :: term()) ->
9496
{ok, render_result()} | {error, term()}.
95-
render(Template, Vars, Options, Context) when is_list(Vars) ->
96-
render(Template, props_to_map(Vars, #{}), Options, Context);
97-
render(Template0, Vars, Options, Context) when is_map(Vars) ->
97+
render(Template0, Vars, Options, Context) ->
98+
render(Template0, #{}, Vars, Options, Context).
99+
100+
%% @doc Render a template. This looks up the templates needed, ensures compilation and
101+
%% returns the rendering result. Start with a block-map to find some predefined blocks.
102+
-spec render(Template :: template(), BlockMap :: map(), Vars :: map() | list(), Options :: options(), Context :: term()) ->
103+
{ok, render_result()} | {error, term()}.
104+
render(Template0, BlockMap0, Vars, Options, Context) when is_list(Vars) ->
105+
render(Template0, BlockMap0, props_to_map(Vars, #{}), Options, Context);
106+
render(Template0, BlockMap0, Vars, Options, Context) when is_map(Vars) ->
98107
Template = normalize_template(Template0),
99108
Runtime = proplists:get_value(runtime, Options, template_compiler_runtime),
100-
case block_lookup(Runtime:map_template(Template, Vars, Context), #{}, [], [], Options, Vars, Runtime, Context) of
109+
case block_lookup(Runtime:map_template(Template, Vars, Context), BlockMap0, [], [], Options, Vars, Runtime, Context) of
101110
{ok, BaseModule, ExtendsStack, BlockMap, OptDebugWrap} ->
102111
% Start with the render function of the "base" template
103112
% Optionally add the unique prefix for this rendering.
@@ -468,7 +477,7 @@ split_loc({Filename, Line}) ->
468477
line => Line
469478
}.
470479

471-
-spec compile_blocks([block_element()], #cs{}) -> {#ws{}, [{atom(), erl_syntax:syntaxTree()}]}.
480+
-spec compile_blocks([block_element()], #cs{}) -> {#ws{}, [{atom(), erl_syntax:syntaxTree(), #ws{}}]}.
472481
compile_blocks(Blocks, CState) ->
473482
Ws = #ws{},
474483
lists:foldl(

src/template_compiler_element.erl

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,15 @@ compile({'call_with', {identifier, SrcPos, Name}, Expr}, CState, Ws) ->
213213
{context, erl_syntax:variable(CState#cs.context_var)}
214214
]),
215215
{Ws1, Ast};
216+
compile({'compose', {TagPos, Template, Args}, Blocks}, CState, Ws) ->
217+
{Ws1, ArgsList} = with_args(Args, CState, Ws, false),
218+
IsContextVar = is_context_vars_arg(Args, CState),
219+
compose(TagPos, Template, ArgsList, IsContextVar, Blocks, CState, Ws1);
220+
compile({'catcompose', {TagPos, Template, IdExpr, Args}, Blocks}, CState, Ws) ->
221+
{Ws1, ArgsList} = with_args(Args, CState, Ws, false),
222+
{Ws2, IdAst} = template_compiler_expr:compile(IdExpr, CState, Ws1),
223+
IsContextVar = is_context_vars_arg(Args, CState),
224+
catcompose(TagPos, Template, IdAst, ArgsList, IsContextVar, Blocks, CState, Ws2);
216225
compile({custom_tag, {identifier, SrcPos, Name}, Args}, #cs{runtime=Runtime} = CState, Ws) ->
217226
{Ws1, ArgsList} = with_args(Args, CState, Ws, true),
218227
TagName = template_compiler_utils:to_atom(Name),
@@ -722,6 +731,89 @@ maybe_add_include({string_literal, SrcPos, Text}, Method, IsCatinclude, Ws) ->
722731
maybe_add_include(_Token, _Method, _IsCatinclude, Ws) ->
723732
Ws.
724733

734+
735+
compose({_, SrcPos, _}, Template, ArgsList, IsContextVars, Blocks, #cs{runtime=Runtime} = CState, Ws) ->
736+
{Ws1, TemplateAst} = template_compiler_expr:compile(Template, CState, Ws),
737+
ArgsListAst = erl_syntax:list([ erl_syntax:tuple([A,B]) || {A,B} <- ArgsList ]),
738+
{_BlocksWs, BlocksAsts} = template_compiler:compile_blocks(Blocks, CState),
739+
BlockClauses = [
740+
?Q("(_@BlockName@, Vars, Blocks, Context) -> _@BlockAst")
741+
|| {BlockName, BlockAst, _BlockWs} <- BlocksAsts
742+
] ++ [
743+
?Q("(_BlockName, _Vars, _Blocks, _Context) -> <<>>")
744+
],
745+
BlockFunAst = erl_syntax:fun_expr(BlockClauses),
746+
BlockListAst = erl_syntax:abstract([ BlockName || {BlockName, _, _} <- BlocksAsts ]),
747+
Ast = ?Q([
748+
"template_compiler_runtime_internal:compose("
749+
"_@srcpos,"
750+
"_@template,"
751+
"_@args,"
752+
"_@runtime,"
753+
"_@context_vars,"
754+
"_@is_context_vars,"
755+
"_@vars,"
756+
"_@block_list,",
757+
"_@block_fun,",
758+
"_@context)"
759+
],
760+
[
761+
{srcpos, erl_syntax:abstract(SrcPos)},
762+
{template, TemplateAst},
763+
{args, ArgsListAst},
764+
{vars, erl_syntax:variable(CState#cs.vars_var)},
765+
{runtime, erl_syntax:atom(Runtime)},
766+
{context, erl_syntax:variable(CState#cs.context_var)},
767+
{context_vars, erl_syntax:abstract(CState#cs.context_vars)},
768+
{block_list, BlockListAst},
769+
{block_fun, BlockFunAst},
770+
{is_context_vars, erl_syntax:abstract(IsContextVars)}
771+
]),
772+
Ws2 = maybe_add_include(Template, undefined, false, Ws1),
773+
{Ws2, Ast}.
774+
775+
catcompose({_, SrcPos, _}, Template, IdAst, ArgsList, IsContextVars, Blocks, #cs{runtime=Runtime} = CState, Ws) ->
776+
{Ws1, TemplateAst} = template_compiler_expr:compile(Template, CState, Ws),
777+
ArgsList1 = [ {erl_syntax:atom('$cat'), IdAst} | ArgsList ],
778+
ArgsListAst = erl_syntax:list([ erl_syntax:tuple([A,B]) || {A,B} <- ArgsList1 ]),
779+
{_BlocksWs, BlocksAsts} = template_compiler:compile_blocks(Blocks, CState),
780+
BlockClauses = [
781+
?Q("(_@BlockName@, Vars, Blocks, Context) -> _@BlockAst")
782+
|| {BlockName, BlockAst, _BlockWs} <- BlocksAsts
783+
] ++ [
784+
?Q("(_BlockName, _Vars, _Blocks, _Context) -> <<>>")
785+
],
786+
BlockFunAst = erl_syntax:fun_expr(BlockClauses),
787+
BlockListAst = erl_syntax:abstract([ BlockName || {BlockName, _, _} <- BlocksAsts ]),
788+
Ast = ?Q([
789+
"template_compiler_runtime_internal:compose("
790+
"_@srcpos,"
791+
"{cat, _@template},"
792+
"_@args,"
793+
"_@runtime,"
794+
"_@context_vars,"
795+
"_@is_context_vars,"
796+
"_@vars,"
797+
"_@block_list,",
798+
"_@block_fun,",
799+
"_@context)"
800+
],
801+
[
802+
{srcpos, erl_syntax:abstract(SrcPos)},
803+
{template, TemplateAst},
804+
{args, ArgsListAst},
805+
{vars, erl_syntax:variable(CState#cs.vars_var)},
806+
{runtime, erl_syntax:atom(Runtime)},
807+
{context, erl_syntax:variable(CState#cs.context_var)},
808+
{context_vars, erl_syntax:abstract(CState#cs.context_vars)},
809+
{block_list, BlockListAst},
810+
{block_fun, BlockFunAst},
811+
{is_context_vars, erl_syntax:abstract(IsContextVars)}
812+
]),
813+
Ws2 = maybe_add_include(Template, undefined, false, Ws1),
814+
{Ws2, Ast}.
815+
816+
725817
expr_list(ExprList, CState, Ws) ->
726818
lists:foldr(
727819
fun(E, {WsAcc, ExprAcc}) ->

src/template_compiler_parser.yrl

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ Nonterminals
5555
CatIncludeTag
5656
NowTag
5757

58+
ComposeBlock
59+
ComposeBraced
60+
EndComposeBraced
61+
CatComposeBlock
62+
CatComposeBraced
63+
5864
BlockBlock
5965
BlockBraced
6066
EndBlockBraced
@@ -172,10 +178,12 @@ Terminals
172178
block_keyword
173179
cache_keyword
174180
call_keyword
181+
catcompose_keyword
175182
catinclude_keyword
176183
close_tag
177184
close_var
178185
comment_keyword
186+
compose_keyword
179187
colon
180188
colons
181189
comma
@@ -188,6 +196,7 @@ Terminals
188196
endblock_keyword
189197
endcache_keyword
190198
endcomment_keyword
199+
endcompose_keyword
191200
endfilter_keyword
192201
endfor_keyword
193202
endif_keyword
@@ -264,7 +273,7 @@ Left 500 '*' '/' '%'.
264273
Unary 600 Uminus Unot.
265274

266275
%% Expected shift/reduce conflicts
267-
Expect 5.
276+
Expect 7.
268277

269278
Template -> ExtendsTag BlockElements : {extends, '$1', '$2'}.
270279
Template -> OverrulesTag BlockElements : {overrules, '$2'}.
@@ -291,6 +300,8 @@ Elements -> Elements WithBlock : '$1' ++ ['$2'].
291300
Elements -> Elements CacheBlock : '$1' ++ ['$2'].
292301
Elements -> Elements ScriptBlock : '$1' ++ ['$2'].
293302
Elements -> Elements CommentBlock : '$1'.
303+
Elements -> Elements ComposeBlock : '$1' ++ ['$2'].
304+
Elements -> Elements CatComposeBlock : '$1' ++ ['$2'].
294305
% Tags
295306
Elements -> Elements TransTag : '$1' ++ ['$2'].
296307
Elements -> Elements TransExtTag : '$1' ++ ['$2'].
@@ -345,6 +356,13 @@ LoadTag -> open_tag load_keyword LoadNames close_tag : {load, '$3'}.
345356
LoadNames -> identifier : ['$1'].
346357
LoadNames -> LoadNames identifier : '$1' ++ ['$2'].
347358

359+
ComposeBlock -> ComposeBraced BlockElements EndComposeBraced : {compose, '$1', '$2'}.
360+
ComposeBraced -> open_tag compose_keyword E OptWith WithArgs close_tag : {'$1', '$3', '$5'}.
361+
EndComposeBraced -> open_tag endcompose_keyword close_tag.
362+
363+
CatComposeBlock -> CatComposeBraced BlockElements EndComposeBraced : {catcompose, '$1', '$2'}.
364+
CatComposeBraced -> open_tag catcompose_keyword E E OptWith WithArgs close_tag : {'$1', '$3', '$4', '$6'}.
365+
348366
BlockBlock -> BlockBraced Elements EndBlockBraced : {block, '$1', '$2'}.
349367
BlockBraced -> open_tag block_keyword identifier close_tag : '$3'.
350368
EndBlockBraced -> open_tag endblock_keyword close_tag.

0 commit comments

Comments
 (0)