Skip to content

Commit

Permalink
Merge branch 'feature/#2'
Browse files Browse the repository at this point in the history
  • Loading branch information
soranoba committed May 8, 2015
2 parents 5a15c03 + 0d90a65 commit e540104
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 66 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ Associative array
<<"hoge">>
```

### More information
You want more information, see the [doc](doc).

## Attention
- The number of new line.
- New line in the template has left all.
Expand Down
54 changes: 45 additions & 9 deletions doc/mustache.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Copyright (c) 2015 Hinagiku Soranoba All Rights Reserved.


<pre><code>
assoc_data() = [{<a href="#type-data_key">data_key()</a>, <a href="#type-data_value">data_value()</a>}]
assoc_data() = [{atom(), <a href="#type-data_value">data_value()</a>}] | [{binary(), <a href="#type-data_value">data_value()</a>}] | [{string(), <a href="#type-data_value">data_value()</a>}]
</code></pre>


Expand All @@ -42,24 +42,38 @@ data() = <a href="#type-assoc_data">assoc_data()</a>



### <a name="type-data_key">data_key()</a> ###
### <a name="type-data_value">data_value()</a> ###



<pre><code>
data_key() = string()
data_value() = <a href="#type-data">data()</a> | iodata() | number() | atom() | fun((<a href="#type-data">data()</a>, function()) -&gt; iodata())
</code></pre>





### <a name="type-data_value">data_value()</a> ###
### <a name="type-option">option()</a> ###



<pre><code>
option() = {key_type, atom | binary | string}
</code></pre>



- key_type: Specify the type of the key in [`data/0`](#data-0). Default value is `string`.



### <a name="type-options">options()</a> ###



<pre><code>
data_value() = <a href="#type-data">data()</a> | iodata() | fun((<a href="#type-data">data()</a>, function()) -&gt; iodata())
options() = [<a href="#type-option">option()</a>]
</code></pre>


Expand All @@ -76,7 +90,7 @@ __abstract datatype__: `template()`
## Function Index ##


<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#compile-2">compile/2</a></td><td>Embed the data in the template.</td></tr><tr><td valign="top"><a href="#parse_binary-1">parse_binary/1</a></td><td>Create a <a href="#template-0"><code>template/0</code></a> from a binary.</td></tr><tr><td valign="top"><a href="#parse_file-1">parse_file/1</a></td><td>Create a <a href="#template-0"><code>template/0</code></a> from a file.</td></tr><tr><td valign="top"><a href="#render-2">render/2</a></td><td>Equivalent to <a href="#compile-2"><tt>compile(parse_binary(Bin), Data)</tt></a>.</td></tr></table>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#compile-2">compile/2</a></td><td>Equivalent to <a href="#compile-3"><tt>compile(Template, Data, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#compile-3">compile/3</a></td><td>Embed the data in the template.</td></tr><tr><td valign="top"><a href="#parse_binary-1">parse_binary/1</a></td><td>Create a <a href="#template-0"><code>template/0</code></a> from a binary.</td></tr><tr><td valign="top"><a href="#parse_file-1">parse_file/1</a></td><td>Create a <a href="#template-0"><code>template/0</code></a> from a file.</td></tr><tr><td valign="top"><a href="#render-2">render/2</a></td><td>Equivalent to <a href="#render-3"><tt>render(Bin, Data, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#render-3">render/3</a></td><td>Equivalent to <a href="#compile-3"><tt>compile(parse_binary(Bin), Data, Options)</tt></a>.</td></tr></table>


<a name="functions"></a>
Expand All @@ -89,7 +103,18 @@ __abstract datatype__: `template()`


<pre><code>
compile(Mustache::<a href="#type-template">template()</a>, Data::<a href="#type-data">data()</a>) -&gt; binary()
compile(Template::<a href="#type-template">template()</a>, Data::<a href="#type-data">data()</a>) -&gt; binary()
</code></pre>
<br />

Equivalent to [`compile(Template, Data, [])`](#compile-3).
<a name="compile-3"></a>

### compile/3 ###


<pre><code>
compile(Mustache::<a href="#type-template">template()</a>, Data::<a href="#type-data">data()</a>, Options::<a href="#type-options">options()</a>) -&gt; binary()
</code></pre>
<br />

Expand All @@ -111,7 +136,7 @@ Create a [`template/0`](#template-0) from a binary.


<pre><code>
parse_file(Filename::<a href="file.md#type-filename">file:filename()</a>) -&gt; <a href="#type-template">template()</a>
parse_file(Filename::<a href="file.md#type-filename_all">file:filename_all()</a>) -&gt; <a href="#type-template">template()</a>
</code></pre>
<br />

Expand All @@ -126,6 +151,17 @@ render(Bin::binary(), Data::<a href="#type-data">data()</a>) -&gt; binary()
</code></pre>
<br />

Equivalent to [`compile(parse_binary(Bin), Data)`](#compile-2).
Equivalent to [`render(Bin, Data, [])`](#render-3).

__See also:__ [compile/2](#compile-2), [parse_binary/1](#parse_binary-1), [parse_file/1](#parse_file-1), [render/2](#render-2).
<a name="render-3"></a>

### render/3 ###


<pre><code>
render(Bin::binary(), Data::<a href="#type-data">data()</a>, Options::<a href="#type-options">options()</a>) -&gt; binary()
</code></pre>
<br />

Equivalent to [`compile(parse_binary(Bin), Data, Options)`](#compile-3).
120 changes: 69 additions & 51 deletions src/mustache.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
%%----------------------------------------------------------------------------------------------------------------------
-export([
render/2,
render/3,
parse_binary/1,
parse_file/1,
compile/2
compile/2,
compile/3
]).

-export_type([
template/0,
data/0
data/0,
options/0
]).

%%----------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -51,15 +54,21 @@
%% @see parse_binary/1
%% @see parse_file/1

-type data_key() :: string() | atom().
-type data_value() :: data() | iodata() | fun((data(), function()) -> iodata()).
-type assoc_data() :: [{data_key(), data_value()}].
-type data_key() :: atom() | binary() | string().
-type data_value() :: data() | iodata() | number() | atom() | fun((data(), function()) -> iodata()).
-type assoc_data() :: [{atom(), data_value()}] | [{binary(), data_value()}] | [{string(), data_value()}].

-type option() :: {key_type, atom | binary | string}.
%% - key_type: Specify the type of the key in {@link data/0}. Default value is `string'.
-type options() :: [option()].

