diff --git a/README.md b/README.md
index 460cf14..5b8c1a0 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,6 @@ mustache
===========
Mustache template engine for Erlang/OTP.
-OTP17 (or later)
-
## What is Mustach ?
A logic-less templates.
- [{{mustache}}](http://mustache.github.io/)
@@ -11,11 +9,31 @@ A logic-less templates.
## 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">>
@@ -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.
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 0000000..4589465
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,11 @@
+
+
+# The mustache application #
+
+
+## Modules ##
+
+
+
+
diff --git a/doc/mustache.md b/doc/mustache.md
new file mode 100644
index 0000000..0765fc2
--- /dev/null
+++ b/doc/mustache.md
@@ -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.
+
+
+
+
+## Data Types ##
+
+
+
+
+### assoc_data() ###
+
+
+
+
+assoc_data() = [{data_key(), data_value()}]
+
+
+
+
+
+
+### data() ###
+
+
+
+
+data() = assoc_data()
+
+
+
+
+
+
+### data_key() ###
+
+
+
+
+data_key() = string()
+
+
+
+
+
+
+### data_value() ###
+
+
+
+
+data_value() = data() | iodata() | fun((data(), function()) -> iodata())
+
+
+
+
+
+
+### template() ###
+
+
+__abstract datatype__: `template()`
+
+
+
+## Function Index ##
+
+
+
+
+
+
+
+## Function Details ##
+
+
+
+### compile/2 ###
+
+
+
+compile(Mustache::template(), Data::data()) -> binary()
+
+
+
+Embed the data in the template.
+
+
+### parse_binary/1 ###
+
+
+
+parse_binary(Bin::binary()) -> template()
+
+
+
+Create a [`template/0`](#template-0) from a binary.
+
+
+### parse_file/1 ###
+
+
+
+parse_file(Filename::file:filename()) -> template()
+
+
+
+Create a [`template/0`](#template-0) from a file.
+
+
+### render/2 ###
+
+
+
+render(Bin::binary(), Data::data()) -> binary()
+
+
+
+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).
diff --git a/rebar.config b/rebar.config
index 398f38c..6b9a057 100644
--- a/rebar.config
+++ b/rebar.config
@@ -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
diff --git a/src/mustache.erl b/src/mustache.erl
index 8ba75a2..0efa42d 100644
--- a/src/mustache.erl
+++ b/src/mustache.erl
@@ -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().
@@ -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
@@ -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)
@@ -270,3 +283,29 @@ escape_char($&) -> <<"&">>;
escape_char($") -> <<""">>;
escape_char($') -> <<"'">>;
escape_char(C) -> <>.
+
+%% @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.
diff --git a/test/mustache_tests.erl b/test/mustache_tests.erl
index 2466f64..f5d1458 100644
--- a/test/mustache_tests.erl
+++ b/test/mustache_tests.erl
@@ -74,6 +74,7 @@ parse_binary_test_() ->
-define(PATH(File), <<"../test/test_data/", File/binary>>).
%% TestData Path
+-ifdef(namespaced_types).
manual_test_() ->
[
{"Variables",
@@ -95,10 +96,14 @@ manual_test_() ->
Template = mustache:parse_file(?PATH(<<"non-empty.mustache">>)),
{ok, File} = file:read_file(?PATH(<<"non-empty.result">>)),
?assertEqual(File, mustache:compile(Template, #{ "repo" => [
- #{ "name" => "resque" },
+ [{"name", "resque"}],
#{ "name" => "hub" },
#{ "name" => "rip" }
- ]}))
+ ]})),
+ ?assertEqual(File, mustache:compile(Template, [{"repo", [ [{"name", "resque"}],
+ #{"name" => "hub"},
+ [{"name", "rip"}]
+ ]}]))
end},
{"Sections : Lamdas",
fun() ->
@@ -155,3 +160,85 @@ render_test_() ->
#{"i" => 1, "f" => 1.5, "b" => <<"hoge">>, "s" => "fugo"}))
end}
].
+-endif.
+
+assoc_list_manual_test_() ->
+ [
+ {"Variables",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"variables.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"variables.result">>)),
+ ?assertEqual(File, mustache:compile(Template, [{"name", "Chris"}, {"company", "GitHub"}]))
+ end},
+ {"Sections : False Values or Empty Lists",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"false_values.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"false_values.result">>)),
+ ?assertEqual(File, mustache:compile(Template, [{"person", false}])),
+ ?assertEqual(File, mustache:compile(Template, [{"person", []}])),
+ ?assertEqual(File, mustache:compile(Template, []))
+ end},
+ {"Sections : Non-Empty Lists",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"non-empty.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"non-empty.result">>)),
+ ?assertEqual(File, mustache:compile(Template, [{"repo", [ [{"name", "resque"}],
+ [{"name", "hub"}],
+ [{"name", "rip"}]
+ ]}
+ ]))
+ end},
+ {"Sections : Lamdas",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"lamdas.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"lamdas.result">>)),
+
+ F = fun(Text, Render) -> ["", Render(Text), ""] end,
+ ?assertEqual(File, mustache:compile(Template, [{"name", "Willy"}, {"wrapped", F}]))
+ end},
+ {"Sections : Non-False Values",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"non-false.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"non-false.result">>)),
+ ?assertEqual(File, mustache:compile(Template, [{"person?", [{"name", "Jon"}]}]))
+ end},
+ {"Inverted Sections",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"invarted.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"invarted.result">>)),
+ ?assertEqual(File, mustache:compile(Template, [{"repo", []}]))
+ end},
+ {"Comments",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"comment.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"comment.result">>)),
+ ?assertEqual(File, mustache:compile(Template, []))
+ end},
+ {"Partials",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"partial.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"partial.result">>)),
+ ?assertEqual(File, mustache:compile(Template, [{"names", [ [{"name", "alice"}],
+ [{"name", "bob"}]
+ ]}]))
+ end},
+ {"Set Delimiter",
+ fun() ->
+ Template = mustache:parse_file(?PATH(<<"delimiter.mustache">>)),
+ {ok, File} = file:read_file(?PATH(<<"delimiter.result">>)),
+ ?assertEqual(File, mustache:compile(Template, [{"default_tags", "tag1"},
+ {"erb_style_tags", "tag2"},
+ {"default_tags_again", "tag3"}
+ ]))
+ end}
+ ].
+
+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"}]))
+ end}
+ ].