diff --git a/README.md b/README.md index 5b8c1a0..e8acd7f 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/doc/mustache.md b/doc/mustache.md index 0765fc2..629c5af 100644 --- a/doc/mustache.md +++ b/doc/mustache.md @@ -23,7 +23,7 @@ Copyright (c) 2015 Hinagiku Soranoba All Rights Reserved.

-assoc_data() = [{data_key(), data_value()}]
+assoc_data() = [{atom(), data_value()}] | [{binary(), data_value()}] | [{string(), data_value()}]
 
@@ -42,24 +42,38 @@ data() = assoc_data() -### data_key() ### +### data_value() ###

-data_key() = string()
+data_value() = data() | iodata() | number() | atom() | fun((data(), function()) -> iodata())
 
-### data_value() ### +### option() ### + + + +

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

-data_value() = data() | iodata() | fun((data(), function()) -> iodata())
+options() = [option()]
 
@@ -76,7 +90,7 @@ __abstract datatype__: `template()` ## Function Index ## -
compile/2Embed the data in the template.
parse_binary/1Create a template/0 from a binary.
parse_file/1Create a template/0 from a file.
render/2Equivalent to compile(parse_binary(Bin), Data).
+
compile/2Equivalent to compile(Template, Data, []).
compile/3Embed the data in the template.
parse_binary/1Create a template/0 from a binary.
parse_file/1Create a template/0 from a file.
render/2Equivalent to render(Bin, Data, []).
render/3Equivalent to compile(parse_binary(Bin), Data, Options).
@@ -89,7 +103,18 @@ __abstract datatype__: `template()`

-compile(Mustache::template(), Data::data()) -> binary()
+compile(Template::template(), Data::data()) -> binary()
+
+
+ +Equivalent to [`compile(Template, Data, [])`](#compile-3). + + +### compile/3 ### + + +

+compile(Mustache::template(), Data::data(), Options::options()) -> binary()
 

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

-parse_file(Filename::file:filename()) -> template()
+parse_file(Filename::file:filename_all()) -> template()
 

@@ -126,6 +151,17 @@ render(Bin::binary(), Data::data()) -> binary()
-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). + + +### render/3 ### + + +

+render(Bin::binary(), Data::data(), Options::options()) -> binary()
+
+
+ +Equivalent to [`compile(parse_binary(Bin), Data, Options)`](#compile-3). diff --git a/src/mustache.erl b/src/mustache.erl index e53fa31..af530a6 100644 --- a/src/mustache.erl +++ b/src/mustache.erl @@ -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 ]). %%---------------------------------------------------------------------------------------------------------------------- @@ -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 @@ -69,10 +78,15 @@ %% 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(). @@ -80,19 +94,24 @@ 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. %%---------------------------------------------------------------------------------------------------------------------- @@ -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(). @@ -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 @@ -286,25 +305,24 @@ 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(). -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. diff --git a/test/mustache_tests.erl b/test/mustache_tests.erl index f5d1458..0e0bad6 100644 --- a/test/mustache_tests.erl +++ b/test/mustache_tests.erl @@ -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. @@ -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}]))} + ].