-ifdef(namespaced_types).
-type data() :: #{data_key() => data_value()} | assoc_data().
-type maps_data() :: #{atom() => data_value()} | #{binary() => data_value()} | #{string() => data_value()}.
-type data() :: maps_data() | assoc_data().
-else.
-type data() :: assoc_data().
-type data() :: assoc_data().
-endif.
%% All key in assoc list or maps must be same type.
%% @see render/2
%% @see compile/2

Expand All @@ -69,30 +78,40 @@
%% Exported Functions
%%----------------------------------------------------------------------------------------------------------------------

%% @equiv compile(parse_binary(Bin), Data)
%% @equiv render(Bin, Data, [])
-spec render(binary(), data()) -> binary().
render(Bin, Data) ->
compile(parse_binary(Bin), Data).
render(Bin, Data, []).

%% @equiv compile(parse_binary(Bin), Data, Options)
-spec render(binary(), data(), options()) -> binary().
render(Bin, Data, Options) ->
compile(parse_binary(Bin), Data, Options).

%% @doc Create a {@link template/0} from a binary.
-spec parse_binary(binary()) -> template().
parse_binary(Bin) when is_binary(Bin) ->
parse_binary_impl(#state{}, Bin).

%% @doc Create a {@link template/0} from a file.
-spec parse_file(file:filename()) -> template().
-spec parse_file(file:filename_all()) -> template().
parse_file(Filename) ->
case file:read_file(Filename) of
{ok, Bin} -> parse_binary_impl(#state{dirname = filename:dirname(Filename)}, Bin);
_ -> error(?FILE_ERROR, [Filename])
end.

%% @doc Embed the data in the template.
%% @equiv compile(Template, Data, [])
-spec compile(template(), data()) -> binary().
compile(#?MODULE{data = Tags} = T, Data) ->
compile(Template, Data) ->
compile(Template, Data, []).

%% @doc Embed the data in the template.
-spec compile(template(), data(), options()) -> binary().
compile(#?MODULE{data = Tags} = T, Data, Options) ->
case check_data_type(Data) of
false -> error(function_clause, [T, Data]);
_ -> iolist_to_binary(lists:reverse(compile_impl(Tags, Data, [])))
_ -> iolist_to_binary(lists:reverse(compile_impl(Tags, Data, [], Options)))
end.

%%----------------------------------------------------------------------------------------------------------------------
Expand All @@ -102,31 +121,31 @@ compile(#?MODULE{data = Tags} = T, Data) ->
%% @doc {@link compile/2}
%%
%% ATTENTION: The result is a list that is inverted.
-spec compile_impl(Template :: [tag()], data(), Result :: iodata()) -> iodata().
compile_impl([], _, Result) ->
-spec compile_impl(Template :: [tag()], data(), Result :: iodata(), Options :: options()) -> iodata().
compile_impl([], _, Result, _) ->
Result;
compile_impl([{n, Key} | T], Map, Result) ->
compile_impl(T, Map, [escape(to_binary(data_get(binary_to_list(Key), Map, <<>>))) | Result]);
compile_impl([{'&', Key} | T], Map, Result) ->
compile_impl(T, Map, [to_binary(data_get(binary_to_list(Key), Map, <<>>)) | Result]);
compile_impl([{'#', Key, Tags, Source} | T], Map, Result) ->
Value = data_get(binary_to_list(Key), Map, undefined),
compile_impl([{n, Key} | T], Map, Result, Options) ->
compile_impl(T, Map, [escape(to_iodata(data_get(convert_keytype(Key, Options), Map, <<>>))) | Result], Options);
compile_impl([{'&', Key} | T], Map, Result, Options) ->
compile_impl(T, Map, [to_iodata(data_get(convert_keytype(Key, Options), Map, <<>>)) | Result], Options);
compile_impl([{'#', Key, Tags, Source} | T], Map, Result, Options) ->
Value = data_get(convert_keytype(Key, Options), Map, undefined),
case check_data_type(Value) of
true -> compile_impl(T, Map, compile_impl(Tags, Value, Result));
_ when is_list(Value) -> compile_impl(T, Map, lists:foldl(fun(X, Acc) -> compile_impl(Tags, X, Acc) end,
Result, Value));
_ when Value =:= false; Value =:= undefined -> compile_impl(T, Map, Result);
_ when is_function(Value, 2) -> compile_impl(T, Map, [Value(Source, fun(Text) -> render(Text, Map) end) | Result]);
_ -> compile_impl(T, Map, compile_impl(Tags, Map, Result))
true -> compile_impl(T, Map, compile_impl(Tags, Value, Result, Options), Options);
_ when is_list(Value) -> compile_impl(T, Map, lists:foldl(fun(X, Acc) -> compile_impl(Tags, X, Acc, Options) end,
Result, Value), Options);
_ when Value =:= false; Value =:= undefined -> compile_impl(T, Map, Result, Options);
_ when is_function(Value, 2) -> compile_impl(T, Map, [Value(Source, fun(Text) -> render(Text, Map, Options) end) | Result], Options);
_ -> compile_impl(T, Map, compile_impl(Tags, Map, Result, Options), Options)
end;
compile_impl([{'^', Key, Tags} | T], Map, Result) ->
Value = data_get(binary_to_list(Key), Map, undefined),
compile_impl([{'^', Key, Tags} | T], Map, Result, Options) ->
Value = data_get(convert_keytype(Key, Options), Map, undefined),
case Value =:= undefined orelse Value =:= [] orelse Value =:= false of
true -> compile_impl(T, Map, compile_impl(Tags, Map, Result));
false -> compile_impl(T, Map, Result)
true -> compile_impl(T, Map, compile_impl(Tags, Map, Result, Options), Options);
false -> compile_impl(T, Map, Result, Options)
end;
compile_impl([Bin | T], Map, Result) ->
compile_impl(T, Map, [Bin | Result]).
compile_impl([Bin | T], Map, Result, Options) ->
compile_impl(T, Map, [Bin | Result], Options).

%% @see parse_binary/1
-spec parse_binary_impl(state(), Input :: binary()) -> template().
Expand Down Expand Up @@ -260,15 +279,15 @@ remove_space_from_tail_impl([{X, Y} | T], Size) when Size =:= X + Y ->
remove_space_from_tail_impl(_, Size) ->
Size.

%% @doc Number to binary
-spec to_binary(number() | binary() | string() | atom()) -> binary() | string().
to_binary(Integer) when is_integer(Integer) ->
%% @doc term to iodata
-spec to_iodata(number() | binary() | string() | atom()) -> binary() | string().
to_iodata(Integer) when is_integer(Integer) ->
list_to_binary(integer_to_list(Integer));
to_binary(Float) when is_float(Float) ->
to_iodata(Float) when is_float(Float) ->
io_lib:format("~p", [Float]);
to_binary(Atom) when is_atom(Atom) ->
to_iodata(Atom) when is_atom(Atom) ->
list_to_binary(atom_to_list(Atom));
to_binary(X) ->
to_iodata(X) ->
X.

%% @doc HTML Escape
Expand All @@ -286,25 +305,24 @@ escape_char($") -> <<"&quot;">>;
escape_char($') -> <<"&apos;">>;
escape_char(C) -> <<C:8>>.

%% @doc fetch the value of the specified key from {@link data/0}
-spec data_get(data_key(), data(), Default :: term()) -> term().
data_get(Key, Data, Default) ->
case data_get_(Key, Data, Default) of
Default ->
data_get_(list_to_atom(Key), Data, Default);
Value ->
Value
%% @doc convert to {@link data_key/0} from binary.
-spec convert_keytype(binary(), options()) -> data_key().
convert_keytype(KeyBin, Options) ->
case proplists:get_value(key_type, Options, string) of
atom -> list_to_atom(binary_to_list(KeyBin));
string -> binary_to_list(KeyBin);
binary -> KeyBin
end.

%% @doc fetch the value of the specified key from {@link data/0}
-spec data_get_(data_key(), data(), Default :: term()) -> term().
-spec data_get(data_key(), data(), Default :: term()) -> term().
-ifdef(namespaced_types).
data_get_(Key, Map, Default) when is_map(Map) ->
data_get(Key, Map, Default) when is_map(Map) ->
maps:get(Key, Map, Default);
data_get_(Key, AssocList, Default) ->
data_get(Key, AssocList, Default) ->
proplists:get_value(Key, AssocList, Default).
-else.
data_get_(Key, AssocList, Default) ->
data_get(Key, AssocList, Default) ->
proplists:get_value(Key, AssocList, Default).
-endif.

Expand Down
20 changes: 14 additions & 6 deletions test/mustache_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ render_test_() ->
[
{"integer, float, binary, string",
fun() ->
?assertEqual(<<"1, 1.5, hoge, fugo">>,
mustache:render(<<"{{i}}, {{f}}, {{b}}, {{s}}">>,
#{"i" => 1, "f" => 1.5, "b" => <<"hoge">>, "s" => "fugo"}))
?assertEqual(<<"1, 1.5, hoge, fugo, atom">>,
mustache:render(<<"{{i}}, {{f}}, {{b}}, {{s}}, {{a}}">>,
#{"i" => 1, "f" => 1.5, "b" => <<"hoge">>, "s" => "fugo", "a" => atom}))
end}
].
-endif.
Expand Down Expand Up @@ -237,8 +237,16 @@ assoc_list_render_test_() ->
[
{"integer, float, binary, string",
fun() ->
?assertEqual(<<"1, 1.5, hoge, fugo">>,
mustache:render(<<"{{i}}, {{f}}, {{b}}, {{s}}">>,
[{"i", 1}, {"f", 1.5}, {"b", <<"hoge">>}, {"s", "fugo"}]))
?assertEqual(<<"1, 1.5, hoge, fugo, atom">>,
mustache:render(<<"{{i}}, {{f}}, {{b}}, {{s}}, {{a}}">>,
[{"i", 1}, {"f", 1.5}, {"b", <<"hoge">>}, {"s", "fugo"}, {"a", atom}]))
end}
].

atom_and_binary_key_test_() ->
[
{"atom key",
?_assertEqual(<<"atom">>, mustache:render(<<"{{atom}}">>, [{atom, "atom"}], [{key_type, atom}]))},
{"binary key",
?_assertEqual(<<"binary">>, mustache:render(<<"{{binary}}">>, [{<<"binary">>, "binary"}], [{key_type, binary}]))}
].

0 comments on commit e540104

Please sign in to comment.