Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/support-assoc-list'
Browse files Browse the repository at this point in the history
  • Loading branch information
soranoba committed May 6, 2015
2 parents 9d75f1e + 8f76de6 commit 4bd1b7a
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 21 deletions.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,38 @@ mustache
===========
Mustache template engine for Erlang/OTP.

OTP17 (or later)

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

## Overview
- Binary and map base.
- Do not use a regular expression !!
- Support an associative array for the before R17.

## Usage
### Use as a library
Add the following settings.

```erlang
%% rebar.config

%% If you want to use a map is necessary
{erl_opts, [
{platform_define, "^[0-9]+", namespaced_types}
]}.

{deps,
[
{mustache, ".*", {git, "git://github.com/soranoba/mustache.git", {branch, "master"}}}
]}.
```

### How to use simple Mustache
- [Mastache Manual](http://mustache.github.io/mustache.5.html)
- Support all of syntax !

Map (R17 or later)
```erlang
1> mustache:render(<<"{{name}}">>, #{"name" => "hoge"}).
<<"hoge">>
Expand All @@ -31,6 +49,22 @@ A logic-less templates.
<<"hoge">>
```

Associative array
```erlang
1> mustache:render(<<"{{name}}">>, [{"name", "hoge"}]).
<<"hoge">>

2> Template1 = mustache:parse_binary(<<"{{name}}">>).
...
3> mustache:compile(Template1, [{"name", "hoge"}]).
<<"hoge">>

4> Template2 = mustache:parse_file(<<"./hoge.mustache">>).
...
5> mustache:compile(Template2, [{"name", "hoge"}]).
<<"hoge">>
```

## Attention
- The number of new line.
- New line in the template has left all.
Expand Down
11 changes: 11 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@


# The mustache application #


## Modules ##


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

131 changes: 131 additions & 0 deletions doc/mustache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@


# Module mustache #
* [Description](#description)
* [Data Types](#types)
* [Function Index](#index)
* [Function Details](#functions)


Mustach template engine for Erlang/OTP.
Copyright (c) 2015 Hinagiku Soranoba All Rights Reserved.


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

## Data Types ##




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



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





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



<pre><code>
data() = <a href="#type-assoc_data">assoc_data()</a>
</code></pre>





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



<pre><code>
data_key() = string()
</code></pre>





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



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





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


__abstract datatype__: `template()`

<a name="index"></a>

## 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>


<a name="functions"></a>

## Function Details ##

<a name="compile-2"></a>

### compile/2 ###


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

Embed the data in the template.
<a name="parse_binary-1"></a>

### parse_binary/1 ###


<pre><code>
parse_binary(Bin::binary()) -&gt; <a href="#type-template">template()</a>
</code></pre>
<br />

Create a [`template/0`](#template-0) from a binary.
<a name="parse_file-1"></a>

### parse_file/1 ###


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

Create a [`template/0`](#template-0) from a file.
<a name="render-2"></a>

### render/2 ###


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

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

__See also:__ [compile/2](#compile-2), [parse_binary/1](#parse_binary-1), [parse_file/1](#parse_file-1), [render/2](#render-2).
1 change: 1 addition & 0 deletions rebar.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
%% vim: set filetype=erlang : -*- erlang -*-

{erl_opts, [
{platform_define, "^[0-9]+", namespaced_types},
warnings_as_errors,
warn_export_all,
warn_untyped_record
Expand Down
73 changes: 56 additions & 17 deletions src/mustache.erl
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,29 @@
-opaque template() :: #?MODULE{}.
%% @see parse_binary/1
%% @see parse_file/1
-type data() :: #{string() => data() | iodata() | fun((data(), function()) -> iodata())}.

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

-ifdef(namespaced_types).
-type data() :: #{data_key() => data_value()} | assoc_data().
-else.
-type data() :: assoc_data().
-endif.
%% @see render/2
%% @see compile/2

-type partial() :: {partial, {state(), EndTag :: binary(), LastTagSize :: non_neg_integer(), Rest :: binary(), [tag()]}}.

%%----------------------------------------------------------------------------------------------------------------------
%% Exported Functions
%%----------------------------------------------------------------------------------------------------------------------

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

%% @doc Create a {@link template/0} from a binary.
-spec parse_binary(binary()) -> template().
Expand All @@ -79,8 +89,11 @@ parse_file(Filename) ->

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

%%----------------------------------------------------------------------------------------------------------------------
%% Internal Function
Expand All @@ -93,21 +106,21 @@ compile(#?MODULE{data = Tags}, Map) when is_map(Map) ->
compile_impl([], _, Result) ->
Result;
compile_impl([{n, Key} | T], Map, Result) ->
compile_impl(T, Map, [escape(to_binary(maps:get(binary_to_list(Key), 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(maps:get(binary_to_list(Key), 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 = maps:get(binary_to_list(Key), Map, undefined),
if
is_list(Value) -> compile_impl(T, Map, lists:foldl(fun(X, Acc) -> compile_impl(Tags, X, Acc) end,
Result, Value));
Value =:= false; Value =:= undefined -> compile_impl(T, Map, Result);
is_function(Value, 2) -> compile_impl(T, Map, [Value(Source, fun(Text) -> render(Text, Map) end) | Result]);
is_map(Value) -> compile_impl(T, Map, compile_impl(Tags, Value, Result));
true -> compile_impl(T, Map, compile_impl(Tags, Map, Result))
Value = data_get(binary_to_list(Key), 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))
end;
compile_impl([{'^', Key, Tags} | T], Map, Result) ->
Value = maps:get(binary_to_list(Key), Map, undefined),
Value = data_get(binary_to_list(Key), 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)
Expand Down Expand Up @@ -270,3 +283,29 @@ escape_char($&) -> <<"&amp;">>;
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().
-ifdef(namespaced_types).
data_get(Key, Map, Default) when is_map(Map) ->
maps:get(Key, Map, Default);
data_get(Key, AssocList, Default) ->
proplists:get_value(Key, AssocList, Default).
-else.
data_get(Key, AssocList, Default) ->
proplists:get_value(Key, AssocList, Default).
-endif.

%% @doc check whether the type of {@link data/0}
%%
%% maybe: There is also the possibility of iolist
-spec check_data_type(data() | term()) -> boolean() | maybe.
-ifdef(namespaced_types).
check_data_type([]) -> maybe;
check_data_type([{_, _} | _]) -> true;
check_data_type(Map) -> is_map(Map).
-else.
check_data_type([]) -> maybe;
check_data_type([{_, _} | _]) -> true;
check_data_type(_) -> false.
-endif.
Loading

0 comments on commit 4bd1b7a

Please sign in to comment.