Permalink
Browse files

Initial commit. First public release

  • Loading branch information...
danabr committed Feb 18, 2012
0 parents commit 2a1ccb23c50704895ad482a58bdcea360c7528b0
Showing with 1,051 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +21 −0 LICENSE
  3. +74 −0 README
  4. +25 −0 beard_basic_renderer.erl
  5. +43 −0 beard_compiler.erl
  6. +66 −0 beard_emit.erl
  7. +472 −0 beard_lex.erl
  8. +19 −0 beard_lex.xrl
  9. +313 −0 beard_yecc.erl
  10. +17 −0 beard_yecc.yrl
@@ -0,0 +1 @@
+*.beam
21 LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2012 Daniel Abrahamsson
+
+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.
+
74 README
@@ -0,0 +1,74 @@
+BEARD
+-----
+
+Beard is a mustache inspired template solution for Erlang. It was a created
+due to mustache.erl being too slow for the authors needs.
+
+Beard takes two files, a logic file and a template file, and turns them into
+an Erlang module with a render/1 function.
+
+Beard is suitable if you have complex templates with lots of data. We have
+converted some heavy yaws pages to beard templates with no noticable
+performance impact at all.
+
+For smaller templates, the more feature rich, but also slower, mustache.erl
+might be a better choice.
+
+SYNTAX
+-------
+There are two constructs in beard, the {{call}} construct and the
+{{#subcontext}} {{/endofsubcontext}} construct.
+
+{{name}} will be replaced by whatever LogicModule:name(Context) returns.
+Note that beard does not automatically escape output, nor convert it to
+strings.
+
+{{#persons}} will call LogicModule:subcontext(persons) and returns a list
+if subcontexts. The subtemplate will be rendered with each subcontext in the
+list.
+
+EXAMPLE
+-------
+The following example illustrates how beard is used (with a logic file
+example, and a template file example.txt):
+
+example:
+ -module(example).
+ -export([render/1]).
+
+ count(Persons) ->
+ integer_to_list(length(Persons)).
+
+ persons(Persons) ->
+ Persons.
+
+ name({Name, _Age}) ->
+ Name.
+
+ age({_Name, Age}) ->
+ integer_to_list(Age).
+
+
+example.txt:
+ Hi!
+ I have {{count}} friends.
+ {{#persons}}
+ {{name}} is {{age}} years old.
+ {{/persons}}
+
+And here is how you compile and render the template with beard:
+$ erl
+$> beard:compile("example.txt", "example", "ebin").
+$> Persons = [{"Anne", 10}, {"Joe", 8}].
+$> Output = beard:render(Persons). % Persons is the context.
+$> file:write_file("example.output", Output).
+$> q().
+$ cat example.output
+ Hi!
+ I have 2 friends
+
+ Anne is 10 years old.
+
+ Joe is 8 years old.
+
+Note that all whitespace and linebreaks from the template are preserved.
@@ -0,0 +1,25 @@
+%%% @copyright Daniel Abrahamsson 2012
+%%% @doc Module for rendering a template without compiling it to a module.
+-module(beard_basic_renderer).
+
+-export([render/3]).
+
+render([], _Module, _Context) ->
+ [];
+render([{data, Data}|Template], Module, Context) ->
+ [Data|render(Template, Module, Context)];
+render([{call, Func}|Template], Module, Context) ->
+ Data = Module:Func(Context),
+ [Data|render(Template, Module, Context)];
+render([{context, Func, SubTemplate}|Template], Module, Context) ->
+ Data = case Module:Func(Context) of
+ true ->
+ render(SubTemplate, Module, Context);
+ false ->
+ "";
+ List when is_list(List) ->
+ [render(SubTemplate, Module, SubContext) || SubContext <- List]
+ end,
+ [Data|render(Template, Module, Context)].
+
+
@@ -0,0 +1,43 @@
+%%% @copyright Daniel Abrahamsson 2012
+%%% @doc Module for compiling a template and a logic module into a Erlang
+%%% module.
+%%% @end
+-module(beard_compiler).
+-export([string/1, file/1, beam/3]).
+
+%%% @doc Parses the string B as a beard template
+string(B) when is_binary(B) ->
+ string(binary_to_list(B));
+string(L) when is_list(L) ->
+ {ok, Tokens, _} = beard_lex:string(L),
+ beard_yecc:parse(Tokens).
+
+%%% @doc Parses the file at FilePath as a beard template.
+file(FilePath) ->
+ {ok, Bin} = file:read_file(FilePath),
+ string(Bin).
+
+%%% @doc Compiles a template and a logic module into a Erlang module,
+%%% which will be placed in OutDir. The module itself will be named
+%%% after the logic module.
+%%% If the compilation fails, the Erlang source will be placed
+%%% as LogicPath ++ ".erl" for debugging purposes. Otherwise,
+%%% the source code will be removed.
+%%% @end
+beam(TemplatePath, LogicPath, OutDir) when is_list(TemplatePath),
+ is_list(LogicPath),
+ is_list(OutDir) ->
+ {ok, Template} = file(TemplatePath),
+ RenderingOutput = beard_emit:emit(Template),
+ {ok, LogicMod} = file:read_file(LogicPath),
+ Outfile = LogicPath ++ ".erl",
+ ok = file:write_file(Outfile, [LogicMod, RenderingOutput]),
+ Opts = [{outdir, OutDir}, report_errors, report_warnings, verbose],
+ case compile:file(LogicPath, Opts) of
+ error ->
+ preserve_output;
+ {error, _, _} ->
+ preserve_output;
+ _Ok ->
+ file:delete(Outfile)
+ end.
@@ -0,0 +1,66 @@
+%%% @copyright Daniel Abrahamsson 2012
+%%% @doc Module for emitting Erlang code for rendering a template.
+-module(beard_emit).
+-export([emit/1]).
+
+emit(Template) ->
+ Funs = extract_functions(Template),
+ emit_functions(Funs).
+
+extract_functions(Template) ->
+ ef1([{"render", Template}], []).
+
+ef1([], Funs) ->
+ Funs;
+ef1([{Name, Template}|Stack], Funs) ->
+ {Fun, CalledFuns} = ef2(Name, Template),
+ ef1(CalledFuns ++ Stack, [{Name, Fun}|Funs]).
+
+ef2(Name, Template) ->
+ ef2(Name, Template, [], [], 0).
+
+ef2(_, [], SubTemplate, CalledFuns, _Index) ->
+ {SubTemplate, CalledFuns};
+ef2(Name, [{context, Func, SubTemplate}|Template], Sub_Template, CalledFuns,
+ Index) ->
+ NewName = Name ++ "_" ++ integer_to_list(Index),
+ ef2(Name, Template, Sub_Template ++ [{call, NewName, Func}],
+ [{NewName, SubTemplate}|CalledFuns], Index+1);
+ef2(Name, [Command|SubTemplate], Sub_Template, CalledFuns, Index) ->
+ ef2(Name, SubTemplate, Sub_Template ++ [Command], CalledFuns, Index).
+
+emit_functions([]) ->
+ "";
+emit_functions([{"render", Template}|T]) ->
+ ["render(Context) ->\n", emit_template(Template),
+ emit_functions(T), "."];
+emit_functions([{Name, Template}|T]) ->
+ [emit_list(Name, Template), ".\n", emit_functions(T)].
+
+emit_list(Name, Template) ->
+ [Name, "([]) -> \"\";\n",
+ Name, "([Context|T]) ->\n",
+ "[", emit_template(Template), "|", Name, "(T)]"].
+
+emit_template(Template) ->
+ ["[", string:join(emit_template_body(Template, ""), ","), "]"].
+
+emit_template_body([], Operations) -> lists:reverse(Operations);
+emit_template_body([{data, Data}|T], Acc) ->
+ emit_template_body(T, [["\"", escape(Data), "\""]|Acc]);
+emit_template_body([{call, Func}|T], Acc) ->
+ emit_template_body(T, [[atom_to_list(Func), "(Context)"]|Acc]);
+emit_template_body([{call, Func, ContextFunc}|T], Acc) ->
+ emit_template_body(T, [[Func, "(", atom_to_list(ContextFunc),
+ "(Context))"]|Acc]).
+
+escape([]) ->
+ [];
+escape([$\n|T]) ->
+ [$\\,$n|escape(T)];
+escape([$\\|T]) ->
+ [$\\, $\\|emit(T)];
+escape([$\"|T]) ->
+ [$\\, $\"|emit(T)];
+escape([H|T]) ->
+ [H|escape(T)].
Oops, something went wrong.

0 comments on commit 2a1ccb2

Please sign in to comment.