Skip to content

Commit

Permalink
Add compose block (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
mworrell authored Jun 28, 2024
1 parent ef201ef commit 8cab2ea
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 22 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,43 @@ Then `a.tpl` renders like:
this is hello the base world template
```

#### Template compose

This includes a template, but also defines extra blocks for the template
to overrule the blocks in the template.

It is like a nameless `{% overrules %}` template, directly defined in the
template text at the spot of the include.


```django
{% compose "a.tpl" what="moon" %}
{% block a %}{{ what }}{% endblock %}
{% endcompose %}
```

And a.tpl is like:

```django
Hello {% block a %}world{% endblock %}, and bye.
```

Then the above renders:

```
Hello moon, and bye.
```

There is also a `catcompose` to use a `catinclude`:

```django
{% catcompose "a.tpl" id what="moon" %}
{% block a %}{{ what }}{% endblock %}
{% endcompose %}
```


#### If tag

Conditionally show or hide parts of a template:
Expand Down
12 changes: 6 additions & 6 deletions rebar.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
{<<"qdate_localtime">>,{pkg,<<"qdate_localtime">>,<<"1.2.0">>},0},
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},2},
{<<"tls_certificate_check">>,
{pkg,<<"tls_certificate_check">>,<<"1.20.0">>},
{pkg,<<"tls_certificate_check">>,<<"1.21.0">>},
1},
{<<"zotonic_stdlib">>,{pkg,<<"zotonic_stdlib">>,<<"1.15.0">>},0}]}.
{<<"zotonic_stdlib">>,{pkg,<<"zotonic_stdlib">>,<<"1.20.0">>},0}]}.
[
{pkg_hash,[
{<<"cowlib">>, <<"A9FA9A625F1D2025FE6B462CB865881329B5CAFF8F1854D1CBC9F9533F00E1E1">>},
{<<"qdate_localtime">>, <<"644ADE4C7F7EAC765E2048DFA714D78EA86BAF5255FE46279B2EAC5729760A07">>},
{<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>},
{<<"tls_certificate_check">>, <<"1AC0C53F95E201FEB8D398EF9D764AE74175231289D89F166BA88A7F50CD8E73">>},
{<<"zotonic_stdlib">>, <<"0876BB82B9CFDE331DC758C8A7818181BB5654F137723DF137DE53C25AD3DD1D">>}]},
{<<"tls_certificate_check">>, <<"042AB2C0C860652BC5CF69C94E3A31F96676D14682E22EC7813BD173CEFF1788">>},
{<<"zotonic_stdlib">>, <<"51A484CF4B692042A5A251510010E00FF419CFF6C85E094DD3E00E283817B53D">>}]},
{pkg_hash_ext,[
{<<"cowlib">>, <<"163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0">>},
{<<"qdate_localtime">>, <<"98A538A5B6046B8652DFC5630B030D0414A1B31D0130C81FA6B88B5C1E625109">>},
{<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>},
{<<"tls_certificate_check">>, <<"AB57B74B1A63DC5775650699A3EC032EC0065005EFF1F020818742B7312A8426">>},
{<<"zotonic_stdlib">>, <<"1864A96F2B5AE278DC52607F89CC90BF492713E7C3AC8EADDC1BA863E06E5921">>}]}
{<<"tls_certificate_check">>, <<"6CEE6CFFC35A390840D48D463541D50746A7B0E421ACAADB833CFC7961E490E7">>},
{<<"zotonic_stdlib">>, <<"C8651604BB9165EC4A6F9EAB1FB1A4D5BE5174C2E3D02815F76EB87CBBC495F2">>}]}
].
25 changes: 17 additions & 8 deletions src/template_compiler.erl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
%% @author Marc Worrell <[email protected]>
%% @copyright 2016-2023 Marc Worrell
%% @copyright 2016-2024 Marc Worrell
%% @doc Main template compiler entry points.
%% @end

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

-export([
render/4,
render/5,
render_block/5,
lookup/3,
flush/0,
Expand All @@ -31,7 +32,8 @@
compile_binary/4,
get_option/2,
is_template_module/1,
translations/1
translations/1,
compile_blocks/2
]).

-include_lib("syntax_tools/include/merl.hrl").
Expand Down Expand Up @@ -92,12 +94,19 @@
%% returns the rendering result.
-spec render(Template :: template(), Vars :: map() | list(), Options :: options(), Context :: term()) ->
{ok, render_result()} | {error, term()}.
render(Template, Vars, Options, Context) when is_list(Vars) ->
render(Template, props_to_map(Vars, #{}), Options, Context);
render(Template0, Vars, Options, Context) when is_map(Vars) ->
render(Template0, Vars, Options, Context) ->
render(Template0, #{}, Vars, Options, Context).

%% @doc Render a template. This looks up the templates needed, ensures compilation and
%% returns the rendering result. Start with a block-map to find some predefined blocks.
-spec render(Template :: template(), BlockMap :: map(), Vars :: map() | list(), Options :: options(), Context :: term()) ->
{ok, render_result()} | {error, term()}.
render(Template0, BlockMap0, Vars, Options, Context) when is_list(Vars) ->
render(Template0, BlockMap0, props_to_map(Vars, #{}), Options, Context);
render(Template0, BlockMap0, Vars, Options, Context) when is_map(Vars) ->
Template = normalize_template(Template0),
Runtime = proplists:get_value(runtime, Options, template_compiler_runtime),
case block_lookup(Runtime:map_template(Template, Vars, Context), #{}, [], [], Options, Vars, Runtime, Context) of
case block_lookup(Runtime:map_template(Template, Vars, Context), BlockMap0, [], [], Options, Vars, Runtime, Context) of
{ok, BaseModule, ExtendsStack, BlockMap, OptDebugWrap} ->
% Start with the render function of the "base" template
% Optionally add the unique prefix for this rendering.
Expand Down Expand Up @@ -468,7 +477,7 @@ split_loc({Filename, Line}) ->
line => Line
}.

-spec compile_blocks([block_element()], #cs{}) -> {#ws{}, [{atom(), erl_syntax:syntaxTree()}]}.
-spec compile_blocks([block_element()], #cs{}) -> {#ws{}, [{atom(), erl_syntax:syntaxTree(), #ws{}}]}.
compile_blocks(Blocks, CState) ->
Ws = #ws{},
lists:foldl(
Expand Down
92 changes: 92 additions & 0 deletions src/template_compiler_element.erl
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ compile({'call_with', {identifier, SrcPos, Name}, Expr}, CState, Ws) ->
{context, erl_syntax:variable(CState#cs.context_var)}
]),
{Ws1, Ast};
compile({'compose', {TagPos, Template, Args}, Blocks}, CState, Ws) ->
{Ws1, ArgsList} = with_args(Args, CState, Ws, false),
IsContextVar = is_context_vars_arg(Args, CState),
compose(TagPos, Template, ArgsList, IsContextVar, Blocks, CState, Ws1);
compile({'catcompose', {TagPos, Template, IdExpr, Args}, Blocks}, CState, Ws) ->
{Ws1, ArgsList} = with_args(Args, CState, Ws, false),
{Ws2, IdAst} = template_compiler_expr:compile(IdExpr, CState, Ws1),
IsContextVar = is_context_vars_arg(Args, CState),
catcompose(TagPos, Template, IdAst, ArgsList, IsContextVar, Blocks, CState, Ws2);
compile({custom_tag, {identifier, SrcPos, Name}, Args}, #cs{runtime=Runtime} = CState, Ws) ->
{Ws1, ArgsList} = with_args(Args, CState, Ws, true),
TagName = template_compiler_utils:to_atom(Name),
Expand Down Expand Up @@ -722,6 +731,89 @@ maybe_add_include({string_literal, SrcPos, Text}, Method, IsCatinclude, Ws) ->
maybe_add_include(_Token, _Method, _IsCatinclude, Ws) ->
Ws.


compose({_, SrcPos, _}, Template, ArgsList, IsContextVars, Blocks, #cs{runtime=Runtime} = CState, Ws) ->
{Ws1, TemplateAst} = template_compiler_expr:compile(Template, CState, Ws),
ArgsListAst = erl_syntax:list([ erl_syntax:tuple([A,B]) || {A,B} <- ArgsList ]),
{_BlocksWs, BlocksAsts} = template_compiler:compile_blocks(Blocks, CState),
BlockClauses = [
?Q("(_@BlockName@, Vars, Blocks, Context) -> _@BlockAst")
|| {BlockName, BlockAst, _BlockWs} <- BlocksAsts
] ++ [
?Q("(_BlockName, _Vars, _Blocks, _Context) -> <<>>")
],
BlockFunAst = erl_syntax:fun_expr(BlockClauses),
BlockListAst = erl_syntax:abstract([ BlockName || {BlockName, _, _} <- BlocksAsts ]),
Ast = ?Q([
"template_compiler_runtime_internal:compose("
"_@srcpos,"
"_@template,"
"_@args,"
"_@runtime,"
"_@context_vars,"
"_@is_context_vars,"
"_@vars,"
"_@block_list,",
"_@block_fun,",
"_@context)"
],
[
{srcpos, erl_syntax:abstract(SrcPos)},
{template, TemplateAst},
{args, ArgsListAst},
{vars, erl_syntax:variable(CState#cs.vars_var)},
{runtime, erl_syntax:atom(Runtime)},
{context, erl_syntax:variable(CState#cs.context_var)},
{context_vars, erl_syntax:abstract(CState#cs.context_vars)},
{block_list, BlockListAst},
{block_fun, BlockFunAst},
{is_context_vars, erl_syntax:abstract(IsContextVars)}
]),
Ws2 = maybe_add_include(Template, undefined, false, Ws1),
{Ws2, Ast}.

catcompose({_, SrcPos, _}, Template, IdAst, ArgsList, IsContextVars, Blocks, #cs{runtime=Runtime} = CState, Ws) ->
{Ws1, TemplateAst} = template_compiler_expr:compile(Template, CState, Ws),
ArgsList1 = [ {erl_syntax:atom('$cat'), IdAst} | ArgsList ],
ArgsListAst = erl_syntax:list([ erl_syntax:tuple([A,B]) || {A,B} <- ArgsList1 ]),
{_BlocksWs, BlocksAsts} = template_compiler:compile_blocks(Blocks, CState),
BlockClauses = [
?Q("(_@BlockName@, Vars, Blocks, Context) -> _@BlockAst")
|| {BlockName, BlockAst, _BlockWs} <- BlocksAsts
] ++ [
?Q("(_BlockName, _Vars, _Blocks, _Context) -> <<>>")
],
BlockFunAst = erl_syntax:fun_expr(BlockClauses),
BlockListAst = erl_syntax:abstract([ BlockName || {BlockName, _, _} <- BlocksAsts ]),
Ast = ?Q([
"template_compiler_runtime_internal:compose("
"_@srcpos,"
"{cat, _@template},"
"_@args,"
"_@runtime,"
"_@context_vars,"
"_@is_context_vars,"
"_@vars,"
"_@block_list,",
"_@block_fun,",
"_@context)"
],
[
{srcpos, erl_syntax:abstract(SrcPos)},
{template, TemplateAst},
{args, ArgsListAst},
{vars, erl_syntax:variable(CState#cs.vars_var)},
{runtime, erl_syntax:atom(Runtime)},
{context, erl_syntax:variable(CState#cs.context_var)},
{context_vars, erl_syntax:abstract(CState#cs.context_vars)},
{block_list, BlockListAst},
{block_fun, BlockFunAst},
{is_context_vars, erl_syntax:abstract(IsContextVars)}
]),
Ws2 = maybe_add_include(Template, undefined, false, Ws1),
{Ws2, Ast}.


expr_list(ExprList, CState, Ws) ->
lists:foldr(
fun(E, {WsAcc, ExprAcc}) ->
Expand Down
20 changes: 19 additions & 1 deletion src/template_compiler_parser.yrl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ Nonterminals
CatIncludeTag
NowTag

ComposeBlock
ComposeBraced
EndComposeBraced
CatComposeBlock
CatComposeBraced

BlockBlock
BlockBraced
EndBlockBraced
Expand Down Expand Up @@ -172,10 +178,12 @@ Terminals
block_keyword
cache_keyword
call_keyword
catcompose_keyword
catinclude_keyword
close_tag
close_var
comment_keyword
compose_keyword
colon
colons
comma
Expand All @@ -188,6 +196,7 @@ Terminals
endblock_keyword
endcache_keyword
endcomment_keyword
endcompose_keyword
endfilter_keyword
endfor_keyword
endif_keyword
Expand Down Expand Up @@ -264,7 +273,7 @@ Left 500 '*' '/' '%'.
Unary 600 Uminus Unot.

%% Expected shift/reduce conflicts
Expect 5.
Expect 7.

Template -> ExtendsTag BlockElements : {extends, '$1', '$2'}.
Template -> OverrulesTag BlockElements : {overrules, '$2'}.
Expand All @@ -291,6 +300,8 @@ Elements -> Elements WithBlock : '$1' ++ ['$2'].
Elements -> Elements CacheBlock : '$1' ++ ['$2'].
Elements -> Elements ScriptBlock : '$1' ++ ['$2'].
Elements -> Elements CommentBlock : '$1'.
Elements -> Elements ComposeBlock : '$1' ++ ['$2'].
Elements -> Elements CatComposeBlock : '$1' ++ ['$2'].
% Tags
Elements -> Elements TransTag : '$1' ++ ['$2'].
Elements -> Elements TransExtTag : '$1' ++ ['$2'].
Expand Down Expand Up @@ -345,6 +356,13 @@ LoadTag -> open_tag load_keyword LoadNames close_tag : {load, '$3'}.
LoadNames -> identifier : ['$1'].
LoadNames -> LoadNames identifier : '$1' ++ ['$2'].

ComposeBlock -> ComposeBraced BlockElements EndComposeBraced : {compose, '$1', '$2'}.
ComposeBraced -> open_tag compose_keyword E OptWith WithArgs close_tag : {'$1', '$3', '$5'}.
EndComposeBraced -> open_tag endcompose_keyword close_tag.

CatComposeBlock -> CatComposeBraced BlockElements EndComposeBraced : {catcompose, '$1', '$2'}.
CatComposeBraced -> open_tag catcompose_keyword E E OptWith WithArgs close_tag : {'$1', '$3', '$4', '$6'}.

BlockBlock -> BlockBraced Elements EndBlockBraced : {block, '$1', '$2'}.
BlockBraced -> open_tag block_keyword identifier close_tag : '$3'.
EndBlockBraced -> open_tag endblock_keyword close_tag.
Expand Down
Loading

0 comments on commit 8cab2ea

Please sign in to comment.