pax_global_header00006660000000000000000000000064135102332700014506gustar00rootroot0000000000000052 comment=819496585f7e6d42630c5521ba3c6934b1a81dab erlang-bbmustache-1.6.1+dfsg/000077500000000000000000000000001351023327000160155ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/.gitignore000066400000000000000000000003011351023327000177770ustar00rootroot00000000000000.eunit deps *.o *.beam *.plt erl_crash.dump ebin rel/example_project .concrete/DEV_MODE .rebar *.swp tags *~ doc/* !doc/overview.edoc !doc/*.md .rebar _build .rebar3 _checkouts benchmarks/.tmp erlang-bbmustache-1.6.1+dfsg/.travis.yml000066400000000000000000000002321351023327000201230ustar00rootroot00000000000000language: erlang otp_release: - 21.0 - 20.3 - 19.3 - 18.3 - 17.5 script: "rm -rf _build; make" notifications: email: - soranoba@gmail.com erlang-bbmustache-1.6.1+dfsg/LICENSE000066400000000000000000000020751351023327000170260ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Hinagiku Soranoba Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. erlang-bbmustache-1.6.1+dfsg/Makefile000066400000000000000000000006041351023327000174550ustar00rootroot00000000000000.PHONY: ct all: compile eunit ct xref dialyze edoc compile: @./rebar3 as dev compile xref: @./rebar3 xref clean: @./rebar3 clean ct: @./rebar3 ct cover: @./rebar3 cover eunit: @./rebar3 eunit edoc: @./rebar3 as dev edoc start: @./rebar3 as dev shell dialyze: @./rebar3 dialyzer bench: @./rebar3 as test compile @./rebar3 as bench compile @./benchmarks/bench.escript erlang-bbmustache-1.6.1+dfsg/README.md000066400000000000000000000057771351023327000173140ustar00rootroot00000000000000bbmustache =========== [![Build Status](https://travis-ci.org/soranoba/bbmustache.svg?branch=master)](https://travis-ci.org/soranoba/bbmustache) [![hex.pm version](https://img.shields.io/hexpm/v/bbmustache.svg)](https://hex.pm/packages/bbmustache) Binary pattern match Based Mustache template engine for Erlang/OTP. ## Overview - Binary pattern match based mustache template engine for Erlang/OTP. - Do not use a regular expression !! - Support maps and associative arrays. - Officially support is OTP17 or later. ### What is Mustach ? A logic-less templates. - [{{mustache}}](http://mustache.github.io/) ## Usage ### Quick start ```bash $ git clone git://github.com/soranoba/bbmustache.git $ cd bbmustache $ make start Erlang/OTP 17 [erts-6.3] [source-f9282c6] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:true] Eshell V6.3 (abort with ^G) 1> {ok,[bbmustache]} 1> bbmustache:render(<<"{{name}}">>, #{"name" => "hoge"}). <<"hoge">> 2> bbmustache:render(<<"{{name}}">>, [{"name", "hoge"}]). <<"hoge">> ``` ### Use as a library Add the following settings. ```erlang %% rebar (rebar.config) {deps, [ {bbmustache, ".*", {git, "git://github.com/soranoba/bbmustache.git", {branch, "master"}}} ]}. %% rebar3 (rebar.config) {deps, [bbmustache]}. ``` ### How to use simple Mustache Map ```erlang 1> bbmustache:render(<<"{{name}}">>, #{"name" => "hoge"}). <<"hoge">> 2> Template1 = bbmustache:parse_binary(<<"{{name}}">>). ... 3> bbmustache:compile(Template1, #{"name" => "hoge"}). <<"hoge">> 4> Template2 = bbmustache:parse_file(<<"./hoge.mustache">>). ... 5> bbmustache:compile(Template2, #{"name" => "hoge"}). <<"hoge">> ``` Associative array ```erlang 1> bbmustache:render(<<"{{name}}">>, [{"name", "hoge"}]). <<"hoge">> 2> Template1 = bbmustache:parse_binary(<<"{{name}}">>). ... 3> bbmustache:compile(Template1, [{"name", "hoge"}]). <<"hoge">> 4> Template2 = bbmustache:parse_file(<<"./hoge.mustache">>). ... 5> bbmustache:compile(Template2, [{"name", "hoge"}]). <<"hoge">> ``` ### More information Please refer to [ManPage](http://mustache.github.io/mustache.5.html) and [Specification](https://github.com/mustache/spec) as the need arises.
For the functions supported by this library, please see [here](benchmarks/README.md). ## FAQ ### Avoid http escaping ```erlang %% please use {{{tag}}} 1> bbmustache:render(<<"

{{{tag}}}

">>, #{"tag" => "I like Erlang & mustache"}). <<"

I like Erlang & mustache

">> ``` ### Want to use symbol of tag ```erlang 1> bbmustache:render(<<"{{=<< >>=}} <>, <<={{ }}=>> {{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. - Because it is optimized to `parse_string/1` + `compile/2`. ## Comparison with other libraries [Benchmarks and check the reference implementation](benchmarks/README.md) ## Contribute Pull request is welcome =D ## License [MIT License](LICENSE) erlang-bbmustache-1.6.1+dfsg/benchmarks/000077500000000000000000000000001351023327000201325ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/benchmarks/.tmp/000077500000000000000000000000001351023327000210105ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/benchmarks/.tmp/.gitkeep000066400000000000000000000000001351023327000224270ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/benchmarks/README.md000066400000000000000000000163421351023327000214170ustar00rootroot00000000000000# Benchmarks [benchmark script](bench.escript) |Library|Time | |:------|:-----| |bbmustache | 44052 | |mustache.erl | 682433 | # Check the reference implementation :warning: For libraries other than bbmustache, there is a possibility that there is a miss. ## comments https://github.com/mustache/spec/tree/v1.1.3/specs/comments.yml | |bbmustache|mustache.erl| |:---|:------------|:------------| |Inline|:white_check_mark:|:white_check_mark:| |Multiline|:white_check_mark:|:white_check_mark:| |Standalone|:white_check_mark:|| |Indented Standalone|:white_check_mark:|| |Standalone Line Endings|:white_check_mark:|| |Standalone Without Previous Line|:white_check_mark:|| |Standalone Without Newline|:white_check_mark:|| |Multiline Standalone|:white_check_mark:|| |Indented Multiline Standalone|:white_check_mark:|| |Indented Inline|:white_check_mark:|:white_check_mark:| |Surrounding Whitespace|:white_check_mark:|:white_check_mark:| ## delimiters https://github.com/mustache/spec/tree/v1.1.3/specs/delimiters.yml | |bbmustache|mustache.erl| |:---|:------------|:------------| |Pair Behavior|:white_check_mark:|| |Special Characters|:white_check_mark:|| |Sections|:white_check_mark:|| |Inverted Sections|:white_check_mark:|| |Partial Inheritence|:white_check_mark:|| |Post-Partial Behavior|:white_check_mark:|| |Surrounding Whitespace|:white_check_mark:|| |Outlying Whitespace (Inline)|:white_check_mark:|| |Standalone Tag|:white_check_mark:|| |Indented Standalone Tag|:white_check_mark:|| |Standalone Line Endings|:white_check_mark:|| |Standalone Without Previous Line|:white_check_mark:|| |Standalone Without Newline|:white_check_mark:|| |Pair with Padding|:white_check_mark:|| ## interpolation https://github.com/mustache/spec/tree/v1.1.3/specs/interpolation.yml | |bbmustache|mustache.erl| |:---|:------------|:------------| |No Interpolation|:white_check_mark:|:white_check_mark:| |Basic Interpolation|:white_check_mark:|:white_check_mark:| |HTML Escaping|:white_check_mark:|| |Triple Mustache|:white_check_mark:|:white_check_mark:| |Ampersand|:white_check_mark:|:white_check_mark:| |Basic Integer Interpolation|:white_check_mark:|:white_check_mark:| |Triple Mustache Integer Interpolation|:white_check_mark:|:white_check_mark:| |Ampersand Integer Interpolation|:white_check_mark:|:white_check_mark:| |Basic Decimal Interpolation|:white_check_mark:|:white_check_mark:| |Triple Mustache Decimal Interpolation|:white_check_mark:|:white_check_mark:| |Ampersand Decimal Interpolation|:white_check_mark:|:white_check_mark:| |Basic Context Miss Interpolation|:white_check_mark:|:white_check_mark:| |Triple Mustache Context Miss Interpolation|:white_check_mark:|:white_check_mark:| |Ampersand Context Miss Interpolation|:white_check_mark:|:white_check_mark:| |Dotted Names - Basic Interpolation|:white_check_mark:|| |Dotted Names - Triple Mustache Interpolation|:white_check_mark:|| |Dotted Names - Ampersand Interpolation|:white_check_mark:|| |Dotted Names - Arbitrary Depth|:white_check_mark:|| |Dotted Names - Broken Chains|:white_check_mark:|| |Dotted Names - Broken Chain Resolution|:white_check_mark:|| |Dotted Names - Initial Resolution|:white_check_mark:|| |Dotted Names - Context Precedence|:white_check_mark:|| |Interpolation - Surrounding Whitespace|:white_check_mark:|:white_check_mark:| |Triple Mustache - Surrounding Whitespace|:white_check_mark:|:white_check_mark:| |Ampersand - Surrounding Whitespace|:white_check_mark:|:white_check_mark:| |Interpolation - Standalone|:white_check_mark:|:white_check_mark:| |Triple Mustache - Standalone|:white_check_mark:|:white_check_mark:| |Ampersand - Standalone|:white_check_mark:|:white_check_mark:| |Interpolation With Padding|:white_check_mark:|:white_check_mark:| |Triple Mustache With Padding|:white_check_mark:|:white_check_mark:| |Ampersand With Padding|:white_check_mark:|:white_check_mark:| ## inverted https://github.com/mustache/spec/tree/v1.1.3/specs/inverted.yml | |bbmustache|mustache.erl| |:---|:------------|:------------| |Falsey|:white_check_mark:|:white_check_mark:| |Truthy|:white_check_mark:|:white_check_mark:| |Context|:white_check_mark:|:white_check_mark:| |List|:white_check_mark:|:white_check_mark:| |Empty List|:white_check_mark:|:white_check_mark:| |Doubled|:white_check_mark:|:white_check_mark:| |Nested (Falsey)|:white_check_mark:|| |Nested (Truthy)|:white_check_mark:|| |Context Misses|:white_check_mark:|:white_check_mark:| |Dotted Names - Truthy|:white_check_mark:|| |Dotted Names - Falsey|:white_check_mark:|| |Dotted Names - Broken Chains|:white_check_mark:|| |Surrounding Whitespace|:white_check_mark:|| |Internal Whitespace|:white_check_mark:|| |Indented Inline Sections|:white_check_mark:|| |Standalone Lines|:white_check_mark:|:white_check_mark:| |Standalone Indented Lines|:white_check_mark:|| |Standalone Line Endings|:white_check_mark:|| |Standalone Without Previous Line|:white_check_mark:|| |Standalone Without Newline|:white_check_mark:|| |Padding|:white_check_mark:|:white_check_mark:| ## partials https://github.com/mustache/spec/tree/v1.1.3/specs/partials.yml | |bbmustache|mustache.erl| |:---|:------------|:------------| |Basic Behavior|:white_check_mark:|| |Failed Lookup|:white_check_mark:|| |Context|:white_check_mark:|| |Recursion|:white_check_mark:|| |Surrounding Whitespace|:white_check_mark:|| |Inline Indentation|:white_check_mark:|| |Standalone Line Endings|:white_check_mark:|| |Standalone Without Previous Line|:white_check_mark:|| |Standalone Without Newline|:white_check_mark:|| |Standalone Indentation|:white_check_mark:|| |Padding Whitespace|:white_check_mark:|| ## sections https://github.com/mustache/spec/tree/v1.1.3/specs/sections.yml | |bbmustache|mustache.erl| |:---|:------------|:------------| |Truthy|:white_check_mark:|:white_check_mark:| |Falsey|:white_check_mark:|:white_check_mark:| |Context|:white_check_mark:|| |Deeply Nested Contexts|:white_check_mark:|| |List|:white_check_mark:|| |Empty List|:white_check_mark:|:white_check_mark:| |Doubled|:white_check_mark:|:white_check_mark:| |Nested (Truthy)|:white_check_mark:|| |Nested (Falsey)|:white_check_mark:|| |Context Misses|:white_check_mark:|:white_check_mark:| |Implicit Iterator - String|:white_check_mark:|| |Implicit Iterator - Integer|:white_check_mark:|| |Implicit Iterator - Decimal|:white_check_mark:|| |Implicit Iterator - Array|:white_check_mark:|| |Dotted Names - Truthy|:white_check_mark:|| |Dotted Names - Falsey|:white_check_mark:|| |Dotted Names - Broken Chains|:white_check_mark:|| |Surrounding Whitespace|:white_check_mark:|| |Internal Whitespace|:white_check_mark:|| |Indented Inline Sections|:white_check_mark:|| |Standalone Lines|:white_check_mark:|:white_check_mark:| |Indented Standalone Lines|:white_check_mark:|| |Standalone Line Endings|:white_check_mark:|| |Standalone Without Previous Line|:white_check_mark:|| |Standalone Without Newline|:white_check_mark:|| |Padding|:white_check_mark:|:white_check_mark:| ## ~lambdas https://github.com/mustache/spec/tree/v1.1.3/specs/~lambdas.yml | |bbmustache|mustache.erl| |:---|:------------|:------------| |Interpolation||| |Interpolation - Expansion||| |Interpolation - Alternate Delimiters||| |Interpolation - Multiple Calls||| |Escaping||| |Section||| |Section - Expansion||| |Section - Alternate Delimiters||| |Section - Multiple Calls||| |Inverted Section|:white_check_mark:|:white_check_mark:| erlang-bbmustache-1.6.1+dfsg/benchmarks/bench.escript000077500000000000000000000134221351023327000226110ustar00rootroot00000000000000#!/usr/bin/env escript %% -*- erlang -*- -define(TEST_LIBRARIES, [ {"bbmustache", fun (Template, Data) -> bbmustache:render(Template, Data, [{key_type, binary}]) end}, {"mustache.erl", fun (Template, Data) -> KeyConvFun = fun(B) -> binary_to_atom(B, latin1) end, ValueConvFun = fun(V) when is_binary(V) -> binary_to_list(V); (O) -> O end, list_to_binary(mustache:render(binary_to_list(Template), dict:from_list(conv_recursive(Data, KeyConvFun, ValueConvFun)))) end} ]). main(_) -> ok = code:add_pathsz(filelib:wildcard(filename:absname("_build/**/ebin"))), ok = file:set_cwd("benchmarks/.tmp"), Jsons = filelib:wildcard("../../_build/test/lib/mustache_spec/specs/*.json"), io:format("test files:~n"), [io:format(" ~s~n", [Json]) || Json <- Jsons], Result0 = main1(Jsons, []), Result = Result0 ++ [{<<"benches">>, bench_main()}], ok = file:write_file("../README.md", bbmustache:compile(bbmustache:parse_file("../output.mustache"), Result, [{key_type, binary}])). bench_main() -> Template = "Hello {{name}} You have just won {{value}} dollars! {{#in_ca}} Well, {{taxed_value}} dollars, after taxes. {{/in_ca}}", MapData = #{<<"name">> => "Chris", <<"value">> => 10000, <<"taxed_value">> => 10000 - (10000 * 0.4), <<"in_ca">> => true}, DictData = dict:from_list([{name, "Chris"}, {value, 10000}, {taxed_value, 10000 - (10000 * 0.4)}, {in_ca, true}]), Benches = [ {"bbmustache", fun(T, D) -> bbmustache:render(T, D, [{key_type, binary}]) end, list_to_binary(Template), MapData}, {"mustache.erl", fun(T, D) -> mustache:render(T, D) end, Template, DictData} ], [ [{<<"library">>, Library}, {<<"result">>, bench_run(Render, T, D)}] || {Library, Render, T, D} <- Benches ]. bench_run(Render, Template, Data) -> lists:sum([begin {T, _} = timer:tc(Render, [Template, Data]), T end || _ <- lists:seq(1, 1000)]). main1([], Result) -> [{<<"spec_files">>, lists:reverse(Result)}]; main1([JsonPath | Rest], Result) -> {ok, JsonBin} = file:read_file(JsonPath), JsonDec = jsone:decode(JsonBin, [{object_format, proplist}]), Tests = proplists:get_value(<<"tests">>, JsonDec), Ret = main2(Tests, []), main1(Rest, [[{<<"spec">>, filename:basename(JsonPath, ".json")}, {<<"libraries">>, [[{<<"library">>, L}] || {L, _} <- ?TEST_LIBRARIES]}, {<<"tests">>, Ret} ] | Result]). main2([], Result) -> lists:reverse(Result); main2([Test | Tests], Result) -> Ret = [spec_test(Render, Test) || {_, Render} <- ?TEST_LIBRARIES], main2(Tests, [[ {<<"test">>, proplists:get_value(<<"name">>, Test)}, {<<"results">>, Ret} ] | Result]). spec_test(Render, Assoc) -> Data0 = proplists:get_value(<<"data">>, Assoc), Data = case proplists:get_value(<<"lambda">>, Data0) of undefined -> Data0; Lambda -> [{<<"lambda">>, lambda(Lambda)} | proplists:delete(<<"lambda">>, Data0)] end, Expected = proplists:get_value(<<"expected">>, Assoc), Template = proplists:get_value(<<"template">>, Assoc), Partials = proplists:get_value(<<"partials">>, Assoc, []), ok = clean_dir("."), ok = lists:foreach(fun(P) -> write_file(P) end, Partials), {Pid, Ref} = spawn_monitor(fun() -> Expected = Render(Template, Data) end), Result = receive {'DOWN', Ref, _, _, Reason} -> Reason =:= normal after 3000 -> exit(Pid, kill), demonitor(Ref, [flush]), false end, [{<<"result">>, Result}]. clean_dir(Dir) -> lists:foreach(fun(F) -> file:delete(F) end, filelib:wildcard(filename:join(Dir, "*.mustache"))). write_file({PartialFilename, PartialData}) -> ok = file:write_file(<>, PartialData); write_file(_) -> ok. conv_recursive([{} | Rest], KeyConvFun, ValueConvFun) -> conv_recursive(Rest, KeyConvFun, ValueConvFun); conv_recursive([{_, _} | _] = AssocList, KeyConvFun, ValueConvFun) -> lists:foldl(fun({Key, [{_, _} | _] = Value}, Acc) -> [{KeyConvFun(Key), conv_recursive(Value, KeyConvFun, ValueConvFun)} | Acc]; ({Key, Value}, Acc) when is_list(Value) -> [{KeyConvFun(Key), [conv_recursive(X, KeyConvFun, ValueConvFun) || X <- Value]} | Acc]; ({Key, Value}, Acc) -> [{KeyConvFun(Key), ValueConvFun(Value)} | Acc] end, [], AssocList); conv_recursive(Other, _, _) -> Other. list_to_dict_recursive([{_, _} | _] = AssocList) -> lists:foldl(fun({Key, [{_, _} | _] = Value}, Dict) -> dict:store(Key, list_to_dict_recursive(Value), Dict); ({Key, Value}, Dict) when is_list(Value) -> dict:store(Key, [list_to_dict_recursive(X) || X <- Value], Dict); ({Key, Value}, Dict) -> dict:store(Key, Value, Dict) end, dict:new(), AssocList); list_to_dict_recursive(Other) -> Other. lambda(LambdaList) -> LambdaStr = binary_to_list(proplists:get_value(<<"erlang">>, LambdaList)), io:format("~s~n", [LambdaStr]), {ok, Token, _} = erl_scan:string(LambdaStr), {ok, [Form]} = erl_parse:parse_exprs(Token), {value, Fun, _} = erl_eval:expr(Form, erl_eval:new_bindings()), Fun. erlang-bbmustache-1.6.1+dfsg/benchmarks/output.mustache000066400000000000000000000011441351023327000232250ustar00rootroot00000000000000# Benchmarks [benchmark script](bench.escript) |Library|Time | |:------|:-----| {{# benches }} |{{ library }} | {{ result }} | {{/ benches }} # Check the reference implementation :warning: For libraries other than bbmustache, there is a possibility that there is a miss. {{# spec_files }} ## {{ spec }} https://github.com/mustache/spec/tree/v1.1.3/specs/{{ spec }}.yml | |{{# libraries }}{{ library }}|{{/ libraries }} |:---|{{# libraries }}:------------|{{/ libraries }} {{# tests }} |{{ test }}|{{# results }}{{# result }}:white_check_mark:{{/ result }}|{{/ results }} {{/ tests }} {{/ spec_files }} erlang-bbmustache-1.6.1+dfsg/ct/000077500000000000000000000000001351023327000164235ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE.erl000066400000000000000000000304141351023327000222170ustar00rootroot00000000000000%% @copyright 2015 Hinagiku Soranoba All Rights Reserved. -module(bbmustache_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -export([ all/0, groups/0, init_per_suite/1, end_per_suite/1, 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, dot_ct/1, dot_unescape_ct/1, indent_partials_ct/1, not_found_partials_ct1/1, not_found_partials_ct2/1, not_found_partials_ct3/1 ]). -define(ALL_TEST, [variables_ct, sections_ct1, sections_ct2, sections_ct3, sections_ct4, lambdas_ct, comments_ct, partials_ct, delimiter_ct, dot_ct, dot_unescape_ct, indent_partials_ct, not_found_partials_ct1, not_found_partials_ct2, not_found_partials_ct3, context_stack_ct, context_stack_ct2]). -define(config2, proplists:get_value). -define(debug(X), begin io:format("~p", [X]), X end). -ifdef(namespaced_types). -define(OTP17(X, Y), X). -else. -define(OTP17(X, Y), Y). -endif. %%---------------------------------------------------------------------------------------------------------------------- %% 'common_test' Callback API %%---------------------------------------------------------------------------------------------------------------------- all() -> [ {group, assoc_list}, {group, maps}, {group, assoc_list_into_maps}, {group, maps_into_assoc_list}, {group, atom_key}, {group, binary_key} ]. groups() -> [ {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")), Config. end_per_suite(_) -> ok. 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. %%---------------------------------------------------------------------------------------------------------------------- %% Common Test Functions %%---------------------------------------------------------------------------------------------------------------------- variables_ct(Config) -> Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"variables.mustache">>])), {ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"variables.result">>])), Data = [{"name", "Chris"}, {"company", "GitHub"}], ?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">>])), {ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"false_values.result">>])), Data1 = [{"person", false}], Data2 = [{"person", []}], Data3 = [], [?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, ?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, ?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, ?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) -> ["", Render(Text), ""] end, Data = [{"name", "Willy"}, {"wrapped", F}], ?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, ?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, ?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, ?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", ["Item 1", "Item 2", "Item 3"]}], ?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", ["Item 1", "Item 2", "Item 3"]}], ?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))). indent_partials_ct(Config) -> Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"a.mustache">>])), {ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"a.result">>])), Data = [{"sections", [[{"section", "1st section"}], [{"section", "2nd section"}]]}], ?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))). not_found_partials_ct1(Config) -> Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"not_found_partial.mustache">>])), {ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"not_found_partial.result">>])), ?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))([])), ?config2(options, Config, []))). not_found_partials_ct2(Config) -> ?assertError({file_not_found, <<"does_not_exist_template">>, enoent}, bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"not_found_partial.mustache">>]), [raise_on_partial_miss])). not_found_partials_ct3(Config) -> Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"not_found_partial.mustache">>])), ?assertError({context_missing, {file_not_found, <<"does_not_exist_template">>}}, bbmustache:compile(Template, ?debug((?config(data_conv, Config))([])), ?config2(options, Config, []) ++ [raise_on_context_miss])). context_stack_ct(Config) -> Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"context.mustache">>])), {ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"context.result">>])), Data = [{"a", [{"A", [{"1", "&"}]}]}, {"b", [{"B", [{"2", "<"}]}]}, {"c", [{"C", [{"3", ">"}]}]}], ?assertEqual(File, bbmustache:compile(Template, ?debug((?config(data_conv, Config))(Data)), ?config2(options, Config, []))). context_stack_ct2(Config) -> Template = bbmustache:parse_file(filename:join([?config(data_dir, Config), <<"context2.mustache">>])), {ok, File} = file:read_file(filename:join([?config(data_dir, Config), <<"context2.result">>])), Data = [ {"items", [[{"item", 1}], [{"item", 2}], [{"item", 3}]]}, {"a", [{"b", ["A", "B", "C"]}]} ], ?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'. 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); 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()}] | #{}. deps_list_to_maps(AssocList, 1) -> maps:from_list(AssocList); deps_list_to_maps(AssocList, Deps) when Deps > 1 -> R = lists:foldl(fun({Key, [{_, _} | _] = Value}, Acc) -> [{Key, deps_list_to_maps(Value, Deps - 1)} | Acc]; ({Key, Value}, Acc) -> [{Key, Value} | Acc] end, [], AssocList), 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. erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/000077500000000000000000000000001351023327000225025ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/a.mustache000066400000000000000000000000711351023327000244530ustar00rootroot00000000000000{{# sections }} {{ section }} {{>b}} {{/ sections }} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/a.result000066400000000000000000000002761351023327000241670ustar00rootroot000000000000001st section 1st section Hello, indent paritals ! 2nd line 3rd line 2nd section 2nd section Hello, indent paritals ! 2nd line 3rd line erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/b.mustache000066400000000000000000000000301351023327000244470ustar00rootroot00000000000000{{ section }} {{>c}}erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/c.mustache000066400000000000000000000000531351023327000244550ustar00rootroot00000000000000Hello, indent paritals ! 2nd line 3rd line erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/comment.mustache000066400000000000000000000000401351023327000256710ustar00rootroot00000000000000

Today{{! ignore me }}.

erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/comment.result000066400000000000000000000000201351023327000253740ustar00rootroot00000000000000

Today.

erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/context.mustache000066400000000000000000000005561351023327000257270ustar00rootroot00000000000000{{ a.A.1 }}:{{ b.B.2 }}:{{ c.C.3 }} {{{ a.A.1 }}}:{{{ b.B.2 }}}:{{{ c.C.3 }}} {{#a}} {{ A.1 }}:{{ B.2 }}:{{ b.B.2 }} {{{ A.1 }}}:{{{ B.2 }}}:{{{ b.B.2 }}} {{#A}} {{ 1 }}:{{ A.1 }}:{{ B.2 }}:{{ b.B.2 }} {{{ 1 }}}:{{{ A.1 }}}:{{{ B.2 }}}:{{{ b.B.2 }}} {{#b}} {{ 1 }}:{{ A.1 }}:{{ B.2 }}:{{ b.B.2 }} {{{ 1 }}}:{{{ A.1 }}}:{{{ B.2 }}}:{{{ b.B.2 }}} {{/b}} {{/A}} {{/a}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/context.result000066400000000000000000000001361351023327000254260ustar00rootroot00000000000000&:<:> &:<:> &::< &::< &:&::< &:&::< &:&:<:< &:&:<:< erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/context2.mustache000066400000000000000000000000501351023327000257760ustar00rootroot00000000000000{{#items}} {{item}}. {{a.b}} {{/items}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/context2.result000066400000000000000000000000251351023327000255050ustar00rootroot000000000000001. ABC 2. ABC 3. ABC erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/delimiter.mustache000066400000000000000000000001351351023327000262120ustar00rootroot00000000000000* {{default_tags}} {{=<% %>=}} * <% erb_style_tags %> <%={{ }}=%> * {{ default_tags_again }} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/delimiter.result000066400000000000000000000000251351023327000257150ustar00rootroot00000000000000* tag1 * tag2 * tag3 erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/dot.mustache000066400000000000000000000000351351023327000250210ustar00rootroot00000000000000{{#mylist}} {{.}} {{/mylist}}erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/dot.result000066400000000000000000000001161351023327000245260ustar00rootroot00000000000000<b>Item 1</b> <b>Item 2</b> <b>Item 3</b> erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/dot_unescape.mustache000066400000000000000000000000371351023327000267060ustar00rootroot00000000000000{{#mylist}} {{{.}}} {{/mylist}}erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/dot_unescape.result000066400000000000000000000000521351023327000264100ustar00rootroot00000000000000Item 1 Item 2 Item 3 erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/false_values.mustache000066400000000000000000000000541351023327000267050ustar00rootroot00000000000000Shown. {{#person}} Never shown! {{/person}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/false_values.result000066400000000000000000000000071351023327000264100ustar00rootroot00000000000000Shown. erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/invarted.mustache000066400000000000000000000001031351023327000260430ustar00rootroot00000000000000{{#repo}} {{name}} {{/repo}} {{^repo}} No repos :( {{/repo}}erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/invarted.result000066400000000000000000000000141351023327000255510ustar00rootroot00000000000000No repos :( erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/lambdas.mustache000066400000000000000000000000551351023327000256400ustar00rootroot00000000000000{{#wrapped}}{{name}} is awesome.{{/wrapped}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/lambdas.result000066400000000000000000000000311351023327000253370ustar00rootroot00000000000000Willy is awesome. erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/non-empty.mustache000066400000000000000000000000441351023327000261610ustar00rootroot00000000000000{{#repo}} {{name}} {{/repo}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/non-empty.result000066400000000000000000000000441351023327000256660ustar00rootroot00000000000000resque hub rip erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/non-false.mustache000066400000000000000000000000471351023327000261200ustar00rootroot00000000000000{{#person?}} Hi {{name}}! {{/person?}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/non-false.result000066400000000000000000000000101351023327000256130ustar00rootroot00000000000000Hi Jon! erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/not_found_partial.mustache000066400000000000000000000001141351023327000277400ustar00rootroot00000000000000It is not_found_partial.mustache. begin {{> does_not_exist_template}} end erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/not_found_partial.result000066400000000000000000000000541351023327000274500ustar00rootroot00000000000000It is not_found_partial.mustache. begin end erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/partial.mustache000066400000000000000000000000561351023327000256720ustar00rootroot00000000000000

Names

{{#names}}{{> user}}{{/names}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/partial.result000066400000000000000000000000741351023327000253770ustar00rootroot00000000000000

Names

alice bob erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/user.mustache000066400000000000000000000000321351023327000252060ustar00rootroot00000000000000{{name}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/variables.mustache000066400000000000000000000000571351023327000262070ustar00rootroot00000000000000*{{name}} *{{age}} *{{company}} *{{{company}}} erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_SUITE_data/variables.result000066400000000000000000000000631351023327000257110ustar00rootroot00000000000000*Chris * *<b>GitHub</b> *GitHub erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_eunit_SUITE.erl000066400000000000000000000015401351023327000234210ustar00rootroot00000000000000%% @copyright 2015 Hinagiku Soranoba All Rights Reserved. -module(bbmustache_eunit_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -export([ all/0, eunit_ct/1 ]). %%---------------------------------------------------------------------------------------------------------------------- %% 'common_test' Callback API %%---------------------------------------------------------------------------------------------------------------------- all() -> [eunit_ct]. %%---------------------------------------------------------------------------------------------------------------------- %% Common Test Functions %%---------------------------------------------------------------------------------------------------------------------- eunit_ct(_Config) -> ok = eunit:test({application, bbmustache}). erlang-bbmustache-1.6.1+dfsg/ct/bbmustache_spec_SUITE.erl000066400000000000000000000060111351023327000232250ustar00rootroot00000000000000%% @copyright 2016 Hinagiku Soranoba All Rights Reserved. %% %% This common test is checked that bbmustache meets the reference implementation. %% %% Reference implementation: %% https://github.com/mustache/spec %% -module(bbmustache_spec_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(SKIP_FILES, [ "~lambdas.json" ]). -define(SKIP_CASES, [ ]). %%---------------------------------------------------------------------------------------------------------------------- %% 'common_test' Callback API %%---------------------------------------------------------------------------------------------------------------------- all() -> [specs]. specs(Config) -> Jsons = case filelib:wildcard("../../**/mustache_spec.app") of [AppPath | _] -> filelib:wildcard(filename:join([filename:absname(filename:dirname(AppPath)), "..", "specs", "*.json"])); [] -> ct:log("mustache_spec.app is not found. please check the deps."), ?assert(spec_is_not_found) end, %% NOTE: priv_dir did write partial files. ok = file:set_cwd(?config(priv_dir, Config)), lists:foreach(fun ?MODULE:spec_tests/1, Jsons). spec_tests(JsonPath) -> ct:log("---- ~s -----", [Basename = filename:basename(JsonPath)]), case lists:member(Basename, ?SKIP_FILES) of true -> ct:log("This test case did skip..."); false -> {ok, JsonBin} = file:read_file(JsonPath), JsonDec = jsone:decode(JsonBin, [{object_format, proplist}]), Tests0 = proplists:get_value(<<"tests">>, JsonDec), SkipTests = proplists:get_all_values(Basename, ?SKIP_CASES), Tests = lists:foldl( fun(T, Acc) -> case lists:member(proplists:get_value(<<"name">>, T), SkipTests) of true -> Acc; false -> [T | Acc] end end, [], lists:reverse(Tests0)), lists:foreach(fun ?MODULE:spec_test/1, Tests) end. spec_test(Assoc) -> Name = proplists:get_value(<<"name">>, Assoc), Data = proplists:get_value(<<"data">>, Assoc), Expected = proplists:get_value(<<"expected">>, Assoc), Template = proplists:get_value(<<"template">>, Assoc), Partials = proplists:get_value(<<"partials">>, Assoc, []), ok = clean_dir("."), ok = lists:foreach(fun ?MODULE:write_file/1, Partials), ct:log("CASE: ~s", [Name]), ?assertEqual(Expected, bbmustache:render(Template, Data, [{key_type, binary}])). clean_dir(Dir) -> lists:foreach(fun(F) -> file:delete(F) end, filelib:wildcard(filename:join(Dir, "*.mustache"))). write_file({PartialFilename, PartialData}) -> ok = file:write_file(<>, PartialData); write_file(_) -> ok. erlang-bbmustache-1.6.1+dfsg/doc/000077500000000000000000000000001351023327000165625ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/doc/README.md000066400000000000000000000002761351023327000200460ustar00rootroot00000000000000 # The bbmustache application # ## Modules ##
bbmustache
erlang-bbmustache-1.6.1+dfsg/doc/bbmustache.md000066400000000000000000000144311351023327000212240ustar00rootroot00000000000000 # Module bbmustache # * [Description](#description) * [Data Types](#types) * [Function Index](#index) * [Function Details](#functions) Binary pattern match Based Mustach template engine for Erlang/OTP. Copyright (c) 2015 Hinagiku Soranoba All Rights Reserved. ## Description ## Please refer to [the man page](http://mustache.github.io/mustache.5.html) and [the spec](https://github.com/mustache/spec) of mustache as the need arises.
Please see [this](../benchmarks/README.md) for a list of features that bbmustache supports. ## Data Types ## ### assoc_data() ###

assoc_data() = [{atom(), data_value()}] | [{binary(), data_value()}] | [{string(), data_value()}]
### compile_option() ###

compile_option() = {key_type, atom | binary | string} | raise_on_context_miss | {escape_fun, fun((binary()) -> binary())}
- key_type: Specify the type of the key in [`data/0`](#data-0). Default value is `string`. - raise_on_context_miss: If key exists in template does not exist in data, it will throw an exception (error). - escape_fun: Specify your own escape function. ### data() ###

data() = assoc_data()
### data_value() ###

data_value() = data() | iodata() | number() | atom() | fun((data(), function()) -> iodata())
Function is intended to support a lambda expression. ### option() ###

option() = compile_option()
This type has been deprecated since 1.6.0. It will remove in 2.0.0. ### parse_option() ###

parse_option() = raise_on_partial_miss
- raise_on_partial_miss: If the template used in partials does not found, it will throw an exception (error). ### render_option() ###

render_option() = compile_option() | parse_option()
### template() ### __abstract datatype__: `template()` ## Function Index ##
compile/2Equivalent to compile(Template, Data, []).
compile/3Embed the data in the template.
parse_binary/1Equivalent to parse_binary(Bin, []).
parse_binary/2Create a template/0 from a binary.
parse_file/1Equivalent to parse_file(Filename, []).
parse_file/2Create a template/0 from a file.
render/2Equivalent to render(Bin, Data, []).
render/3Equivalent to compile(parse_binary(Bin), Data, Options).
## Function Details ## ### compile/2 ###

compile(Template::template(), Data::data()) -> binary()

Equivalent to [`compile(Template, Data, [])`](#compile-3). ### compile/3 ###

compile(Bbmustache::template(), Data::data(), Options::[compile_option()]) -> binary()

Embed the data in the template. ``` 1> Template = bbmustache:parse_binary(<<"{{name}}">>). 2> bbmustache:compile(Template, #{"name" => "Alice"}). <<"Alice">> ``` Data support an associative array or a map.
All keys MUST be same type. ### parse_binary/1 ###

parse_binary(Bin::binary()) -> template()

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

parse_binary(Bin::binary(), Options::[parse_option()]) -> template()

Create a [`template/0`](#template-0) from a binary. ### parse_file/1 ###

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

Equivalent to [`parse_file(Filename, [])`](#parse_file-2). ### parse_file/2 ###

parse_file(Filename::file:filename_all(), Options::[parse_option()]) -> template()

Create a [`template/0`](#template-0) from a file. ### render/2 ###

render(Bin::binary(), Data::data()) -> binary()

Equivalent to [`render(Bin, Data, [])`](#render-3). __See also:__ [compile/2](#compile-2), [compile_option/0](#compile_option-0), [parse_binary/1](#parse_binary-1), [parse_file/1](#parse_file-1), [parse_option/0](#parse_option-0), [render/2](#render-2). ### render/3 ###

render(Bin::binary(), Data::data(), Options::[render_option()]) -> binary()

Equivalent to [`compile(parse_binary(Bin), Data, Options)`](#compile-3). erlang-bbmustache-1.6.1+dfsg/rebar.config000066400000000000000000000026251351023327000203040ustar00rootroot00000000000000%% vim: set filetype=erlang : -*- erlang -*- {erl_opts, [ {platform_define, "^[0-9]+", namespaced_types}, warnings_as_errors, warn_export_all, warn_untyped_record ]}. {xref_checks, [ fail_on_warning, undefined_function_calls ]}. {cover_enabled, true}. {edoc_opts, [ {doclet, edown_doclet}, {dialyzer_specs, all}, {report_missing_type, true}, {report_type_mismatch, true}, {pretty_print, erl_pp}, {preprocess, true} ]}. {validate_app_modules, true}. {ct_opts, [{dir, "ct"}]}. {profiles, [{test, [{erl_opts, [export_all]}, {deps, [ {jsone, "1.4.6"}, {mustache_spec, ".*", {git, "git://github.com/soranoba/spec.git", {tag, "v1.1.3-erl"}}} ]}, {plugins, [rebar3_raw_deps]} ]}, {dev, [{deps, [ {edown, ".*", {git, "git://github.com/uwiger/edown.git", {branch, "master"}}} ]} ]}, {bench, [{deps, [ {mustache, ".*", {git, "git://github.com/mojombo/mustache.erl", {tag, "v0.1.1"}}} ]} ]} ]}. erlang-bbmustache-1.6.1+dfsg/rebar.lock000066400000000000000000000000041351023327000177540ustar00rootroot00000000000000[]. erlang-bbmustache-1.6.1+dfsg/src/000077500000000000000000000000001351023327000166045ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/src/bbmustache.app.src000066400000000000000000000006411351023327000222120ustar00rootroot00000000000000{application,bbmustache, [{description,"Binary pattern match Based Mustache template engine for Erlang/OTP"}, {vsn,"1.6.1"}, {registered,[]}, {applications,[kernel,stdlib]}, {maintainers,["Hinagiku Soranoba"]}, {licenses,["MIT"]}, {links,[{"GitHub","https://github.com/soranoba/bbmustache"}]}, {env,[]}]}. erlang-bbmustache-1.6.1+dfsg/src/bbmustache.erl000066400000000000000000000677311351023327000214430ustar00rootroot00000000000000%% @copyright 2015 Hinagiku Soranoba All Rights Reserved. %% %% @doc Binary pattern match Based Mustach template engine for Erlang/OTP. %% %% Please refer to [the man page](http://mustache.github.io/mustache.5.html) and [the spec](https://github.com/mustache/spec) of mustache as the need arises.
%% %% Please see [this](../benchmarks/README.md) for a list of features that bbmustache supports. %% -module(bbmustache). %%---------------------------------------------------------------------------------------------------------------------- %% Exported API %%---------------------------------------------------------------------------------------------------------------------- -export([ render/2, render/3, parse_binary/1, parse_binary/2, parse_file/1, parse_file/2, compile/2, compile/3 ]). -export_type([ template/0, data/0, option/0, % deprecated compile_option/0, parse_option/0, render_option/0 ]). %%---------------------------------------------------------------------------------------------------------------------- %% Defines & Records & Types %%---------------------------------------------------------------------------------------------------------------------- -define(PARSE_ERROR, incorrect_format). -define(FILE_ERROR, file_not_found). -define(CONTEXT_MISSING_ERROR(Msg), {context_missing, Msg}). -define(IIF(Cond, TValue, FValue), case Cond of true -> TValue; false -> FValue end). -define(ADD(X, Y), ?IIF(X =:= <<>>, Y, [X | Y])). -define(START_TAG, <<"{{">>). -define(STOP_TAG, <<"}}">>). -define(RAISE_ON_CONTEXT_MISS_ENABLED(Options), proplists:get_bool(raise_on_context_miss, Options)). -define(RAISE_ON_PARTIAL_MISS_ENABLED(Options), proplists:get_bool(raise_on_partial_miss, Options)). -define(PARSE_OPTIONS, [raise_on_partial_miss]). -type key() :: binary(). %% Key MUST be a non-whitespace character sequence NOT containing the current closing delimiter.
%% %% In addition, `.' have a special meaning.
%% (1) `parent.child' ... find the child in the parent.
%% (2) `.' ... It means this. However, the type of correspond is only `[integer() | float() | binary() | string() | atom()]'. Otherwise, the behavior is undefined. %% -type source() :: binary(). %% If you use lamda expressions, the original text is necessary. %% %% ``` %% e.g. %% template: %% {{#lamda}}a{{b}}c{{/lamda}} %% parse result: %% {'#', <<"lamda">>, [<<"a">>, {'n', <<"b">>}, <<"c">>], <<"a{{b}}c">>} %% ''' %% %% NOTE: %% Since the binary reference is used internally, it is not a capacitively large waste. %% However, the greater the number of tags used, it should use the wasted memory. -type tag() :: {n, [key()]} | {'&', [key()]} | {'#', [key()], [tag()], source()} | {'^', [key()], [tag()]} | {'>', key(), Indent :: source()} | binary(). % plain text -record(?MODULE, { data :: [tag()], partials = [] :: [{key(), [tag()]} | key()], %% The `{key(), [tag()]}` indicates that `key()` already parsed and `[tag()]` is the result of parsing. %% The `key()` indicates that the file did not exist. options = [] :: [compile_option()], indents = [] :: [binary()], context_stack = [] :: [data()] }). -opaque template() :: #?MODULE{}. %% @see parse_binary/1 %% @see parse_file/1 -record(state, { dirname = <<>> :: file:filename_all(), start = ?START_TAG :: binary(), stop = ?STOP_TAG :: binary(), partials = [] :: [key()], standalone = true :: boolean() }). -type state() :: #state{}. -type data_key() :: atom() | binary() | string(). %% You can choose one from these as the type of key in {@link data/0}. -type data_value() :: data() | iodata() | number() | atom() | fun((data(), function()) -> iodata()). %% Function is intended to support a lambda expression. -type assoc_data() :: [{atom(), data_value()}] | [{binary(), data_value()}] | [{string(), data_value()}]. -type parse_option() :: raise_on_partial_miss. %% - raise_on_partial_miss: If the template used in partials does not found, it will throw an exception (error). -type compile_option() :: {key_type, atom | binary | string} | raise_on_context_miss | {escape_fun, fun((binary()) -> binary())}. %% - key_type: Specify the type of the key in {@link data/0}. Default value is `string'. %% - raise_on_context_miss: If key exists in template does not exist in data, it will throw an exception (error). %% - escape_fun: Specify your own escape function. -type render_option() :: compile_option() | parse_option(). %% @see compile_option/0 %% @see parse_option/0 -type option() :: compile_option(). %% This type has been deprecated since 1.6.0. It will remove in 2.0.0. %% @see compile_option/0 -ifdef(namespaced_types). -type maps_data() :: #{atom() => data_value()} | #{binary() => data_value()} | #{string() => data_value()}. -type data() :: maps_data() | assoc_data(). -else. -type data() :: assoc_data(). -endif. %% All keys MUST be same type. %% @see render/2 %% @see compile/2 -type endtag() :: {endtag, {state(), [key()], LastTagSize :: non_neg_integer(), Rest :: binary(), Result :: [tag()]}}. %%---------------------------------------------------------------------------------------------------------------------- %% Exported Functions %%---------------------------------------------------------------------------------------------------------------------- %% @equiv render(Bin, Data, []) -spec render(binary(), data()) -> binary(). render(Bin, Data) -> render(Bin, Data, []). %% @equiv compile(parse_binary(Bin), Data, Options) -spec render(binary(), data(), [render_option()]) -> binary(). render(Bin, Data, Options) -> {ParseOptions, CompileOptions} = lists:partition(fun(X) -> lists:member(X, ?PARSE_OPTIONS) end, Options), compile(parse_binary(Bin, ParseOptions), Data, CompileOptions). %% @equiv parse_binary(Bin, []) -spec parse_binary(binary()) -> template(). parse_binary(Bin) when is_binary(Bin) -> parse_binary(Bin, []). %% @doc Create a {@link template/0} from a binary. -spec parse_binary(binary(), [parse_option()]) -> template(). parse_binary(Bin, Options) -> {State, Data} = parse(#state{}, Bin), parse_remaining_partials(State, #?MODULE{data = Data}, Options). %% @equiv parse_file(Filename, []) -spec parse_file(file:filename_all()) -> template(). parse_file(Filename) -> parse_file(Filename, []). %% @doc Create a {@link template/0} from a file. -spec parse_file(file:filename_all(), [parse_option()]) -> template(). parse_file(Filename, Options) -> State = #state{dirname = filename:dirname(Filename)}, case file:read_file(Filename) of {ok, Bin} -> {State1, Data} = parse(State, Bin), Template = case to_binary(filename:extension(Filename)) of <<".mustache">> = Ext -> #?MODULE{partials = [{filename:basename(Filename, Ext), Data}], data = Data}; _ -> #?MODULE{data = Data} end, parse_remaining_partials(State1, Template, Options); _ -> error(?FILE_ERROR, [Filename, Options]) end. %% @equiv compile(Template, Data, []) -spec compile(template(), data()) -> binary(). compile(Template, Data) -> compile(Template, Data, []). %% @doc Embed the data in the template. %% %% ``` %% 1> Template = bbmustache:parse_binary(<<"{{name}}">>). %% 2> bbmustache:compile(Template, #{"name" => "Alice"}). %% <<"Alice">> %% ''' %% Data support an associative array or a map.
%% All keys MUST be same type. -spec compile(template(), data(), [compile_option()]) -> binary(). compile(#?MODULE{data = Tags} = T, Data, Options) -> case check_data_type(Data) of false -> error(function_clause, [T, Data]); _ -> Ret = compile_impl(Tags, Data, [], T#?MODULE{options = Options, data = []}), iolist_to_binary(lists:reverse(Ret)) end. %%---------------------------------------------------------------------------------------------------------------------- %% Internal Function %%---------------------------------------------------------------------------------------------------------------------- %% @doc {@link compile/2} %% %% ATTENTION: The result is a list that is inverted. -spec compile_impl(Template :: [tag()], data(), Result :: iodata(), template()) -> iodata(). compile_impl([], _, Result, _) -> Result; compile_impl([{n, Keys} | T], Map, Result, State) -> Value = iolist_to_binary(to_iodata(get_data_recursive(Keys, Map, <<>>, State))), EscapeFun = proplists:get_value(escape_fun, State#?MODULE.options, fun escape/1), compile_impl(T, Map, ?ADD(EscapeFun(Value), Result), State); compile_impl([{'&', Keys} | T], Map, Result, State) -> compile_impl(T, Map, ?ADD(to_iodata(get_data_recursive(Keys, Map, <<>>, State)), Result), State); compile_impl([{'#', Keys, Tags, Source} | T], Map, Result, State) -> Value = get_data_recursive(Keys, Map, false, State), NestedState = State#?MODULE{context_stack = [Map | State#?MODULE.context_stack]}, case check_data_type(Value) of true -> compile_impl(T, Map, compile_impl(Tags, Value, Result, NestedState), State); _ when is_list(Value) -> compile_impl(T, Map, lists:foldl(fun(X, Acc) -> compile_impl(Tags, X, Acc, NestedState) end, Result, Value), State); _ when Value =:= false -> compile_impl(T, Map, Result, State); _ when is_function(Value, 2) -> Ret = Value(Source, fun(Text) -> render(Text, Map, State#?MODULE.options) end), compile_impl(T, Map, ?ADD(Ret, Result), State); _ -> compile_impl(T, Map, compile_impl(Tags, Map, Result, State), State) end; compile_impl([{'^', Keys, Tags} | T], Map, Result, State) -> Value = get_data_recursive(Keys, Map, false, State), case Value =:= [] orelse Value =:= false of true -> compile_impl(T, Map, compile_impl(Tags, Map, Result, State), State); false -> compile_impl(T, Map, Result, State) end; compile_impl([{'>', Key, Indent} | T], Map, Result0, #?MODULE{partials = Partials} = State) -> case proplists:get_value(Key, Partials) of undefined -> case ?RAISE_ON_CONTEXT_MISS_ENABLED(State#?MODULE.options) of true -> error(?CONTEXT_MISSING_ERROR({?FILE_ERROR, Key})); false -> compile_impl(T, Map, Result0, State) end; PartialT -> Indents = State#?MODULE.indents ++ [Indent], Result1 = compile_impl(PartialT, Map, [Indent | Result0], State#?MODULE{indents = Indents}), compile_impl(T, Map, Result1, State) end; compile_impl([B1 | [_|_] = T], Map, Result, #?MODULE{indents = Indents} = State) when Indents =/= [] -> %% NOTE: indent of partials case byte_size(B1) > 0 andalso binary:last(B1) of $\n -> compile_impl(T, Map, [Indents, B1 | Result], State); _ -> compile_impl(T, Map, [B1 | Result], State) end; compile_impl([Bin | T], Map, Result, State) -> compile_impl(T, Map, [Bin | Result], State). %% @doc Parse remaining partials in State. It returns {@link template/0}. -spec parse_remaining_partials(state(), template(), [parse_option()]) -> template(). parse_remaining_partials(#state{partials = []}, Template = #?MODULE{}, _Options) -> Template; parse_remaining_partials(State = #state{partials = [P | PartialKeys]}, Template = #?MODULE{partials = Partials}, Options) -> case proplists:is_defined(P, Partials) of true -> parse_remaining_partials(State#state{partials = PartialKeys}, Template, Options); false -> Filename0 = <

>, Dirname = State#state.dirname, Filename = ?IIF(Dirname =:= <<>>, Filename0, filename:join([Dirname, Filename0])), case file:read_file(Filename) of {ok, Input} -> {State1, Data} = parse(State, Input), parse_remaining_partials(State1, Template#?MODULE{partials = [{P, Data} | Partials]}, Options); {error, Reason} -> case ?RAISE_ON_PARTIAL_MISS_ENABLED(Options) of true -> error({?FILE_ERROR, P, Reason}); false -> parse_remaining_partials(State#state{partials = PartialKeys}, Template#?MODULE{partials = [P | Partials]}, Options) end end end. %% @doc Analyze the syntax of the mustache. -spec parse(state(), binary()) -> {#state{}, [tag()]}. parse(State0, Bin) -> case parse1(State0, Bin, []) of {endtag, {_, Keys, _, _, _}} -> error({?PARSE_ERROR, {section_is_incorrect, binary_join(Keys, <<".">>)}}); {#state{partials = Partials} = State, Tags} -> {State#state{partials = lists:usort(Partials), start = ?START_TAG, stop = ?STOP_TAG}, lists:reverse(Tags)} end. %% @doc Part of the `parse/1' %% %% ATTENTION: The result is a list that is inverted. -spec parse1(state(), Input :: binary(), Result :: [tag()]) -> {state(), [tag()]} | endtag(). parse1(#state{start = Start} = State, Bin, Result) -> case binary:match(Bin, [Start, <<"\n">>]) of nomatch -> {State, ?ADD(Bin, Result)}; {S, L} -> Pos = S + L, B2 = binary:part(Bin, Pos, byte_size(Bin) - Pos), case binary:at(Bin, S) of $\n -> parse1(State#state{standalone = true}, B2, ?ADD(binary:part(Bin, 0, Pos), Result)); % \n _ -> parse2(State, split_tag(State, Bin), Result) end end. %% @doc Part of the `parse/1' %% %% ATTENTION: The result is a list that is inverted. -spec parse2(state(), iolist(), Result :: [tag()]) -> {state(), [tag()]} | endtag(). parse2(State, [B1, B2, B3], Result) -> case remove_space_from_head(B2) of <> when T =:= $&; T =:= ${ -> parse1(State#state{standalone = false}, B3, [{'&', keys(Tag)} | ?ADD(B1, Result)]); <> when T =:= $#; T =:= $^ -> parse_loop(State, ?IIF(T =:= $#, '#', '^'), keys(Tag), B3, [B1 | Result]); <<"=", Tag0/binary>> -> Tag1 = remove_space_from_tail(Tag0), Size = byte_size(Tag1) - 1, case Size >= 0 andalso Tag1 of <>}}) end; <<"!", _/binary>> -> parse3(State, B3, [B1 | Result]); <<"/", Tag/binary>> -> EndTagSize = byte_size(B2) + byte_size(State#state.start) + byte_size(State#state.stop), {endtag, {State, keys(Tag), EndTagSize, B3, [B1 | Result]}}; <<">", Tag/binary>> -> parse_jump(State, filename_key(Tag), B3, [B1 | Result]); Tag -> parse1(State#state{standalone = false}, B3, [{n, keys(Tag)} | ?ADD(B1, Result)]) end; parse2(_, _, _) -> error({?PARSE_ERROR, unclosed_tag}). %% @doc Part of the `parse/1' %% %% it is end processing of tag that need to be considered the standalone. -spec parse3(#state{}, binary(), [tag()]) -> {state(), [tag()]} | endtag(). parse3(State0, Post0, [Tag | Result0]) when is_tuple(Tag) -> {State1, _, Post1, Result1} = standalone(State0, Post0, Result0), parse1(State1, Post1, [Tag | Result1]); parse3(State0, Post0, Result0) -> {State1, _, Post1, Result1} = standalone(State0, Post0, Result0), parse1(State1, Post1, Result1). %% @doc Loop processing part of the `parse/1' %% %% `{{# Tag}}' or `{{^ Tag}}' corresponds to this. -spec parse_loop(state(), '#' | '^', [key()], Input :: binary(), Result :: [tag()]) -> {state(), [tag()]} | endtag(). parse_loop(State0, Mark, Keys, Input0, Result0) -> {State1, _, Input1, Result1} = standalone(State0, Input0, Result0), case parse1(State1, Input1, []) of {endtag, {State2, Keys, LastTagSize, Rest0, LoopResult0}} -> {State3, _, Rest1, LoopResult1} = standalone(State2, Rest0, LoopResult0), case Mark of '#' -> Source = binary:part(Input1, 0, byte_size(Input1) - byte_size(Rest1) - LastTagSize), parse1(State3, Rest1, [{'#', Keys, lists:reverse(LoopResult1), Source} | Result1]); '^' -> parse1(State3, Rest1, [{'^', Keys, lists:reverse(LoopResult1)} | Result1]) end; {endtag, {_, OtherKeys, _, _, _}} -> error({?PARSE_ERROR, {section_is_incorrect, binary_join(OtherKeys, <<".">>)}}); _ -> error({?PARSE_ERROR, {section_end_tag_not_found, <<"/", (binary_join(Keys, <<".">>))/binary>>}}) end. %% @doc Endtag part of the `parse/1' -spec parse_jump(state(), Tag :: binary(), NextBin :: binary(), Result :: [tag()]) -> {state(), [tag()]} | endtag(). parse_jump(State0, Tag, NextBin0, Result0) -> {State1, Indent, NextBin1, Result1} = standalone(State0, NextBin0, Result0), State2 = State1#state{partials = [Tag | State1#state.partials]}, parse1(State2, NextBin1, [{'>', Tag, Indent} | Result1]). %% @doc Update delimiter part of the `parse/1' %% %% ParseDelimiterBin :: e.g. `{{=%% %%=}}' -> `%% %%' -spec parse_delimiter(state(), ParseDelimiterBin :: binary(), NextBin :: binary(), Result :: [tag()]) -> {state(), [tag()]} | endtag(). parse_delimiter(State0, ParseDelimiterBin, NextBin, Result) -> case binary:match(ParseDelimiterBin, <<"=">>) of nomatch -> case [X || X <- binary:split(ParseDelimiterBin, <<" ">>, [global]), X =/= <<>>] of [Start, Stop] -> parse3(State0#state{start = Start, stop = Stop}, NextBin, Result); _ -> error({?PARSE_ERROR, delimiters_may_not_contain_whitespaces}) end; _ -> error({?PARSE_ERROR, delimiters_may_not_contain_equals}) end. %% @doc Split by the tag, it returns a list of the split binary. %% %% e.g. %% ``` %% 1> split_tag(State, <<"...{{hoge}}...">>). %% [<<"...">>, <<"hoge">>, <<"...">>] %% %% 2> split_tag(State, <<"...{{hoge ...">>). %% [<<"...">>, <<"hoge ...">>] %% %% 3> split_tag(State, <<"...">>) %% [<<"...">>] %% ''' -spec split_tag(state(), binary()) -> [binary(), ...]. split_tag(#state{start = StartDelimiter, stop = StopDelimiter}, Bin) -> case binary:match(Bin, StartDelimiter) of nomatch -> [Bin]; {StartPos, StartDelimiterLen} -> PosLimit = byte_size(Bin) - StartDelimiterLen, ShiftNum = while({true, StartPos + 1}, fun(Pos) -> ?IIF(Pos =< PosLimit andalso binary:part(Bin, Pos, StartDelimiterLen) =:= StartDelimiter, {true, Pos + 1}, {false, Pos}) end) - StartPos - 1, {PreTag, X} = split_binary(Bin, StartPos + ShiftNum), Tag0 = part(X, StartDelimiterLen, 0), case binary:split(Tag0, StopDelimiter) of [_] -> [PreTag, Tag0]; % not found. [Tag, Rest] -> IncludeStartDelimiterTag = binary:part(X, 0, byte_size(Tag) + StartDelimiterLen), E = ?IIF(repeatedly_binary(StopDelimiter, $}), ?IIF(byte_size(Rest) > 0 andalso binary:first(Rest) =:= $}, 1, 0), ?IIF(byte_size(Tag) > 0 andalso binary:last(Tag) =:= $}, -1, 0)), S = ?IIF(repeatedly_binary(StartDelimiter, ${), ?IIF(ShiftNum > 0, -1, 0), ?IIF(byte_size(Tag) > 0 andalso binary:first(Tag) =:= ${, 1, 0)), case E =:= 0 orelse S =:= 0 of true -> % {{ ... }} [PreTag, Tag, Rest]; false -> % {{{ ... }}} [part(PreTag, 0, min(0, S)), part(IncludeStartDelimiterTag, max(0, S) + StartDelimiterLen - 1, min(0, E)), part(Rest, max(0, E), 0)] end end end. %% @doc if it is standalone line, remove spaces from edge. -spec standalone(#state{}, binary(), [tag()]) -> {#state{}, StashPre :: binary(), Post :: binary(), [tag()]}. standalone(#state{standalone = false} = State, Post, [Pre | Result]) -> {State, <<>>, Post, ?ADD(Pre, Result)}; standalone(#state{standalone = false} = State, Post, Result) -> {State, <<>>, Post, Result}; standalone(State, Post0, Result0) -> {Pre, Result1} = case Result0 =/= [] andalso hd(Result0) of Pre0 when is_binary(Pre0) -> {Pre0, tl(Result0)}; _ -> {<<>>, Result0} end, case remove_indent_from_head(Pre) =:= <<>> andalso remove_indent_from_head(Post0) of <<"\r\n", Post1/binary>> -> {State, Pre, Post1, Result1}; <<"\n", Post1/binary>> -> {State, Pre, Post1, Result1}; <<>> -> {State, Pre, <<>>, Result1}; _ -> {State#state{standalone = false}, <<>>, Post0, ?ADD(Pre, Result1)} end. %% @doc If the binary is repeatedly the character, return true. Otherwise, return false. -spec repeatedly_binary(binary(), byte()) -> boolean(). repeatedly_binary(<>, X) -> repeatedly_binary(Rest, X); repeatedly_binary(<<>>, _) -> true; repeatedly_binary(_, _) -> false. %% @doc During the first element of the tuple is true, to perform the function repeatedly. -spec while({boolean(), term()}, fun((term()) -> {boolean(), term()})) -> term(). while({true, Value}, Fun) -> while(Fun(Value), Fun); while({false, Value}, _Fun) -> Value. %% @equiv binary:part(X, Start, byte_size(X) - Start + End) -spec part(binary(), non_neg_integer(), 0 | neg_integer()) -> binary(). part(X, Start, End) when End =< 0 -> binary:part(X, Start, byte_size(X) - Start + End). %% @doc binary to keys -spec keys(binary()) -> [key()]. keys(Bin0) -> Bin1 = << <> || <> <= Bin0, X =/= $ >>, case Bin1 =:= <<>> orelse Bin1 =:= <<".">> of true -> [Bin1]; false -> [X || X <- binary:split(Bin1, <<".">>, [global]), X =/= <<>>] end. %% @doc binary to filename key -spec filename_key(binary()) -> key(). filename_key(Bin) -> remove_space_from_tail(remove_space_from_head(Bin)). %% @doc Function for binary like the `string:join/2'. -spec binary_join(BinaryList :: [binary()], Separator :: binary()) -> binary(). binary_join([], _) -> <<>>; binary_join(Bins, Sep) -> [Hd | Tl] = [ [Sep, B] || B <- Bins ], iolist_to_binary([tl(Hd) | Tl]). %% @doc Remove the space from the head. -spec remove_space_from_head(binary()) -> binary(). remove_space_from_head(<<" ", Rest/binary>>) -> remove_space_from_head(Rest); remove_space_from_head(Bin) -> Bin. %% @doc Remove the indent from the head. -spec remove_indent_from_head(binary()) -> binary(). remove_indent_from_head(<>) when X =:= $\t; X =:= $ -> remove_indent_from_head(Rest); remove_indent_from_head(Bin) -> Bin. %% @doc Remove the space from the tail. -spec remove_space_from_tail(binary()) -> binary(). remove_space_from_tail(<<>>) -> <<>>; remove_space_from_tail(Bin) -> PosList = binary:matches(Bin, <<" ">>), LastPos = remove_space_from_tail_impl(lists:reverse(PosList), byte_size(Bin)), binary:part(Bin, 0, LastPos). %% @see remove_space_from_tail/1 -spec remove_space_from_tail_impl([{non_neg_integer(), pos_integer()}], non_neg_integer()) -> non_neg_integer(). remove_space_from_tail_impl([{X, Y} | T], Size) when Size =:= X + Y -> remove_space_from_tail_impl(T, X); remove_space_from_tail_impl(_, Size) -> Size. %% @doc term to iodata -spec to_iodata(number() | binary() | string() | atom()) -> iodata(). to_iodata(Integer) when is_integer(Integer) -> list_to_binary(integer_to_list(Integer)); to_iodata(Float) when is_float(Float) -> %% NOTE: It is the same behaviour as io_lib:format("~p", [Float]), but it is fast than. %% http://www.cs.indiana.edu/~dyb/pubs/FP-Printing-PLDI96.pdf io_lib_format:fwrite_g(Float); to_iodata(Atom) when is_atom(Atom) -> list_to_binary(atom_to_list(Atom)); to_iodata(X) -> X. %% @doc string or binary to binary -spec to_binary(binary() | [byte()]) -> binary(). to_binary(Bin) when is_binary(Bin) -> Bin; to_binary(Bytes) when is_list(Bytes) -> list_to_binary(Bytes). %% @doc HTML Escape -spec escape(binary()) -> binary(). escape(Bin) -> << <<(escape_char(X))/binary>> || <> <= Bin >>. %% @doc escape a character if needed. -spec escape_char(byte()) -> <<_:8, _:_*8>>. escape_char($<) -> <<"<">>; escape_char($>) -> <<">">>; escape_char($&) -> <<"&">>; escape_char($") -> <<""">>; escape_char(C) -> <>. %% @doc convert to {@link data_key/0} from binary. -spec convert_keytype(key(), template()) -> data_key(). convert_keytype(KeyBin, #?MODULE{options = Options}) -> case proplists:get_value(key_type, Options, string) of atom -> try binary_to_existing_atom(KeyBin, utf8) of Atom -> Atom catch _:_ -> <<" ">> % It is not always present in data/0 end; string -> binary_to_list(KeyBin); binary -> KeyBin end. %% @doc fetch the value of the specified `Keys' from {@link data/0} %% %% - If `Keys' is `[<<".">>]', it returns `Data'. %% - If raise_on_context_miss enabled, it raise an exception when missing `Keys'. Otherwise, it returns `Default'. -spec get_data_recursive([key()], data(), Default :: term(), template()) -> term(). get_data_recursive(Keys, Data, Default, Template) -> case get_data_recursive_impl(Keys, Data, Template) of {ok, Term} -> Term; error -> case ?RAISE_ON_CONTEXT_MISS_ENABLED(Template#?MODULE.options) of true -> error(?CONTEXT_MISSING_ERROR({key, binary_join(Keys, <<".">>)})); false -> Default end end. %% @see get_data_recursive/4 -spec get_data_recursive_impl([key()], data(), template()) -> {ok, term()} | error. get_data_recursive_impl([], Data, _) -> {ok, Data}; get_data_recursive_impl([<<".">>], Data, _) -> {ok, Data}; get_data_recursive_impl([Key | RestKey] = Keys, Data, #?MODULE{context_stack = Stack} = State) -> case check_data_type(Data) =:= true andalso find_data(convert_keytype(Key, State), Data) of {ok, ChildData} -> get_data_recursive_impl(RestKey, ChildData, State#?MODULE{context_stack = []}); _ when Stack =:= [] -> error; _ -> get_data_recursive_impl(Keys, hd(Stack), State#?MODULE{context_stack = tl(Stack)}) end. %% @doc find the value of the specified key from {@link data/0} -spec find_data(data_key(), data() | term()) -> {ok, Value ::term()} | error. -ifdef(namespaced_types). find_data(Key, Map) when is_map(Map) -> maps:find(Key, Map); find_data(Key, AssocList) -> case proplists:lookup(Key, AssocList) of none -> error; {_, V} -> {ok, V} end. -else. find_data(Key, AssocList) -> case proplists:lookup(Key, AssocList) of none -> error; {_, V} -> {ok, V} end. -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([Tuple | _]) when is_tuple(Tuple) -> true; check_data_type(Map) -> is_map(Map). -else. check_data_type([]) -> maybe; check_data_type([Tuple | _]) when is_tuple(Tuple) -> true; check_data_type(_) -> false. -endif. erlang-bbmustache-1.6.1+dfsg/test/000077500000000000000000000000001351023327000167745ustar00rootroot00000000000000erlang-bbmustache-1.6.1+dfsg/test/bbmustache_tests.erl000066400000000000000000000261251351023327000230450ustar00rootroot00000000000000%% @copyright 2015 Hinagiku Soranoba All Rights Reserved. -module(bbmustache_tests). -include_lib("eunit/include/eunit.hrl"). %%---------------------------------------------------------------------------------------------------------------------- %% Unit Tests %%---------------------------------------------------------------------------------------------------------------------- -define(PARSE_ERROR, incorrect_format). -define(FILE_ERROR, file_not_found). -define(NT_S(X, Y), ?_assertMatch({_, X, _, _, _, _}, bbmustache:parse_binary(Y))). %% parse_binary_test generater (success case) -define(NT_F(X, Y), ?_assertError(X, bbmustache:parse_binary(Y))). %% parse_binary_test generater (failure case) parse_file_test_() -> [ {"file_not_exist (without extension)", ?_assertError(?FILE_ERROR, bbmustache:parse_file(<<"not_exist">>))}, {"file_not_exist (with extension)", ?_assertError(?FILE_ERROR, bbmustache:parse_file(<<"not_exist.mustache">>))} ]. parse_binary_test_() -> [ {"bbmustache:template/0 format check", ?NT_S([], <<>>)}, {"{{tag}}", ?NT_S([<<"a">>, {n, [<<"t">>]}, <<"b">>], <<"a{{t}}b">>)}, {"{{ tag }}", ?NT_S([{n, [<<"t">>]}], <<"{{ t }}">>)}, {"{{ ta g }}", ?NT_S([{n, [<<"tag">>]}], <<"{{ ta g }}">>)}, {"{{}}", ?NT_S([{n, [<<>>]}], <<"{{}}">>)}, {"{{ }}", ?NT_S([{n, [<<>>]}], <<"{{ }}">>)}, {"{{tag", ?NT_F({?PARSE_ERROR, unclosed_tag}, <<"{{tag">>)}, {"{{{tag}}}", ?NT_S([<<"a">>, {'&', [<<"t">>]}, <<"b">>], <<"a{{{t}}}b">>)}, {"{{{ tag }}}", ?NT_S([{'&', [<<"t">>]}], <<"{{{ t }}}">>)}, {"{{{ ta g }}}",?NT_S([{'&', [<<"tag">>]}], <<"{{{ ta g }}}">>)}, {"{{{tag", ?NT_F({?PARSE_ERROR, unclosed_tag}, <<"{{{tag">>)}, {"{{{tag}} other}", ?NT_S([<<"{">>, {n, [<<"tag">>]}, <<" other}">>], <<"{{{tag}} other}">>)}, {"{{& tag}}", ?NT_S([<<"a">>, {'&', [<<"t">>]}, <<"b">>], <<"a{{& t}}b">>)}, {"{{ & tag }}", ?NT_S([{'&', [<<"t">>]}], <<"{{ & t }}">>)}, {"{{ & ta g }}",?NT_S([{'&', [<<"tag">>]}], <<"{{ & ta g }}">>)}, {"{{&ta g }}", ?NT_S([{'&', [<<"tag">>]}], <<"{{&ta g}}">>)}, {"{{&tag}}", ?NT_S([{'&', [<<"t">>]}], <<"{{&t}}">>)}, {"{{/tag}}", ?NT_F({?PARSE_ERROR, {section_is_incorrect, <<"tag">>}}, <<"{{/tag}}">>)}, {"{{#tag}}", ?NT_F({?PARSE_ERROR, {section_end_tag_not_found, <<"/tag">>}}, <<"{{#tag}}">>)}, {"{{#tag1}}{{#tag2}}{{name}}{{/tag1}}{{/tag2}}", ?NT_S([<<"a">>, {'#', [<<"t1">>], [<<"b">>, {'#', [<<"t2">>], [<<"c">>, {n, [<<"t3">>]}, <<"d">>], <<"c{{t3}}d">>}, <<"e">>], <<"b{{#t2}}c{{t3}}d{{/t2}}e">>}, <<"f">>], <<"a{{#t1}}b{{#t2}}c{{t3}}d{{/t2}}e{{/t1}}f">>)}, {"{{#tag1}}{{#tag2}}{{/tag1}}{{/tag2}}", ?NT_F({?PARSE_ERROR, {section_is_incorrect, <<"t1">>}}, <<"{{#t1}}{{#t2}}{{/t1}}{{/t2}}">>)}, {"{{# tag}}{{/ tag}}", ?NT_S([{'#', [<<"tag">>], [], <<>>}], <<"{{# tag}}{{/ tag}}">>)}, {"{{ #tag }}{{ / tag }}", ?NT_S([{'#', [<<"tag">>], [], <<>>}], <<"{{ #tag }}{{ / tag }}">>)}, {"{{ # tag }}{{ /tag }}", ?NT_S([{'#', [<<"tag">>], [], <<>>}], <<"{{ # tag }}{{ /tag }}">>)}, {"{{ # ta g}}{{ / ta g}}", ?NT_S([{'#', [<<"tag">>], [], <<>>}], <<"{{ # ta g}}{{ / ta g}}">>)}, {"{{!comment}}", ?NT_S([<<"a">>, <<"c">>], <<"a{{!comment}}c">>)}, {"{{! comment }}", ?NT_S([], <<"{{! comment }}">>)}, {"{{! co mmen t }}", ?NT_S([], <<"{{! co mmen t }}">>)}, {"{{ !comment }}", ?NT_S([], <<"{{ !comment }}">>)}, {" {{ !comment }} \r\n", ?NT_S([], <<" {{ !comment }} \r\n">>)}, {"{{^tag}}", ?NT_F({?PARSE_ERROR, {section_end_tag_not_found, <<"/tag">>}}, <<"a{{^tag}}b">>)}, {"{{^tag1}}{{^tag2}}{{name}}{{/tag2}}{{/tag1}}", ?NT_S([<<"a">>, {'^', [<<"t1">>], [<<"b">>, {'^', [<<"t2">>], [<<"c">>, {n, [<<"t3">>]}, <<"d">>]}, <<"e">>]}, <<"f">>], <<"a{{^t1}}b{{^t2}}c{{t3}}d{{/t2}}e{{/t1}}f">>)}, {"{{^tag1}}{{^tag2}}{{/tag1}}{{tag2}}", ?NT_F({?PARSE_ERROR, {section_is_incorrect, <<"t1">>}}, <<"{{^t1}}{{^t2}}{{/t1}}{{/t2}}">>)}, {"{{^ tag}}{{/ tag}}", ?NT_S([{'^', [<<"tag">>], []}], <<"{{^ tag}}{{/ tag}}">>)}, {"{{ ^tag }}{{ / tag }}", ?NT_S([{'^', [<<"tag">>], []}], <<"{{ ^tag }}{{ / tag }}">>)}, {"{{ ^ tag }}{{ /tag }}", ?NT_S([{'^', [<<"tag">>], []}], <<"{{ ^ tag }}{{ /tag }}">>)}, {"{{ ^ ta g}}{{ / t ag}}", ?NT_S([{'^', [<<"tag">>], []}], <<"{{ ^ ta g}}{{ / t ag}}">>)}, {"{{=<< >>=}}{{n}}<><<={{ }}=>>{{n}}<>", ?NT_S([<<"a">>, <<"b{{n}}c">>, {n, [<<"n">>]}, <<"d">>, <<"e">>, {n, [<<"m">>]}, <<"f<>g">>], <<"a{{=<< >>=}}b{{n}}c<>d<<={{ }}=>>e{{m}}f<>g">>)}, {"{{=<< >>=}}<<#tag>><<{n}>><>", ?NT_S([{'#', [<<"tag">>], [{'&', [<<"n">>]}], <<"<<{n}>>">>}], <<"{{=<< >>=}}<<#tag>><<{n}>><>">>)}, {"{{=<< >>=}}<>", ?NT_S([{n, [<<"n">>]}], <<"{{=<< >>=}}<>">>)}, {"{{ = << >> = }}<>", ?NT_S([{n, [<<"n">>]}], <<"{{ = << >> = }}<>">>)}, {"{{=<= =>=}}<=n=>", ?NT_F({?PARSE_ERROR, delimiters_may_not_contain_equals}, <<"{{=<= =>=}}<=n=>">>)}, {"{{ = < < >> = }}< >", ?NT_F({?PARSE_ERROR, delimiters_may_not_contain_whitespaces}, <<"{{ = < < >> = }}< >">>)}, {"{{=<< >>}}", ?NT_F({?PARSE_ERROR, {unsupported_tag, <<"=<< >>">>}}, <<"{{=<< >>}}">>)}, {"{{={ }=}}{{n}}", ?NT_S([{'&', [<<"n">>]}], <<"{{={ }=}}{{n}}">>)} ]. assoc_list_render_test_() -> [ {"integer, float, binary, string", fun() -> ?assertEqual(<<"1, 1.5, hoge, fugo, atom">>, bbmustache: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", fun() -> F = fun(Text, Render) -> ["", Render(Text), ""] end, ?assertEqual(<<"Willy is awesome.">>, bbmustache:render(<<"{{#wrapped}}{{name}} is awesome.{{dummy_atom}}{{/wrapped}}">>, [{name, "Willy"}, {wrapped, F}], [{key_type, atom}])), ?assertError(_, binary_to_existing_atom(<<"dummy_atom">>, utf8)) end}, {"binary key", fun() -> F = fun(Text, Render) -> ["", Render(Text), ""] end, ?assertEqual(<<"Willy is awesome.">>, bbmustache:render(<<"{{#wrapped}}{{name}} is awesome.{{dummy}}{{/wrapped}}">>, [{<<"name">>, "Willy"}, {<<"wrapped">>, F}], [{key_type, binary}])) end} ]. unsupported_data_test_() -> [ {"dict", ?_assertError(function_clause, bbmustache:render(<<>>, dict:new()))} ]. raise_on_context_miss_test_() -> [ {"It raise an exception, if the key of escape tag does not exist", ?_assertError({context_missing, {key, <<"child">>}}, bbmustache:render(<<"{{ child }}">>, [], [raise_on_context_miss]))}, {"It raise an exception, if the key of unescape tag does not exist", ?_assertError({context_missing, {key, <<"child">>}}, bbmustache:render(<<"{{{child}}}">>, [], [raise_on_context_miss]))}, {"It raise an exception, if the key of & tag does not exist", ?_assertError({context_missing, {key, <<"child">>}}, bbmustache:render(<<"{{&child}}">>, [], [raise_on_context_miss]))}, {"It raise an exception, if the child does not exist (parent is a # tag)", ?_assertError({context_missing, {key, <<"child">>}}, bbmustache:render(<<"{{#parent}}{{child}}{{/parent}}">>, [{"parent", true}], [raise_on_context_miss]))}, {"It raise an exception, if the child does not exist (parent is a ^ tag)", ?_assertError({context_missing, {key, <<"child">>}}, bbmustache:render(<<"{{^parent}}{{child}}{{/parent}}">>, [{"parent", false}], [raise_on_context_miss]))}, {"It raise an exception, if the key of # tag does not exist", ?_assertError({context_missing, {key, <<"parent">>}}, bbmustache:render(<<"{{#parent}}{{/parent}}">>, [], [raise_on_context_miss]))}, {"It raise an exception, if the key of ^ tag does not exist", ?_assertError({context_missing, {key, <<"parent">>}}, bbmustache:render(<<"{{^parent}}{{/parent}}">>, [], [raise_on_context_miss]))}, {"It does not raise an exception, if the child of the hidden parent does not exist (parent is a ^ tag)", ?_assertEqual(<<"">>, bbmustache:render(<<"{{^parent}}{{child}}{{/parent}}">>, [{"parent", true}], [raise_on_context_miss]))}, {"It does not raise an exception, if the child of the hidden parent does not exist (parent is a # tag)", ?_assertEqual(<<"">>, bbmustache:render(<<"{{#parent}}{{child}}{{/parent}}">>, [{"parent", false}], [raise_on_context_miss]))}, {"It raise an exception, if specified file does not exist", ?_assertError({context_missing, {file_not_found, <<"not_found_filename">>}}, bbmustache:render(<<"{{> not_found_filename}}">>, [], [raise_on_context_miss]))}, {"The exceptions thrown include information on the specified key", ?_assertError({context_missing, {key, <<"parent.child">>}}, bbmustache:render(<<"{{#parent}}{{ parent . child }}{{/parent}}">>, [{"parent", [{"dummy", true}]}, {"child", []}], [raise_on_context_miss]))} ]. context_stack_test_() -> [ {"It can use the key which parent is not a dictionary (resolve #22)", ?_assertEqual(<<"aaabbb">>, bbmustache:render(<<"{{#parent}}aaa{{parent.child}}bbb{{/parent}}">>, [{"parent", true}]))}, {"It hide all tags in # tag that is specfied empty list", ?_assertEqual(<<"">>, bbmustache:render(<<"{{#parent}}aaa{{parent.child}}bbb{{/parent}}">>, [{"parent", []}], [raise_on_context_miss]))} ]. escape_fun_test_() -> [ {"It is able to specified own escape function", ?_assertEqual(<<"==>value<==">>, bbmustache:render(<<"{{tag}}">>, [{"tag", "value"}], [{escape_fun, fun(X) -> <<"==>", X/binary, "<==">> end}]))} ].