Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/dot-operator'
Browse files Browse the repository at this point in the history
  • Loading branch information
soranoba committed Jan 1, 2016
2 parents 11185f8 + d93b0a8 commit 1419de6
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 81 deletions.
95 changes: 74 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ bbmustache

Binary pattern match Based Mustache template engine for Erlang/OTP.

## What is Mustach ?
A logic-less templates.
- [{{mustache}}](http://mustache.github.io/)

## Overview
- Binary pattern match based.
- Binary pattern match based mustache template engine for Erlang/OTP.
- Do not use a regular expression !!
- Support maps and associative arrays.

### What is Mustach ?
A logic-less templates.
- [{{mustache}}](http://mustache.github.io/)

## Usage
### Quick start

Expand All @@ -28,20 +28,7 @@ Eshell V6.3 (abort with ^G)
<<"hoge">>
2> bbmustache:render(<<"{{name}}">>, [{"name", "hoge"}]).
<<"hoge">>
%% auto http escape
3> bbmustache:render(<<"<h1>{{tag}}</h1>">>, #{"tag" => "I like Erlang & mustache"}).
<<"<h1>I like Erlang &amp; mustache</h1>">>
%% don't use http escape
4> bbmustache:render(<<"<h1>{{{tag}}}</h1>">>, #{"tag" => "I like Erlang & mustache"}).
<<"<h1>I like Erlang & mustache</h1>">>
%% change the symbol of the tag
5> bbmustache:render(<<"{{=<< >>=}} <<tag>>, <<={{ }}=>> {{tag}}">>, #{"tag" => "hi"}).
<<" hi, hi">>
```
Please refer to [the documentation for how to use the mustache](http://mustache.github.io/mustache.5.html) as the need arises.
### Use as a library
Add the following settings.
Expand All @@ -62,8 +49,6 @@ Add the following settings.
If you don't use the rebar and use the OTP17 or later, this library should be compile with `-Dnamespaced_types`.
### How to use simple Mustache
- [Mastache Manual](http://mustache.github.io/mustache.5.html)
- Support all of syntax !
Map (R17 or later)
```erlang
Expand Down Expand Up @@ -97,8 +82,76 @@ Associative array
<<"hoge">>
```
### Undocumented function
Although present in many of the implementation, there is a function that does not exist in the document of the mustache.<br />
The `bbmustache` corresponds as much as possible to it.
#### Render plain lists
If you specify the dot as a key, it points to this function.
```erlang
%% template.mustache
{{#mylist}}
escape
{{.}}
unescape
{{{.}}}
{{/mylist}}
%% script
1> bbmustache:compile(bbmustache:parse_file("template.mustache"), #{"mylist" => ["<b>1</b>", "<b>2</b>", "<b>3</b>"]}).
<<" escape\n &lt;b&gt;1&lt;&#x2F;b&gt;\n unescape\n <b>1</b>\n escape\n &lt;b&gt;2&lt;&#x2F;b&gt;\n unescape\n "...>>
%% result
escape
&lt;b&gt;1&lt;&#x2F;b&gt;
unescape
<b>1</b>
escape
&lt;b&gt;2&lt;&#x2F;b&gt;
unescape
<b>2</b>
escape
&lt;b&gt;3&lt;&#x2F;b&gt;
unescape
<b>3</b>
```
However, the types of correspond is only these.<br />
The behavior when given the other types, it is undefined.
```erlang
[integer() | float() | binary() | string() | atom()]
```
### More information
You want more information, see the [doc](doc).
Please refer to [the documentation for how to use the mustache](http://mustache.github.io/mustache.5.html) as the need arises.<br />
`bbmustache` supports all of the syntax that is described in it.<br />
If you want more information regarding the use of `bbmustache`, please see the `bbmustache`'s [document](doc).
## FAQ
### Avoid http escaping
```erlang
%% please use {{{tag}}}
1> bbmustache:render(<<"<h1>{{{tag}}}</h1>">>, #{"tag" => "I like Erlang & mustache"}).
<<"<h1>I like Erlang & mustache</h1>">>
```
### Want to use symbol of tag
```erlang
1> bbmustache:render(<<"{{=<< >>=}} <<tag>>, <<={{ }}=>> {{tag}}">>, #{"tag" => "hi"}).
<<" hi, hi">>
```
### Want to change the type of the key
```erlang
1> bbmustache:render(<<"{{tag}}">>, #{tag => "hi"}, [{key_type, atom}]).
<<"hi">>
```
## Attention
- Lambda expression is included wasted processing.
Expand Down
122 changes: 81 additions & 41 deletions ct/bbmustache_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
init_per_group/2, end_per_group/2,

variables_ct/1, sections_ct1/1, sections_ct2/1, sections_ct3/1, sections_ct4/1,
lambdas_ct/1, comments_ct/1, partials_ct/1, delimiter_ct/1
lambdas_ct/1, comments_ct/1, partials_ct/1, delimiter_ct/1, dot_ct/1, dot_unescape_ct/1
]).
-define(ALL_TEST, [variables_ct, sections_ct1, sections_ct2, sections_ct3, sections_ct4,
lambdas_ct, comments_ct, partials_ct, delimiter_ct]).
lambdas_ct, comments_ct, partials_ct, delimiter_ct, dot_ct, dot_unescape_ct]).

-define(config2, proplists:get_value).
-define(debug(X), begin io:format("~p", [X]), X end).

-ifdef(namespaced_types).
-define(OTP17(X, Y), X).
Expand All @@ -26,26 +29,24 @@
%%----------------------------------------------------------------------------------------------------------------------

all() ->
A = [
{group, assoc_list}
],
B = [
{group, maps},
{group, assoc_list_into_maps},
{group, maps_into_assoc_list}
],
A ++ ?OTP17(B, []).
[
{group, assoc_list},
{group, maps},
{group, assoc_list_into_maps},
{group, maps_into_assoc_list},
{group, atom_key},
{group, binary_key}
].

groups() ->
A = [
{assoc_list, [], ?ALL_TEST}
],
B = [
{maps, [], ?ALL_TEST},
{assoc_list_into_maps, [], ?ALL_TEST},
{maps_into_assoc_list, [], ?ALL_TEST}
],
A ++ ?OTP17(B, []).
[
{assoc_list, [], ?ALL_TEST},
{maps, [], ?ALL_TEST},
{assoc_list_into_maps, [], ?ALL_TEST},
{maps_into_assoc_list, [], ?ALL_TEST},
{atom_key, [], ?ALL_TEST},
{binary_key, [], ?ALL_TEST}
].

init_per_suite(Config) ->
ct:log(?OTP17("otp17 or later", "before otp17")),
Expand All @@ -54,15 +55,25 @@ init_per_suite(Config) ->
end_per_suite(_) ->
ok.

init_per_group(maps, Config) ->
[{data_conv, ?OTP17(fun list_to_maps_recursive/1, ok)} | Config];
init_per_group(assoc_list_into_map, Config) ->
[{data_conv, ?OTP17(fun maps:from_list/1, ok)} | Config];
init_per_group(maps_into_assoc_list, Config) ->
[{data_conv, ?OTP17(fun(X) -> deps_list_to_maps(X, 2) end, ok)} | Config];
init_per_group(_, Config) ->
F = fun(X) -> X end,
[{data_conv, F} | Config].
init_per_group(assoc_list, Config) ->
[{data_conv, fun(X) -> X end} | Config];
init_per_group(maps, _Config) ->
?OTP17([{data_conv, fun list_to_maps_recursive/1} | _Config],
{skip, map_is_unsupported});
init_per_group(assoc_list_into_maps, _Config) ->
?OTP17([{data_conv, fun maps:from_list/1} | _Config],
{skip, map_is_unsupported});
init_per_group(maps_into_assoc_list, _Config) ->
?OTP17([{data_conv, fun(X) -> deps_list_to_maps(X, 2) end} | _Config],
{skip, map_is_unsupported});
init_per_group(atom_key, Config) ->
[{data_conv, fun(X) -> key_conv_recursive(X, fun erlang:list_to_atom/1) end},
{options, [{key_type, atom}]}
| Config];
init_per_group(binary_key, Config) ->
[{data_conv, fun(X) -> key_conv_recursive(X, fun erlang:list_to_binary/1) end},
{options, [{key_type, binary}]}
| Config].

end_per_group(_, _) ->
ok.
Expand All @@ -76,7 +87,7 @@ variables_ct(Config) ->
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"variables.result">>])),

Data = [{"name", "Chris"}, {"company", "<b>GitHub</b>"}],
?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(Data))).
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

sections_ct1(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"false_values.mustache">>])),
Expand All @@ -85,72 +96,89 @@ sections_ct1(Config) ->
Data1 = [{"person", false}],
Data2 = [{"person", []}],
Data3 = [],
[?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(X)))
[?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(X)), ?config2(options, Config, [])))
|| X <- [Data1, Data2, Data3]].

sections_ct2(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"non-empty.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"non-empty.result">>])),

Data = [{"repo", [ [{"name", "resque"}], [{"name", "hub"}], [{"name", "rip"}]]}],
?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(Data))).
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

sections_ct3(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"non-false.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"non-false.result">>])),

Data = [{"person?", [{"name", "Jon"}]}],
?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(Data))).
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

sections_ct4(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"invarted.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"invarted.result">>])),

Data = [{"repo", []}],
?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(Data))).
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

lambdas_ct(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"lambdas.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"lambdas.result">>])),

F = fun(Text, Render) -> ["<b>", Render(Text), "</b>"] end,
Data = [{"name", "Willy"}, {"wrapped", F}],
?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(Data))).
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

comments_ct(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"comment.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"comment.result">>])),

Data = [],
?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(Data))).
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

partials_ct(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"partial.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"partial.result">>])),

Data = [{"names", [[{"name", "alice"}], [{"name", "bob"}]]}],
?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(Data))).
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

delimiter_ct(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"delimiter.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"delimiter.result">>])),

Data = [{"default_tags", "tag1"}, {"erb_style_tags", "tag2"}, {"default_tags_again", "tag3"}],
?assertEqual(File, bbmustache:compile(Template, (?config(data_conv, Config))(Data))).
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

dot_ct(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"dot.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"dot.result">>])),

Data = [{"mylist", ["<b>Item 1</b>", "<b>Item 2</b>", "<b>Item 3</b>"]}],
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

dot_unescape_ct(Config) ->
Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"dot_unescape.mustache">>])),
{ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"dot_unescape.result">>])),

Data = [{"mylist", ["<b>Item 1</b>", "<b>Item 2</b>", "<b>Item 3</b>"]}],
?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))).

%%----------------------------------------------------------------------------------------------------------------------
%% Internal Functions
%%----------------------------------------------------------------------------------------------------------------------

-ifdef(namespaced_types).
%% @doc Recursively converted `map' into `assoc list'.
-spec list_to_maps_recursive([{term(), term()}]) -> #{}.
list_to_maps_recursive(AssocList) ->
list_to_maps_recursive([{_, _} | _] = AssocList) ->
lists:foldl(fun({Key, [{_, _} | _] = Value}, Map) ->
maps:put(Key, list_to_maps_recursive(Value), Map);
({Key, Value}, Map) when is_list(Value) ->
maps:put(Key, [list_to_maps_recursive(X) || X <- Value], Map);
({Key, Value}, Map) ->
maps:put(Key, Value, Map)
end, maps:new(), AssocList).
end, maps:new(), AssocList);
list_to_maps_recursive(Other) ->
Other.

%% @doc Convert `map' into `assoc list' that exist at the specified depth.
-spec deps_list_to_maps([{term(), term()}], Deps :: pos_integer()) -> [{term(), term()}] | #{}.
Expand All @@ -165,3 +193,15 @@ deps_list_to_maps(AssocList, Deps) when Deps > 1 ->
lists:reverse(R).

-endif.

%% @doc Recursively converted keys in assoc list.
key_conv_recursive([{_, _} | _] = AssocList, ConvFun) ->
lists:foldl(fun({Key, [{_, _} | _] = Value}, Acc) ->
[{ConvFun(Key), key_conv_recursive(Value, ConvFun)} | Acc];
({Key, Value}, Acc) when is_list(Value) ->
[{ConvFun(Key), [key_conv_recursive(X, ConvFun) || X <- Value]} | Acc];
({Key, Value}, Acc) ->
[{ConvFun(Key), Value} | Acc]
end, [], AssocList);
key_conv_recursive(Other, _) ->
Other.
3 changes: 3 additions & 0 deletions ct/bbmustache_SUITE_data/dot.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{#mylist}}
{{.}}
{{/mylist}}
3 changes: 3 additions & 0 deletions ct/bbmustache_SUITE_data/dot.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
&lt;b&gt;Item 1&lt;&#x2F;b&gt;
&lt;b&gt;Item 2&lt;&#x2F;b&gt;
&lt;b&gt;Item 3&lt;&#x2F;b&gt;
3 changes: 3 additions & 0 deletions ct/bbmustache_SUITE_data/dot_unescape.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{#mylist}}
{{{.}}}
{{/mylist}}
3 changes: 3 additions & 0 deletions ct/bbmustache_SUITE_data/dot_unescape.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<b>Item 1</b>
<b>Item 2</b>
<b>Item 3</b>
2 changes: 1 addition & 1 deletion ct/bbmustache_SUITE_data/variables.result
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*Chris
*
*&lt;b&gt;GitHub&lt;/b&gt;
*&lt;b&gt;GitHub&lt;&#x2F;b&gt;
*<b>GitHub</b>
8 changes: 8 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@


# The bbmustache application #


## Modules ##


<table width="100%" border="0" summary="list of modules">
<tr><td><a href="bbmustache.md" class="module">bbmustache</a></td></tr></table>

1 change: 1 addition & 0 deletions doc/bbmustache.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Copyright (c) 2015 Hinagiku Soranoba All Rights Reserved.
## Description ##
This library support all of mustache syntax. <br />
Please refer to [the documentation for how to use the mustache](http://mustache.github.io/mustache.5.html) as the need arises.

<a name="types"></a>

## Data Types ##
Expand Down
Binary file modified rebar3
Binary file not shown.
Loading

0 comments on commit 1419de6

Please sign in to comment.