Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add compose tag #50

Merged
merged 1 commit into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading