first commit
This commit is contained in:
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
.rebar3
|
||||
_build
|
||||
_checkouts
|
||||
_vendor
|
||||
.eunit
|
||||
*.o
|
||||
*.beam
|
||||
*.plt
|
||||
*.swp
|
||||
*.swo
|
||||
.erlang.cookie
|
||||
ebin
|
||||
log
|
||||
erl_crash.dump
|
||||
.rebar
|
||||
logs
|
||||
.idea
|
||||
*.iml
|
||||
rebar3.crashdump
|
||||
*~
|
||||
20
LICENSE.md
Normal file
20
LICENSE.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright 2023 Erlang-Punch
|
||||
|
||||
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.
|
||||
9
README.md
Normal file
9
README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# erml
|
||||
|
||||
An OTP application
|
||||
|
||||
## Build
|
||||
|
||||
```erlang
|
||||
rebar3 compile
|
||||
```
|
||||
13
examples/htmx/card.erml
Normal file
13
examples/htmx/card.erml
Normal file
@@ -0,0 +1,13 @@
|
||||
{'div', #{class => "card"}, [
|
||||
{'div', #{class => "card-images"}, [
|
||||
{img, #{src => {card_image_url}, class => "img-responsive"}}
|
||||
]},
|
||||
{'div', #{class => "card-header"}, [
|
||||
{'div', #{class => "card-title h5"}, {card_title}},
|
||||
{'div', #{class => "card-subtitle text-gray"}, {card_subtitle}}
|
||||
]},
|
||||
{'div', #{class => "card-body"}, {card_body}},
|
||||
{'div', #{class => "card-footer"}, [
|
||||
{button, #{class => "btn btn-primary"}, {card_button}}
|
||||
]}
|
||||
]}.
|
||||
99
examples/htmx/index.erml
Normal file
99
examples/htmx/index.erml
Normal file
@@ -0,0 +1,99 @@
|
||||
{html, [
|
||||
{head, [
|
||||
{title, [<<"This is an example">>]},
|
||||
{link, #{rel => "stylesheet", href => "https://unpkg.com/spectre.css/dist/spectre.min.css"}, []},
|
||||
{link, #{rel => "stylesheet", href => "https://unpkg.com/spectre.css/dist/spectre-exp.min.css"}, []},
|
||||
{link, #{rel => "stylesheet", href => "https://unpkg.com/spectre.css/dist/spectre-icons.min.css"}, []},
|
||||
{script, #{src => "https://unpkg.com/htmx.org@1.9.9"}, []}
|
||||
]},
|
||||
{body, [
|
||||
{'header', #{class => "navbar"}, [
|
||||
{section, #{class => "navbar-section"}, [
|
||||
{a, #{href => "#", class => "navbar-brand mr-2"}, <<"Gabarit HTML">>},
|
||||
{a, #{href => "#", class => "btn btn-link"}, <<"Introduction">>},
|
||||
{a, #{href => "#", class => "btn btn-link"}, <<"Docs">>}
|
||||
]},
|
||||
{section, #{class => "navbar-section"}, [
|
||||
{'div', #{class => "input-group input-inline"}, [
|
||||
{input, #{class => "form-input", type => "text", placeholder => "search"}, []},
|
||||
{button, #{class => "btn btn-primary input-group-btn"}, <<"search">>}
|
||||
]}
|
||||
]}
|
||||
]},
|
||||
{'div', #{class => "container"}, [
|
||||
{'div', #{class => "columns"}, [
|
||||
|
||||
% card 1
|
||||
{'div', #{class => "card"}, [
|
||||
{'div', #{class => "card-images"}, [
|
||||
{img, #{src => "https://picsum.photos/id/1/300/200", class => "img-responsive"}}
|
||||
]},
|
||||
{'div', #{class => "card-header"}, [
|
||||
{'div', #{class => "card-title h5"}, [<<"card 1">>]},
|
||||
{'div', #{class => "card-subtitle text-gray"}, [<<"This is card subtitle">>]}
|
||||
]},
|
||||
{'div', #{class => "card-body"}, [<<"This is the body!">>]},
|
||||
{'div', #{class => "card-footer"}, [
|
||||
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||
]}
|
||||
]},
|
||||
% card 2
|
||||
{'div', #{class => "card"}, [
|
||||
{'div', #{class => "card-images"}, [
|
||||
{img, #{src => "https://picsum.photos/id/2/300/200", class => "img-responsive"}}
|
||||
]},
|
||||
{'div', #{class => "card-header"}, [
|
||||
{'div', #{class => "card-title h5"}, [<<"test">>]},
|
||||
{'div', #{class => "card-subtitle text-gray"}, [<<"test">>]}
|
||||
]},
|
||||
{'div', #{class => "card-body"}, [<<"test">>]},
|
||||
{'div', #{class => "card-footer"}, [
|
||||
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||
]}
|
||||
]},
|
||||
% card 3
|
||||
{'div', #{class => "card"}, [
|
||||
{'div', #{class => "card-images"}, [
|
||||
{img, #{src => "https://picsum.photos/id/3/300/200", class => "img-responsive"}}
|
||||
]},
|
||||
{'div', #{class => "card-header"}, [
|
||||
{'div', #{class => "card-title h5"}, [<<"test">>]},
|
||||
{'div', #{class => "card-subtitle text-gray"}, [<<"test">>]}
|
||||
]},
|
||||
{'div', #{class => "card-body"}, [<<"test">>]},
|
||||
{'div', #{class => "card-footer"}, [
|
||||
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||
]}
|
||||
]},
|
||||
% card 4
|
||||
{'div', #{class => "card"}, [
|
||||
{'div', #{class => "card-images"}, [
|
||||
{img, #{src => "https://picsum.photos/id/4/300/200", class => "img-responsive"}}
|
||||
]},
|
||||
{'div', #{class => "card-header"}, [
|
||||
{'div', #{class => "card-title h5"}, [<<"test">>]},
|
||||
{'div', #{class => "card-subtitle text-gray"}, [<<"test">>]}
|
||||
]},
|
||||
{'div', #{class => "card-body"}, [<<"test">>]},
|
||||
{'div', #{class => "card-footer"}, [
|
||||
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||
]}
|
||||
]},
|
||||
% card 5
|
||||
{'div', #{class => "card"}, [
|
||||
{'div', #{class => "card-images"}, [
|
||||
{img, #{src => "https://picsum.photos/id/5/300/200", class => "img-responsive"}}
|
||||
]},
|
||||
{'div', #{class => "card-header"}, [
|
||||
{'div', #{class => "card-title h5"}, [<<"test">>]},
|
||||
{'div', #{class => "card-subtitle text-gray"}, [<<"test">>]}
|
||||
]},
|
||||
{'div', #{class => "card-body"}, [<<"test">>]},
|
||||
{'div', #{class => "card-footer"}, [
|
||||
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}.
|
||||
0
priv/entities/Makefile
Normal file
0
priv/entities/Makefile
Normal file
7
rebar.config
Normal file
7
rebar.config
Normal file
@@ -0,0 +1,7 @@
|
||||
{erl_opts, [debug_info]}.
|
||||
{deps, []}.
|
||||
|
||||
{shell, [
|
||||
% {config, "config/sys.config"},
|
||||
{apps, [erml]}
|
||||
]}.
|
||||
14
src/erml.app.src
Normal file
14
src/erml.app.src
Normal file
@@ -0,0 +1,14 @@
|
||||
{application, erml,
|
||||
[{description, "An Erlang HTML library"},
|
||||
{vsn, "0.1.0"},
|
||||
{registered, []},
|
||||
{mod, {erml_app, []}},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib
|
||||
]},
|
||||
{env,[]},
|
||||
{modules, []},
|
||||
{licenses, ["MIT"]},
|
||||
{links, []}
|
||||
]}.
|
||||
14
src/erml_app.erl
Normal file
14
src/erml_app.erl
Normal file
@@ -0,0 +1,14 @@
|
||||
%%%===================================================================
|
||||
%%% @doc draft.
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(erml_app).
|
||||
-behaviour(application).
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
erml_sup:start_link().
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
||||
|
||||
104
src/erml_generator.erl
Normal file
104
src/erml_generator.erl
Normal file
@@ -0,0 +1,104 @@
|
||||
%%%===================================================================
|
||||
%%% @doc draft.
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(erml_generator).
|
||||
-export([compile/3, compile/4]).
|
||||
-export([flatten/2]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
compile(Module, Args, Data) ->
|
||||
compile(Module, Args, Data, #{}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
compile(Module, Args, Data, Opts) ->
|
||||
init_loop(Module, Args, Data, Opts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
init_loop(Module, Args, Data, Opts) ->
|
||||
try Module:init(Args) of
|
||||
{ok, State} ->
|
||||
loop(Module, Data, Opts, State, [], [])
|
||||
catch
|
||||
E:R:S ->
|
||||
{error, {E,R,S}}
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% custom function to flatten an improper list containing binaries and
|
||||
%% other terms.
|
||||
%%--------------------------------------------------------------------
|
||||
flatten([], Buffer)
|
||||
when is_binary(Buffer) -> Buffer;
|
||||
flatten([], Buffer)
|
||||
when is_list(Buffer) -> lists:reverse(Buffer);
|
||||
flatten([H|T], Buffer)
|
||||
when is_binary(H), is_binary(Buffer) ->
|
||||
flatten(T, <<Buffer/binary, H/binary>>);
|
||||
flatten([H|T], Buffer)
|
||||
when is_binary(Buffer) ->
|
||||
flatten(T, [H|[Buffer]]);
|
||||
flatten([H|T], [Last|Rest])
|
||||
when is_binary(Last), is_binary(H) ->
|
||||
flatten(T, [<<Last/binary, H/binary>>|Rest]);
|
||||
flatten([H|T], Buffer)
|
||||
when is_list(Buffer) ->
|
||||
flatten(T, [H|Buffer]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
loop(Module, Data, Opts, State, LBuffer, RBuffer) ->
|
||||
case elements(Module, Data, Opts, State, LBuffer, RBuffer) of
|
||||
{ok, L, R, _State} ->
|
||||
List = lists:flatten([L,R]),
|
||||
Flatten = flatten(List, <<>>),
|
||||
{ok, Flatten};
|
||||
Elsewise ->
|
||||
Elsewise
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
elements(Module, [], Opts, State, LB, RB) ->
|
||||
{ok, LB, RB, State};
|
||||
elements(Module, [Element|Elements], Opts, State, LB, RB) ->
|
||||
case element(Module, Element, Opts, State) of
|
||||
{ok, Content, NewState} ->
|
||||
elements(Module, Elements, Opts, NewState, [LB, Content], RB);
|
||||
{ok, Begin, End, NewState} ->
|
||||
elements(Module, Elements, Opts, NewState, [LB, Begin], [End,RB]);
|
||||
Elsewise ->
|
||||
Elsewise
|
||||
end;
|
||||
elements(Module, Element, Opts, State, LB, RB) ->
|
||||
case element(Module, Element, Opts, State) of
|
||||
{ok, Content, NewState} ->
|
||||
{ok, [LB, Content], RB, NewState};
|
||||
{ok, Begin, End, NewState} ->
|
||||
{ok, [LB, Begin], [End,RB], NewState};
|
||||
Elsewise ->
|
||||
Elsewise
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
element(Module, Element, Opts, State) ->
|
||||
case Module:tag(Element, Opts, State) of
|
||||
{ok, Content, NewState} ->
|
||||
{ok, Content, NewState};
|
||||
{ok, Begin, End, NewState} ->
|
||||
{ok, Begin, End, NewState};
|
||||
{ok, Begin, End, Inner, NewState} ->
|
||||
elements(Module, Inner, Opts, NewState, [Begin], [End]);
|
||||
Elsewise ->
|
||||
Elsewise
|
||||
end.
|
||||
520
src/erml_html.erl
Normal file
520
src/erml_html.erl
Normal file
@@ -0,0 +1,520 @@
|
||||
%%%===================================================================
|
||||
%%% @doc draft: Create html page using tuple, map and binaries.
|
||||
%%%
|
||||
%%% ```
|
||||
%%% % create a simple page
|
||||
%%% Title = {h1, <<"this is my title">>}.
|
||||
%%% Paragraph1 = {p, <<"long time ago...">>}.
|
||||
%%% Paragraph2 = {p, <<"that's all folks!">>}.
|
||||
%%% Paragraph3 = fun(_Opts) -> {ok, {p, <<"end.">>}} end.
|
||||
%%% Body = [Title, Paragraph1, Paragraph2, Paragraph3].
|
||||
%%% erml_html:create({html, Body}).
|
||||
%%% '''
|
||||
%%%
|
||||
%%% Will generate this page:
|
||||
%%%
|
||||
%%% ```
|
||||
%%% <<"<html><h1>this is my title</h1>",
|
||||
%%% "<p>long time ago...</p>",
|
||||
%%% "<p>that's all folks!</p>",
|
||||
%%% "<p>end.</p>",
|
||||
%%% "</html>">>
|
||||
%%% '''
|
||||
%%%
|
||||
%%% @todo convert this code as behavior.
|
||||
%%% @todo when reading a template, compile it as module.
|
||||
%%% @todo when a template includes dynamic code, allow rendering it
|
||||
%%% with custom option in the mode.
|
||||
%%%
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(erml_html).
|
||||
-export([open/1, open/2]).
|
||||
-export([create/1, create/2]).
|
||||
-export([table/2]).
|
||||
-export([join/1, join/2]).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
-type tag() :: atom() | list() | binary().
|
||||
-type attributes() :: #{}.
|
||||
-type content() :: [].
|
||||
-type options() :: #{}.
|
||||
-type element() :: {tag(), attributes()}
|
||||
| {tag(), attributes(), content()}
|
||||
| {tag(), attributes(), content(), options()}
|
||||
| {apply, {atom(), list()}}
|
||||
| {apply, {atom(), atom(), list()}}
|
||||
| {include, list() | binary()}
|
||||
| binary().
|
||||
-type elements() :: [element()].
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc A demo function to generate table.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
table([], _) ->
|
||||
create({table, []});
|
||||
table([Header|Rest], #{ header := true } = Opts) ->
|
||||
Keys = maps:keys(Header),
|
||||
Head = table_header(Keys, Header, Opts),
|
||||
Body = table_rows(Rest, Keys, [], Opts),
|
||||
{table, [Head, Body]};
|
||||
table([Header|Rest] = Rows, Opts) ->
|
||||
Keys = maps:keys(Header),
|
||||
Body = table_rows(Rows, Keys, [], Opts),
|
||||
{table, Body}.
|
||||
|
||||
table_header(Keys, Data, #{ extra := true }) ->
|
||||
Th = [ {th, [{span, #{}, Key}
|
||||
, <<" ">>
|
||||
,{span, #{}, [<<"(">>, maps:get(Key, Data), <<")">>]}
|
||||
]
|
||||
}
|
||||
|| Key <- Keys ],
|
||||
{thead, {tr, #{}, Th}};
|
||||
table_header(Keys, _, Opts) ->
|
||||
{thead, {tr, #{}, [ {th, #{}, Key} || Key <- Keys ]}}.
|
||||
|
||||
table_rows([], _, Buffer, _) -> {tbody, Buffer};
|
||||
table_rows([Last], Keys, Buffer, #{ footer := true }) ->
|
||||
Footer = {tfooter, {tr, [ {td, maps:get(Last, Key)} || Key <- Keys ]}},
|
||||
Body = {tbody, lists:reverse(Buffer)},
|
||||
[Body, Footer];
|
||||
table_rows([Row|Rest], Keys, Buffer, Opts) ->
|
||||
table_rows(Rest, Keys, [{tr, [ {td, [maps:get(Key, Row)]} || Key <- Keys ]}|Buffer], Opts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc create a new HTML page from elements.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create(Element) -> Return when
|
||||
Element :: element() | elements(),
|
||||
Return :: binary().
|
||||
|
||||
create(Element) ->
|
||||
create(Element, #{}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc create a new HTML page from elements.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec create(Element, Opts) -> Return when
|
||||
Element :: element() | elements(),
|
||||
Opts :: options(),
|
||||
Return :: binary().
|
||||
|
||||
create(Element, Opts) ->
|
||||
Full = tags(Element, Opts),
|
||||
doctype(Full, Opts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc create a new HTML page from elements.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
open(Path) ->
|
||||
open(Path, #{}).
|
||||
|
||||
open(Path, Opts) ->
|
||||
{ok, Data} = file:consult(Path),
|
||||
create(Data, Opts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
tags([], _) -> <<>>;
|
||||
tags(Elements, Opts) ->
|
||||
tags(Elements, <<>>, Opts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
tags([], Buffer, _Opts) -> Buffer;
|
||||
tags(Element, Buffer, Opts)
|
||||
when is_tuple(Element) ->
|
||||
tags([Element], Buffer, Opts);
|
||||
tags([Element|Elements], Buffer, Opts)
|
||||
when is_tuple(Element) ->
|
||||
Tag = tag(Element, Opts),
|
||||
tags(Elements, <<Buffer/binary, Tag/binary>>, Opts);
|
||||
tags([Element|Elements], Buffer, Opts)
|
||||
when is_list(Element) ->
|
||||
Tags = tags(Element, Opts),
|
||||
tags(Elements, <<Buffer/binary, Tags/binary>>, Opts);
|
||||
tags([Element|Elements], Buffer, Opts) ->
|
||||
Encoded = text(Element, Opts),
|
||||
tags(Elements, <<Buffer/binary, Encoded/binary>>, Opts);
|
||||
tags(Element, Buffer, Opts) ->
|
||||
Encoded = text(Element, Opts),
|
||||
<<Buffer/binary, Encoded/binary>>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
doctype(Buffer, #{ doctype := true }) ->
|
||||
<<"<!DOCTYPE html>", Buffer/binary>>;
|
||||
doctype(Buffer, _Opts) ->
|
||||
Buffer.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
text(Content, Opts)
|
||||
when is_integer(Content) ->
|
||||
text(integer_to_binary(Content), Opts);
|
||||
text(Content, Opts)
|
||||
when is_atom(Content) ->
|
||||
text(atom_to_binary(Content), Opts);
|
||||
text(Content, #{ html_entities := false })
|
||||
when is_binary(Content) ->
|
||||
Content;
|
||||
text(Content, _Opts)
|
||||
when is_binary(Content) ->
|
||||
erml_html_entities:encode(Content).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
tag({include_raw, Path}, Opts) ->
|
||||
case file:read_file(Path) of
|
||||
{ok, Content} ->
|
||||
erml_html_entities:encode(Content);
|
||||
Elsewise ->
|
||||
throw(Elsewise)
|
||||
end;
|
||||
tag({include_template, Path}, Opts) ->
|
||||
case file:consult(Path) of
|
||||
{ok, Content} ->
|
||||
tags([Content], Opts);
|
||||
Elsewise ->
|
||||
throw(Elsewise)
|
||||
end;
|
||||
tag({include_template, Path, Variables}, Opts) ->
|
||||
case file:consult(Path) of
|
||||
{ok, Content} ->
|
||||
tags([Content], #{ variables => Variables });
|
||||
Elsewise ->
|
||||
throw(Elsewise)
|
||||
end;
|
||||
% variable support
|
||||
tag({Variable}, Opts) ->
|
||||
Result = get_variable(Variable, Opts),
|
||||
tags([Result], Opts);
|
||||
tag({apply, {Function, Args}}, #{module := Module} = Opts)
|
||||
when is_atom(Function), is_list(Args) ->
|
||||
case apply(Module, Function, [Opts, Args]) of
|
||||
{ok, Result} when is_binary(Result) ->
|
||||
erml_html_entities:encode(Result);
|
||||
{ok, Result} when is_list(Result) ->
|
||||
tags(Result, Opts);
|
||||
{ok, Result} when is_tuple(Result) ->
|
||||
tag(Result, Opts)
|
||||
end;
|
||||
tag({apply, {Module, Function, Args}}, Opts)
|
||||
when is_atom(Module), is_atom(Function), is_list(Args) ->
|
||||
case apply(Module, Function, [Opts|Args]) of
|
||||
{ok, Result} when is_binary(Result) ->
|
||||
erml_html_entities:encode(Result);
|
||||
{ok, Result} when is_list(Result) ->
|
||||
tags(Result, Opts);
|
||||
{ok, Result} when is_tuple(Result) ->
|
||||
tag(Result, Opts)
|
||||
end;
|
||||
tag({apply, Fun}, Opts)
|
||||
when is_function(Fun, 1) ->
|
||||
case Fun(Opts) of
|
||||
{ok, Result} when is_binary(Result) ->
|
||||
erml_html_entities:encode(Result);
|
||||
{ok, Result} when is_list(Result) ->
|
||||
tags(Result, Opts);
|
||||
{ok, Result} when is_tuple(Result) ->
|
||||
tag(Result, Opts)
|
||||
end;
|
||||
tag({Element, Content}, Opts)
|
||||
when is_binary(Content) ->
|
||||
tag({Element, #{}, Content}, Opts);
|
||||
% {html, []} should use default or empty attributes. The second
|
||||
% element of the tuple is the inner content.
|
||||
tag({Element, Content}, Opts)
|
||||
when is_list(Content) ->
|
||||
tag({Element, #{}, Content}, Opts);
|
||||
tag({Element, Content}, Opts)
|
||||
when is_tuple(Content) ->
|
||||
tag({Element, #{}, Content}, Opts);
|
||||
% {html, #{}} is a tag without content and using customer attributes.
|
||||
tag({Element, Attributes}, Opts)
|
||||
when is_map(Attributes) ->
|
||||
tag({Element, Attributes, []}, Opts);
|
||||
tag({Element, Attributes, _Content} = Tag, Opts) ->
|
||||
tag1(Tag, <<>>, Opts);
|
||||
tag(Element, Opts) when is_function(Element) ->
|
||||
tag1(Element, <<>>, Opts).
|
||||
|
||||
tag_test() ->
|
||||
% tag can be defined with a triplet
|
||||
[?assertEqual(<<"<html></html>">>
|
||||
,tag({html, #{}, []}, []))
|
||||
|
||||
% tag can be defined with a pair
|
||||
,?assertEqual(<<"<html></html>">>
|
||||
,tag({html, []}, []))
|
||||
|
||||
% tag can be defined with a pair
|
||||
,?assertEqual(<<"<html>test</html>">>
|
||||
,tag({html, [<<"test">>]}, []))
|
||||
|
||||
% tag can be defined with a pair
|
||||
,?assertEqual(<<"<html>test</html>">>
|
||||
,tag({html, <<"test">>}, []))
|
||||
|
||||
% tag's content can be a list containing binary
|
||||
,?assertEqual(<<"<html>test</html>">>
|
||||
,tag({html, #{}, [<<"test">>]}, []))
|
||||
|
||||
% tag's content can be a binary
|
||||
,?assertEqual(<<"<html>test</html>">>
|
||||
,tag({html, #{}, <<"test">>}, []))
|
||||
|
||||
% attribute's value can be an atom
|
||||
,?assertEqual(<<"<html id=\"test\">test</html>">>
|
||||
,tag({html, #{ id => test }, [<<"test">>]}, []))
|
||||
|
||||
% attribute's value can be a binary
|
||||
,?assertEqual(<<"<html id=\"test\">test</html>">>
|
||||
,tag({html, #{ id => <<"test">> }, [<<"test">>]}, []))
|
||||
|
||||
% attribute's value can be a string
|
||||
,?assertEqual(<<"<html id=\"test\">test</html>">>
|
||||
,tag({html, #{ id => "test" }, [<<"test">>]}, []))
|
||||
|
||||
% attribute's key can be a binary
|
||||
,?assertEqual(<<"<html id=\"test\">test</html>">>
|
||||
,tag({html, #{ <<"id">> => "test" }, [<<"test">>]}, []))
|
||||
|
||||
% attributes values must be encoded using html entities
|
||||
,?assertEqual(<<"<html id=\"&test\">test</html>">>
|
||||
,tag({html, #{ <<"id">> => "&test" }, [<<"test">>]}, []))
|
||||
|
||||
% empty element support
|
||||
,?assertEqual(<<"<html id=\"test\" />">>
|
||||
,tag({{empty, html}, #{ id => "test"}}, []))
|
||||
].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
% support for empty tag
|
||||
tag1({{empty, Element}, Attributes, Content}, Buffer, Opts)
|
||||
when is_atom(Element) ->
|
||||
NewElement = atom_to_binary(Element),
|
||||
tag1({{empty, NewElement}, Attributes, Content}, Buffer, Opts);
|
||||
tag1({{empty, Element}, Attributes, Content}, Buffer, Opts)
|
||||
when is_binary(Element), is_map(Attributes) ->
|
||||
tag_empty({Element, Attributes, Content, #{}}, Buffer, Opts);
|
||||
% support for non-empty tag
|
||||
tag1({Element, Attributes, Content}, Buffer, Opts)
|
||||
when is_atom(Element) ->
|
||||
NewElement = atom_to_binary(Element),
|
||||
tag1({NewElement, Attributes, Content}, Buffer, Opts);
|
||||
tag1({Element, Attributes, Content}, Buffer, Opts)
|
||||
when is_binary(Element), is_map(Attributes) ->
|
||||
Item = {Element, Attributes, Content, #{}},
|
||||
% @todo add suppot for pre and code tags.
|
||||
case Element of
|
||||
<<"base">> -> tag_without_content(Item, Buffer, Opts);
|
||||
<<"br">> -> tag_without_content(Item, Buffer, Opts);
|
||||
<<"img">> -> tag_without_content(Item, Buffer, Opts);
|
||||
<<"input">> -> tag_without_content(Item, Buffer, Opts);
|
||||
<<"link">> -> tag_without_content(Item, Buffer, Opts);
|
||||
<<"meta">> -> tag_without_content(Item, Buffer, Opts);
|
||||
<<"source">> -> tag_without_content(Item, Buffer, Opts);
|
||||
_ -> tag_with_content(Item, Buffer, Opts)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
tag_with_content({Element, Attributes, Content, _LocalOpts}, _Buffer, Opts) ->
|
||||
NewAttributes = attributes(Attributes, Opts),
|
||||
StartElement = bracket(Element, NewAttributes),
|
||||
NewContent = tags(Content, Opts),
|
||||
EndElement = bracket_end(Element),
|
||||
<<StartElement/binary, NewContent/binary, EndElement/binary>>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
tag_without_content({Element, Attributes, _Content, _LocalOpts}, _Buffer, Opts) ->
|
||||
NewAttributes = attributes(Attributes, Opts),
|
||||
Item = bracket(Element, NewAttributes),
|
||||
<<Item/binary>>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
tag_empty({Element, Attributes, _Content, _LocalOpts}, _Buffer, Opts) ->
|
||||
NewAttributes = attributes(Attributes, Opts),
|
||||
Item = bracket_empty(Element, NewAttributes),
|
||||
<<Item/binary>>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
bracket_empty(Element, <<>>) ->
|
||||
<<"<", Element/binary, " />">>;
|
||||
bracket_empty(Element, Attributes) ->
|
||||
<<"<", Element/binary, " ", Attributes/binary, " />">>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
bracket(Element, <<>>) ->
|
||||
<<"<", Element/binary, ">">>;
|
||||
bracket(Element, Attributes) ->
|
||||
<<"<", Element/binary, " ", Attributes/binary, ">">>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
bracket_end(Element) ->
|
||||
<<"</", Element/binary, ">">>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @hidden
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
attributes(Attributes, Opts)
|
||||
when map_size(Attributes) =:= 0 ->
|
||||
<<>>;
|
||||
attributes(Attributes, Opts) ->
|
||||
Keys = maps:keys(Attributes),
|
||||
attributes(Attributes, Keys, [], Opts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @hidden
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
attributes(_Attributes, [], Buffer, Opts) ->
|
||||
join(lists:reverse(Buffer), <<" ">>);
|
||||
attributes(Attributes, [Key|Keys], Buffer, Opts) ->
|
||||
Value = erlang:map_get(Key, Attributes),
|
||||
Attribute = attribute(Key, Value, Opts),
|
||||
attributes(Attributes, Keys, [Attribute|Buffer], Opts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @hidden
|
||||
%% @doc
|
||||
%% @todo cleanup the mess for htmx
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
attribute(Key, Value, Opts) ->
|
||||
case key(Key, Opts) of
|
||||
% css
|
||||
{ok, <<"style", _binary>> = NewKey} ->
|
||||
NewValue = value(Value, Opts),
|
||||
Quoted = single_quote(NewValue),
|
||||
<<NewKey/binary,"=", Quoted/binary>>;
|
||||
% this is a javascript like attribute
|
||||
{ok, <<"on", _binary>> = NewKey} ->
|
||||
NewValue = value(Value, Opts),
|
||||
Quoted = single_quote(NewValue),
|
||||
<<NewKey/binary,"=", Quoted/binary>>;
|
||||
% this is an htmx attribute, must be a valid json.
|
||||
{ok, <<"hx-vals">> = NewKey} ->
|
||||
NewValue = value(Value, Opts),
|
||||
Quoted = single_quote(NewValue),
|
||||
<<NewKey/binary,"=", Quoted/binary>>;
|
||||
% standard htmx attributes
|
||||
{ok, <<"hx-", _/binary>> = NewKey} ->
|
||||
NewValue = value(Value, Opts),
|
||||
Quoted = double_quote(NewValue),
|
||||
<<NewKey/binary,"=", Quoted/binary>>;
|
||||
% this is an htmx attribute
|
||||
{ok, <<"htmx-", _/binary>> = NewKey} ->
|
||||
NewValue = value(Value, Opts),
|
||||
Quoted = double_quote(NewValue),
|
||||
<<NewKey/binary,"=", Quoted/binary>>;
|
||||
{ok, <<"href">> = NewKey} ->
|
||||
NewValue = value(Value, Opts),
|
||||
Quoted = double_quote(NewValue),
|
||||
<<NewKey/binary,"=", Quoted/binary>>;
|
||||
% javascript element
|
||||
{ok, <<"src">> = NewKey} ->
|
||||
NewValue = value(Value, Opts),
|
||||
Quoted = single_quote(NewValue),
|
||||
<<NewKey/binary,"=", Quoted/binary>>;
|
||||
% that's another kind of key
|
||||
{ok, NewKey} ->
|
||||
NewValue = value(Value, Opts),
|
||||
Encoded = erml_html_entities:encode(NewValue),
|
||||
Quoted = double_quote(Encoded),
|
||||
<<NewKey/binary, "=", Quoted/binary>>
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
join(Binaries) -> join(Binaries, <<" ">>).
|
||||
|
||||
join(Binaries, Sep) -> join(Binaries, Sep, <<>>).
|
||||
join([Binary], _, Buffer) ->
|
||||
<<Binary/binary, Buffer/binary>>;
|
||||
join([Binary|Rest], Sep, Buffer) ->
|
||||
join(Rest, Sep, <<Sep/binary, Binary/binary, Buffer/binary>>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
get_variable(Variable, #{variables := Variables} = Opts) ->
|
||||
case maps:get(Variable, Variables, '$undefined') of
|
||||
'$undefined' ->
|
||||
throw({error, {undefined, Variable}});
|
||||
Content -> Content
|
||||
end;
|
||||
get_variable(Variable, Opts) ->
|
||||
throw({error, {unset, variables}}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
value({Variable}, Opts) ->
|
||||
get_variable(Variable, Opts);
|
||||
value(Value, _Opts) when is_list(Value) ->
|
||||
list_to_binary(Value);
|
||||
value(Value, _Opts) when is_atom(Value) ->
|
||||
atom_to_binary(Value);
|
||||
value(Value, _Opts) when is_integer(Value) ->
|
||||
integer_to_binary(Value);
|
||||
value(Value, _Opts) when is_binary(Value) ->
|
||||
Value.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
key(Key, _Opts) when is_list(Key) ->
|
||||
{ok, list_to_binary(Key)};
|
||||
key(Key, _Opts) when is_atom(Key) ->
|
||||
{ok, atom_to_binary(Key)};
|
||||
key(Key, _Opts) when is_integer(Key) ->
|
||||
{ok, integer_to_binary(Key)};
|
||||
key(Key, _Opts) when is_binary(Key) ->
|
||||
{ok, Key};
|
||||
key(Key, Opts) ->
|
||||
{error, Key}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
double_quote(Value) ->
|
||||
<<$\", Value/binary, $\">>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
single_quote(Value) ->
|
||||
<<$\', Value/binary, $\'>>.
|
||||
4342
src/erml_html_entities.erl
Normal file
4342
src/erml_html_entities.erl
Normal file
File diff suppressed because it is too large
Load Diff
489
src/erml_html_tag.erl
Normal file
489
src/erml_html_tag.erl
Normal file
@@ -0,0 +1,489 @@
|
||||
%%%===================================================================
|
||||
%%% @doc draft module to convert erml html into behavior.
|
||||
%%%
|
||||
%%% The idea behind this module is to offer a comprehensive, flexible
|
||||
%%% and easy interface to generate optimized HTML code. When
|
||||
%%% evaluated, a template should produce two kind of data:
|
||||
%%%
|
||||
%%% 1. if the page is static, a `binary' term is produced containing
|
||||
%%% the whole document
|
||||
%%%
|
||||
%%% 2. if the page is dynamic (including some function call), a list
|
||||
%%% containing binary and function is then produced.
|
||||
%%%
|
||||
%%% == Requirement ==
|
||||
%%%
|
||||
%%% - Reusable HTML part
|
||||
%%%
|
||||
%%% - Optimized HTML
|
||||
%%%
|
||||
%%% - Flexible HTML generator (include, function, module call...)
|
||||
%%%
|
||||
%%% - Flexible HTML entities (standard, strict, custom...)
|
||||
%%%
|
||||
%%% - Compatible with spectre css by default (see:
|
||||
%%% https://picturepan2.github.io/spectre/)
|
||||
%%%
|
||||
%%% - Compatible with htmx by default (see: https://htmx.org/)
|
||||
%%%
|
||||
%%% == More ==
|
||||
%%%
|
||||
%%% More tests will be required to evaluate the performance of the
|
||||
%%% whole implementation, and if it's required to create dedicated
|
||||
%%% module when compiling. Different models can be used:
|
||||
%%%
|
||||
%%% - Store templates into ETS (in memory)
|
||||
%%%
|
||||
%%% - Compile a module with common interface
|
||||
%%%
|
||||
%%% - Store static page in cache with versionning and dynamic one on
|
||||
%%% another storage layer.
|
||||
%%%
|
||||
%%% == Notes ==
|
||||
%%%
|
||||
%%% ```
|
||||
%%% {}: null
|
||||
%%% {Variable}: variable
|
||||
%%% {Tag, Content}: tag without attribute
|
||||
%%% {Tag, Attribute, Content}: full tag
|
||||
%%% {Tag, Attribute, Content, Opts}: full tag with custom options
|
||||
%%% {apply, Module, Function, Args}: special tag
|
||||
%%% {apply, Function}
|
||||
%%% {apply, Function, Args}
|
||||
%%% '''
|
||||
%%%
|
||||
%%% Static template produce (a binary):
|
||||
%%%
|
||||
%%% ```
|
||||
%%% <<"<body><head>test</head></body>">>
|
||||
%%% '''
|
||||
%%%
|
||||
%%% Dynamic template production (a list of binaries and tuples):
|
||||
%%%
|
||||
%%% ```
|
||||
%%% [ <<"<body><head>"
|
||||
%%% , {apply, Module, Function, Args}
|
||||
%%% , "</head></body>">>
|
||||
%%% ]
|
||||
%%% '''
|
||||
%%%
|
||||
%%% ```
|
||||
%%% 1: [{body, #{}, {head, #{}, []}}].
|
||||
%%% 2: {body, #{}, {head, #{}, []}}.
|
||||
%%% 3: ["<body>", {head, #{}, []} ,"</body>"]
|
||||
%%% 4: ["<body>", "<head>", "</head>", "</body>"]
|
||||
%%% 5: "<body><head></head></body>"
|
||||
%%% '''
|
||||
%%%
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(erml_html_tag).
|
||||
-export([create/1, create/2]).
|
||||
-export([init/1]).
|
||||
-export([tag/3]).
|
||||
-export([attribute/5]).
|
||||
-export([content/3]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% some type definition.
|
||||
%%--------------------------------------------------------------------
|
||||
-type state() :: term().
|
||||
-type data() :: binary() | function().
|
||||
-type return_ok() :: {ok, data(), state()}.
|
||||
-type return_stop() :: {stop, term(), state()}.
|
||||
-type return() :: return_ok() | return_stop().
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
create(Data) -> create(Data, #{}).
|
||||
create(Data, Opts) -> erml_generator:compile(?MODULE, Opts, Data).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
-spec init(Args) -> Return when
|
||||
Args :: term(),
|
||||
Return :: term().
|
||||
|
||||
init(Args) ->
|
||||
{ok, []}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc main function used to generate html using erml tags.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec tag(Tag, Opts, State) -> Return when
|
||||
Tag :: term(),
|
||||
Opts :: map(),
|
||||
State :: term(),
|
||||
Return :: return().
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add variable support (template only). A variable can be any Erlang
|
||||
% term but MUST be present in variables key from Opts
|
||||
%---------------------------------------------------------------------
|
||||
tag({Variable} = Tag, Opts, State) ->
|
||||
{stop, {todo, Tag, Opts}, State};
|
||||
|
||||
%--------------------------------------------------------------------
|
||||
% explicit content with specific options
|
||||
%--------------------------------------------------------------------
|
||||
tag({content, Content} = Tag, Opts, State) ->
|
||||
content(Content, Opts, State);
|
||||
tag({content, Content, LocalOpts} = Tag, Opts, State) ->
|
||||
content(Content, maps:merge(Opts, LocalOpts), State);
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add raw include support. A raw include can use default parameters
|
||||
% from Opts but can also use local parameters, useful if someone needs
|
||||
% to insert a script or stylecheat.
|
||||
%---------------------------------------------------------------------
|
||||
tag({include_raw, Path} = Tag, Opts, State) ->
|
||||
{stop, {todo, Tag, Opts}, State};
|
||||
tag({include_raw, Path, LocalOpts} = Tag, Opts, State) ->
|
||||
{stop, {todo, Tag, Opts}, State};
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add template include support. A template is an erml file or a list
|
||||
% of term containing erml html tags.
|
||||
%---------------------------------------------------------------------
|
||||
tag({include_template, Path} = Tag, Opts, State) ->
|
||||
{stop, {todo, Tag, Opts}, State};
|
||||
tag({include_template, Path, LocalOpts} = Tag, Opts, State) ->
|
||||
{stop, {todo, Tag, Opts}, State};
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% call a standard behaviors and integrate their answer in the
|
||||
% template. this will create a dynamic template. This part of the code
|
||||
% is managed by the generator or the final renderer.
|
||||
%---------------------------------------------------------------------
|
||||
% default call to gen_server
|
||||
tag({call, Pid, Message}, Opts, State) ->
|
||||
tag({gen_server, call, Pid, Message, 1000}, Opts, State);
|
||||
tag({call, Pid, Message, Timeout}, Opts, State) ->
|
||||
tag({gen_server, call, Pid, Message, Timeout}, Opts, State);
|
||||
|
||||
% gen_server support
|
||||
tag({gen_server, call, Pid, Message}, Opts, State) ->
|
||||
tag({gen_server, call, Pid, Message, 1000}, Opts, State);
|
||||
tag({gen_server, call, Pid, Message, Timeout} = Call, Opts, State) ->
|
||||
{ok, Call, State};
|
||||
|
||||
% statem support
|
||||
tag({gen_statem, call, Pid, Message}, Opts, State) ->
|
||||
tag({gen_statem, call, Pid, Message, 1000}, Opts, State);
|
||||
tag({gen_statem, call, Pid, Message, Timeout} = Call, Opts, State) ->
|
||||
{ok, Call, State};
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add MFA support. This part of the code add a dynamic layer to the
|
||||
% page. Every time the page is called, MFA defined is called and then
|
||||
% generate a new page.
|
||||
%---------------------------------------------------------------------
|
||||
tag({apply, Module, Function, Arguments}, Opts, State)
|
||||
when is_atom(Module), is_atom(Function), is_list(Arguments) ->
|
||||
try apply(Module, Function, Arguments) of
|
||||
{ok, Result} when is_binary(Result) ->
|
||||
{ok, Result, State}
|
||||
catch
|
||||
E:R:S -> {E,R,S}
|
||||
end;
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add local function support (helper) to execute on the same module.
|
||||
%---------------------------------------------------------------------
|
||||
tag({apply, Function, Arguments}, Opts, State)
|
||||
when is_atom(Function), is_list(Arguments) ->
|
||||
try Function(Arguments) of
|
||||
{ok, Result} when is_binary(Result) ->
|
||||
{ok, Result, State}
|
||||
catch
|
||||
E:R:S -> {E,R,S}
|
||||
end;
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add support for function generator (without argument). Same as MFA.
|
||||
%---------------------------------------------------------------------
|
||||
tag({apply, Function}, Opts, State)
|
||||
when is_function(Function, 0) ->
|
||||
try Function() of
|
||||
{ok, Result} when is_binary(Result) ->
|
||||
{ok, Result, State}
|
||||
catch
|
||||
E:R:S -> {E,R,S}
|
||||
end;
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add support for function with opts. Same as MFA.
|
||||
%---------------------------------------------------------------------
|
||||
tag({apply, Function}, Opts, State)
|
||||
when is_function(Function, 1) ->
|
||||
try Function(Opts) of
|
||||
{ok, Result} when is_binary(Result) ->
|
||||
{ok, Result, State}
|
||||
catch
|
||||
E:R:S -> {E,R,S}
|
||||
end;
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add support for empty tag. Empty tags are special tags without
|
||||
% content.
|
||||
%---------------------------------------------------------------------
|
||||
tag({empty, Tag} = T, Opts, State) ->
|
||||
{stop, {todo, T, Opts}, State};
|
||||
tag({{empty, Tag} = T, Attributes}, Opts, State) ->
|
||||
{stop, {todo, T, Opts}, State};
|
||||
|
||||
%--------------------------------------------------------------------
|
||||
% some tags will behave differently:
|
||||
% - base
|
||||
% - br
|
||||
% - img
|
||||
% - input
|
||||
% - link
|
||||
% - meta
|
||||
% - source
|
||||
%--------------------------------------------------------------------
|
||||
tag({<<"code">>, Attributes, [Integer|_] = Content} = Tag, Opts, State)
|
||||
when is_list(Content), is_integer(Integer), Integer>0 ->
|
||||
Result = list_to_binary(Content),
|
||||
tag({<<"code">>, Attributes, Result}, Opts, State);
|
||||
tag({<<"code">>, Attributes, [List|_] = Content} = Tag, Opts, State)
|
||||
when is_list(Content), is_list(List) ->
|
||||
Result = list_to_binary(string:join(Content, "\n")),
|
||||
tag({<<"code">>, Attributes, Result}, Opts, State);
|
||||
tag({<<"code">>, Attributes, [Binary|_] = Content} = Tag, Opts, State)
|
||||
when is_list(Content), is_binary(Binary) ->
|
||||
Result = join(Content, <<"\n">>),
|
||||
tag({<<"code">>, Attributes, Result}, Opts, State);
|
||||
tag({<<"code">>, Attributes, Content}, Opts, State)
|
||||
when is_binary(Content) ->
|
||||
Begin = bracket(<<"code">>, <<>>),
|
||||
End = bracket_end(<<"code">>),
|
||||
{ok, <<Begin/binary, Content/binary, End/binary>>, State};
|
||||
|
||||
tag({<<"pre">>, Attributes, [Integer|_] = Content} = Tag, Opts, State)
|
||||
when is_list(Content), is_integer(Integer), Integer>0 ->
|
||||
Result = list_to_binary(Content),
|
||||
tag({<<"pre">>, Attributes, Result}, Opts, State);
|
||||
tag({<<"pre">>, Attributes, [List|_] = Content} = Tag, Opts, State)
|
||||
when is_list(Content), is_list(List) ->
|
||||
Result = list_to_binary(string:join(Content, "\n")),
|
||||
tag({<<"pre">>, Attributes, Result}, Opts, State);
|
||||
tag({<<"pre">>, Attributes, [Binary|_] = Content} = Tag, Opts, State)
|
||||
when is_list(Content), is_binary(Binary) ->
|
||||
Result = join(Content, <<"\n">>),
|
||||
tag({<<"pre">>, Attributes, Result}, Opts, State);
|
||||
tag({<<"pre">>, Attributes, Content}, Opts, State)
|
||||
when is_binary(Content) ->
|
||||
Begin = bracket(<<"pre">>, <<>>),
|
||||
End = bracket_end(<<"pre">>),
|
||||
{ok, <<Begin/binary, Content/binary, End/binary>>, State};
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add support for regular tag
|
||||
%---------------------------------------------------------------------
|
||||
tag({Tag, Content}, Opts, State) ->
|
||||
tag({Tag, #{}, Content}, Opts, State);
|
||||
tag({Tag, Attributes, Content}, Opts, State)
|
||||
when is_binary(Tag) ->
|
||||
case attributes(Tag, Attributes, Opts, State) of
|
||||
{ok, <<>>, NewState} ->
|
||||
Begin = bracket(Tag, <<>>),
|
||||
End = bracket_end(Tag),
|
||||
{ok, Begin, End, Content, State};
|
||||
{ok, Serialized, NewState} ->
|
||||
Begin = bracket(Tag, Serialized),
|
||||
End = bracket_end(Tag),
|
||||
{ok, Begin, End, Content, State}
|
||||
end;
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% add support when a list is present. If a list is present, we assume
|
||||
% this is a list of tags and we should treat them one by one.
|
||||
%---------------------------------------------------------------------
|
||||
tag(Tags, Opts, State)
|
||||
when is_list(Tags) ->
|
||||
{stop, {todo, Tags, Opts}, State};
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% All tags defined as atom, list or numbers are converted into binary
|
||||
% by default. It offers a flexible way to define tags for the developer.
|
||||
%---------------------------------------------------------------------
|
||||
tag({Tag, Content}, Opts, State) ->
|
||||
tag({Tag, #{}, Content}, Opts, State);
|
||||
tag({Tag, Attributes, Content}, Opts, State)
|
||||
when is_atom(Tag) ->
|
||||
NewTag = atom_to_binary(Tag),
|
||||
tag({NewTag, Attributes, Content}, Opts, State);
|
||||
tag({Tag, Attributes, Content}, Opts, State)
|
||||
when is_list(Tag) ->
|
||||
NewTag = list_to_binary(Tag),
|
||||
tag({NewTag, Attributes, Content}, Opts, State);
|
||||
tag({Tag, Attributes, Content}, Opts, State)
|
||||
when is_integer(Tag) ->
|
||||
NewTag = integer_to_binary(Tag),
|
||||
tag({NewTag, Attributes, Content}, Opts, State);
|
||||
tag({Tag, Attributes, Content}, Opts, State)
|
||||
when is_float(Tag) ->
|
||||
NewTag = float_to_binary(Tag),
|
||||
tag({NewTag, Attributes, Content}, Opts, State);
|
||||
|
||||
%--------------------------------------------------------------------
|
||||
% we assume binary, numbers, and atoms are text. these tags must be
|
||||
% protected and encoded with html entities.
|
||||
%--------------------------------------------------------------------
|
||||
tag(Text, Opts, State)
|
||||
when is_binary(Text); is_atom(Text);
|
||||
is_integer(Text); is_float(Text) ->
|
||||
content(Text, Opts, State);
|
||||
|
||||
%---------------------------------------------------------------------
|
||||
% If unsupported tags are present, we should stop.
|
||||
%---------------------------------------------------------------------
|
||||
tag(Unsupported, Opts, State) ->
|
||||
{stop, {todo, Unsupported, Opts}, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
attributes(Tag, Attributes, Opts, State) ->
|
||||
attributes(Tag, Attributes, maps:keys(Attributes), [], Opts, State).
|
||||
|
||||
attributes(Tag, Attributes, [], Buffer, Opts, State) ->
|
||||
{ok, join(lists:reverse(Buffer)), State};
|
||||
attributes(Tag, Attributes, [Key|Keys], Buffer, Opts, State) ->
|
||||
Value = maps:get(Key, Attributes),
|
||||
case attribute(Tag, Key, Value, Opts, State) of
|
||||
{ok, K, V, NewState} ->
|
||||
Pair = <<K/binary,"=",V/binary>>,
|
||||
attributes(Tag, Attributes, Keys, [Pair|Buffer], Opts, NewState);
|
||||
Elsewise ->
|
||||
Elsewise
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc main function used to generate html using erml tags.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec attribute(Tag, Key, Value, Opts, State) -> Return when
|
||||
Tag :: binary(),
|
||||
Key :: term(),
|
||||
Value :: term(),
|
||||
Opts :: map(),
|
||||
State :: term(),
|
||||
Return :: return().
|
||||
|
||||
% convert keys to binary
|
||||
attribute(Tag, Key, Value, Opts, State)
|
||||
when is_atom(Key) ->
|
||||
attribute(Tag, atom_to_binary(Key), Value, Opts, State);
|
||||
attribute(Tag, Key, Value, Opts, State)
|
||||
when is_list(Key) ->
|
||||
attribute(Tag, list_to_binary(Key), Value, Opts, State);
|
||||
|
||||
% convert values to binary
|
||||
attribute(Tag, Key, Value, Opts, State)
|
||||
when is_atom(Value) ->
|
||||
attribute(Tag, Key, atom_to_binary(Value), Opts, State);
|
||||
attribute(Tag, Key, Value, Opts, State)
|
||||
when is_list(Value) ->
|
||||
attribute(Tag, Key, list_to_binary(Value), Opts, State);
|
||||
|
||||
% some example of specific attributes
|
||||
attribute(<<"a">>, <<"href">> = Key, Value, Opts, State) ->
|
||||
{ok, Key, Value, State};
|
||||
attribute(<<"img">>, <<"src">> = Key, Value, Opts, State) ->
|
||||
{ok, Key, Value, State};
|
||||
attribute(_Tag, <<"style">>, Value, Opts, State) ->
|
||||
{ok, <<"style">>, Value, State};
|
||||
attribute(_Tag, <<"on", _/binary>> = Key, Value, Opts, State) ->
|
||||
{ok, Key, Value, State};
|
||||
attribute(_Tag, <<"hx-vals">> = Key, Value, Opts, State) ->
|
||||
{ok, Key, Value, State};
|
||||
attribute(_Tag, <<"hx-", _binary/binary>> = Key, Value, Opts, State) ->
|
||||
{ok, Key, Value, State};
|
||||
attribute(_Tag, <<"htmx-", _binary/binary>> = Key, Value, Opts, State) ->
|
||||
{ok, Key, Value, State};
|
||||
attribute(_Tag, Key, Value, Opts, State)
|
||||
when is_binary(Key), is_binary(Value) ->
|
||||
{ok, Key, quote(Value, $"), State};
|
||||
|
||||
% global attributes, by default, not supported
|
||||
attribute(Tag, Key, Value, Opts, State) ->
|
||||
{stop, {todo, Tag, Key, Value, Opts, State}, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc main function to check content (inner text).
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec content(Content, Opts, State) -> Return when
|
||||
Content :: term(),
|
||||
Opts :: map(),
|
||||
State :: term(),
|
||||
Return :: return().
|
||||
|
||||
content(Content, Opts, State)
|
||||
when is_atom(Content) ->
|
||||
content(atom_to_binary(Content), Opts, State);
|
||||
content(Content, Opts, State)
|
||||
when is_integer(Content) ->
|
||||
content(integer_to_binary(Content), Opts, State);
|
||||
content(Content, Opts, State)
|
||||
when is_float(Content) ->
|
||||
content(float_to_binary(Content), Opts, State);
|
||||
content(Content, Opts, State)
|
||||
when is_list(Content) ->
|
||||
content(list_to_binary(Content), Opts, State);
|
||||
content(Content, #{ entities := false } = _Opts, State)
|
||||
when is_binary(Content) ->
|
||||
{ok, Content, State};
|
||||
content(Content, Opts, State)
|
||||
when is_binary(Content) ->
|
||||
{ok, erml_html_entities:encode(Content), State};
|
||||
content(Content, Opts, State) ->
|
||||
{stop, {todo, content, Content, Opts, State}, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
join([]) -> <<>>;
|
||||
join(Binaries) -> join(Binaries, <<" ">>).
|
||||
|
||||
join([], _) -> <<>>;
|
||||
join(Binaries, Sep) -> join(Binaries, Sep, <<>>).
|
||||
|
||||
join([], _, Buffer) -> Buffer;
|
||||
join([Binary], _, Buffer) ->
|
||||
<<Binary/binary, Buffer/binary>>;
|
||||
join([Binary|Rest], Sep, Buffer) ->
|
||||
join(Rest, Sep, <<Sep/binary, Binary/binary, Buffer/binary>>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
quote(Value, Char) ->
|
||||
<<Char, Value/binary, Char>>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
bracket_empty(Element, <<>>) ->
|
||||
<<"<", Element/binary, " />">>;
|
||||
bracket_empty(Element, Attributes) ->
|
||||
<<"<", Element/binary, " ", Attributes/binary, " />">>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
bracket(Element, <<>>) ->
|
||||
<<"<", Element/binary, ">">>;
|
||||
bracket(Element, Attributes) ->
|
||||
<<"<", Element/binary, " ", Attributes/binary, ">">>.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
bracket_end(Element) ->
|
||||
<<"</", Element/binary, ">">>.
|
||||
5
src/erml_rebar_plugin.erl
Normal file
5
src/erml_rebar_plugin.erl
Normal file
@@ -0,0 +1,5 @@
|
||||
%%%===================================================================
|
||||
%%% @doc rebar3 erml plugin.
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(erml_rebar_plugin).
|
||||
18
src/erml_sup.erl
Normal file
18
src/erml_sup.erl
Normal file
@@ -0,0 +1,18 @@
|
||||
%%%===================================================================
|
||||
%%% @doc draft.
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(erml_sup).
|
||||
-behaviour(supervisor).
|
||||
-export([start_link/0]).
|
||||
-export([init/1]).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
SupFlags = #{strategy => one_for_all,
|
||||
intensity => 0,
|
||||
period => 1},
|
||||
ChildSpecs = [],
|
||||
{ok, {SupFlags, ChildSpecs}}.
|
||||
27
src/rebar3_erml_compiler.erl
Normal file
27
src/rebar3_erml_compiler.erl
Normal file
@@ -0,0 +1,27 @@
|
||||
%%%===================================================================
|
||||
%%% @doc
|
||||
%%% see: https://rebar3.org/docs/extending/custom_compiler_plugins/
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(rebar3_erml_compiler).
|
||||
-export([init/1, do/1, format_error/1]).
|
||||
|
||||
init(State) ->
|
||||
Opts = [{name, compile},
|
||||
{namespace, erml},
|
||||
{module, ?MODULE},
|
||||
{bare, true},
|
||||
{deps, [{default, app_discovery}]},
|
||||
{example, "rebar3 erml compile"},
|
||||
{opts, []},
|
||||
{short_desc, "Erml file compiler"},
|
||||
{desc, ""}
|
||||
],
|
||||
Provider = providers:create(Opts),
|
||||
{ok, rebar_state:add_provider(State, Provider)}.
|
||||
|
||||
do(State) ->
|
||||
{ok, State}.
|
||||
|
||||
format_error(Message) ->
|
||||
ok.
|
||||
122
test/erml_SUITE.erl
Normal file
122
test/erml_SUITE.erl
Normal file
@@ -0,0 +1,122 @@
|
||||
%%%===================================================================
|
||||
%%%
|
||||
%%%===================================================================
|
||||
-module(erml_SUITE).
|
||||
-compile(export_all).
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec suite() -> Info
|
||||
%% Info = [tuple()]
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
suite() ->
|
||||
[{timetrap,{seconds,30}}].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec init_per_suite(Config0) ->
|
||||
%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
|
||||
%% Config0 = Config1 = [tuple()]
|
||||
%% Reason = term()
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
init_per_suite(Config) ->
|
||||
Config.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec end_per_suite(Config0) -> term() | {save_config,Config1}
|
||||
%% Config0 = Config1 = [tuple()]
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
end_per_suite(_Config) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec init_per_group(GroupName, Config0) ->
|
||||
%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
|
||||
%% GroupName = atom()
|
||||
%% Config0 = Config1 = [tuple()]
|
||||
%% Reason = term()
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
init_per_group(_GroupName, Config) ->
|
||||
Config.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec end_per_group(GroupName, Config0) ->
|
||||
%% term() | {save_config,Config1}
|
||||
%% GroupName = atom()
|
||||
%% Config0 = Config1 = [tuple()]
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
end_per_group(_GroupName, _Config) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec init_per_testcase(TestCase, Config0) ->
|
||||
%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
|
||||
%% TestCase = atom()
|
||||
%% Config0 = Config1 = [tuple()]
|
||||
%% Reason = term()
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
Config.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec end_per_testcase(TestCase, Config0) ->
|
||||
%% term() | {save_config,Config1} | {fail,Reason}
|
||||
%% TestCase = atom()
|
||||
%% Config0 = Config1 = [tuple()]
|
||||
%% Reason = term()
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
end_per_testcase(_TestCase, _Config) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec groups() -> [Group]
|
||||
%% Group = {GroupName,Properties,GroupsAndTestCases}
|
||||
%% GroupName = atom()
|
||||
%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
|
||||
%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
|
||||
%% TestCase = atom()
|
||||
%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
|
||||
%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
|
||||
%% repeat_until_any_ok | repeat_until_any_fail
|
||||
%% N = integer() | forever
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
groups() ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec all() -> GroupsAndTestCases | {skip,Reason}
|
||||
%% GroupsAndTestCases = [{group,GroupName} | TestCase]
|
||||
%% GroupName = atom()
|
||||
%% TestCase = atom()
|
||||
%% Reason = term()
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
all() ->
|
||||
[my_test_case].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec TestCase() -> Info
|
||||
%% Info = [tuple()]
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
my_test_case() ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec TestCase(Config0) ->
|
||||
%% ok | exit() | {skip,Reason} | {comment,Comment} |
|
||||
%% {save_config,Config1} | {skip_and_save,Reason,Config1}
|
||||
%% Config0 = Config1 = [tuple()]
|
||||
%% Reason = term()
|
||||
%% Comment = term()
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
my_test_case(_Config) ->
|
||||
ok.
|
||||
10
test/erml_SUITE_data/code_simple.erml
Normal file
10
test/erml_SUITE_data/code_simple.erml
Normal file
@@ -0,0 +1,10 @@
|
||||
%%%===================================================================
|
||||
%%% Simple code template.
|
||||
%%%===================================================================
|
||||
{code, [
|
||||
<<"struct proc *p;">>,
|
||||
<<"struct process *pr;">>,
|
||||
<<"struct pdevinit *pdev;">>,
|
||||
<<"extern struct pdevinit pdevinit[];">>,
|
||||
<<"extern void disk_init(void);">>,
|
||||
]}.
|
||||
10
test/erml_SUITE_data/form_select.erml
Normal file
10
test/erml_SUITE_data/form_select.erml
Normal file
@@ -0,0 +1,10 @@
|
||||
%%%===================================================================
|
||||
%%% A form with select/option.
|
||||
%%%===================================================================
|
||||
{'div', #{class => "form-group"}, [
|
||||
{select, #{class => "form-select"}, [
|
||||
{option, [<<"Joe Armstrong">>]},
|
||||
{option, [<<"Robert Virding">>]},
|
||||
{option, [<<"Mike Williams">>]}
|
||||
]}
|
||||
]}.
|
||||
13
test/erml_SUITE_data/form_simple.erml
Normal file
13
test/erml_SUITE_data/form_simple.erml
Normal file
@@ -0,0 +1,13 @@
|
||||
%%%===================================================================
|
||||
%%% A simple form.
|
||||
%%%===================================================================
|
||||
{'div', #{class => "form-group"}, [
|
||||
{label, #{ class => "form-label"
|
||||
, for => "input-example-1", ["Name"]
|
||||
}, []},
|
||||
{input, #{ class => "form-input"
|
||||
, type => "text"
|
||||
, id => "input-example-1"
|
||||
, placeholder => "Name"
|
||||
}, []}
|
||||
]}.
|
||||
9
test/erml_SUITE_data/hero_simple.erml
Normal file
9
test/erml_SUITE_data/hero_simple.erml
Normal file
@@ -0,0 +1,9 @@
|
||||
%%%===================================================================
|
||||
%%% A simple hero page.
|
||||
%%%===================================================================
|
||||
{'div', #{class => "hero bg-gray"}, [
|
||||
{'div', #{class => "hero-body"}, [
|
||||
{h1, <<"Hero title">>},
|
||||
{p, <<"This is a hero example">>}
|
||||
]}
|
||||
]}.
|
||||
9
test/erml_SUITE_data/list_simple.erml
Normal file
9
test/erml_SUITE_data/list_simple.erml
Normal file
@@ -0,0 +1,9 @@
|
||||
%%%===================================================================
|
||||
%%% A static list.
|
||||
%%%===================================================================
|
||||
{ul, [
|
||||
{li, <<"In order to handle failure, you need two machines.">>},
|
||||
{li, <<"Shared memory is evil.">>},
|
||||
{li, <<"Erlang is like Meccano. Meccano is very good.">>},
|
||||
{li, <<"Bad ideas in computer science and anywhere are sticky.">>}
|
||||
]}.
|
||||
4
test/erml_SUITE_data/list_template.erml
Normal file
4
test/erml_SUITE_data/list_template.erml
Normal file
@@ -0,0 +1,4 @@
|
||||
%%%===================================================================
|
||||
%%% A template list.
|
||||
%%%===================================================================
|
||||
{ul, {list}}.
|
||||
19
test/erml_SUITE_data/page_complex.erml
Normal file
19
test/erml_SUITE_data/page_complex.erml
Normal file
@@ -0,0 +1,19 @@
|
||||
%%%===================================================================
|
||||
%%% A more complex page calling other tag.
|
||||
%%%===================================================================
|
||||
{html, [
|
||||
{head, [
|
||||
{title, <<"Lorem Ipsum">>}
|
||||
]},
|
||||
{body, [
|
||||
% calling a module
|
||||
{'div', #{id => "module"}, [
|
||||
{apply, {gabarit_test_module, random_paragraph, []}}
|
||||
]},
|
||||
% include a raw text
|
||||
% including a template with variable
|
||||
{'div', #{id => "template"}, [
|
||||
{include_template, "quote_template", #{quote => <<"test">>, author => <<"test">>}}
|
||||
]}
|
||||
]}
|
||||
]}.
|
||||
25
test/erml_SUITE_data/page_include.erml
Normal file
25
test/erml_SUITE_data/page_include.erml
Normal file
@@ -0,0 +1,25 @@
|
||||
%%%===================================================================
|
||||
%%% A more complex page calling other tag.
|
||||
%%%===================================================================
|
||||
{html, [
|
||||
{head, [
|
||||
{title, <<"Include test">>}
|
||||
]},
|
||||
{body, [
|
||||
{'div', #{id => "ascii"}, [
|
||||
{include_raw, "raw_ascii.txt"}
|
||||
]},
|
||||
{'div', #{id => "html"}, [
|
||||
{include_raw, "raw_html.txt"}
|
||||
]},
|
||||
{'div', #{id => "chinese"}, [
|
||||
{include_raw, "raw_chinese.txt"}
|
||||
]},
|
||||
{'div', #{id => "japanese"}, [
|
||||
{include_raw, "raw_japanese.txt"}
|
||||
]},
|
||||
{'div', #{id => "russian"}, [
|
||||
{include_raw, "raw_russian.txt"}
|
||||
]}
|
||||
]}
|
||||
]}.
|
||||
53
test/erml_SUITE_data/page_simple.erml
Normal file
53
test/erml_SUITE_data/page_simple.erml
Normal file
@@ -0,0 +1,53 @@
|
||||
%%%===================================================================
|
||||
%%% This is a simple template page containing Lorem Ipsum content.
|
||||
%%%===================================================================
|
||||
{html, [
|
||||
{head, [
|
||||
{title, <<"Lorem Ipsum">>}
|
||||
]},
|
||||
{body, [
|
||||
{h1, <<"Lorem Ipsum">>},
|
||||
{h2, <<"Neque porro quisquam est qui dolorem ipsum quia dolor sit"
|
||||
"amet, consectetur, adipisci velit...">>},
|
||||
{p, <<"Lorem ipsum dolor sit amet, consectetur adipiscing"
|
||||
"elit. Aenean vel lobortis augue, sit amet mollis"
|
||||
"tortor. Etiam ante tortor, rutrum sit amet dolor vel,"
|
||||
"maximus commodo lacus. Donec eu ex sed diam ullamcorper"
|
||||
"facilisis. Integer felis quam, rhoncus nec fermentum vel,"
|
||||
"aliquam eu libero. Donec lectus dui, mollis vitae elit sit"
|
||||
"amet, consectetur placerat nibh. Maecenas sit amet elit eu"
|
||||
"libero auctor volutpat ut sit amet metus. Nunc consequat"
|
||||
"facilisis dolor, non scelerisque diam luctus eu. Nunc"
|
||||
"bibendum lectus non nisi rhoncus convallis. Proin"
|
||||
"dignissim, nulla at mattis venenatis, quam massa ornare"
|
||||
"massa, dictum eleifend nisi sapien at quam. Donec lacinia"
|
||||
"dapibus augue vel consectetur. Nulla facilisi. Mauris non"
|
||||
"blandit lectus, imperdiet sollicitudin quam. Fusce semper"
|
||||
"nisi eu enim convallis ullamcorper. Aenean velit arcu,"
|
||||
"dictum ut arcu vitae, rutrum condimentum metus. Ut nulla"
|
||||
"ante, ultricies et urna eu, hendrerit ullamcorper"
|
||||
"massa. Vestibulum vestibulum volutpat tempus. ">>},
|
||||
{p, <<"Pellentesque interdum augue eu blandit"
|
||||
"molestie. Pellentesque iaculis mauris eget odio sagittis, a"
|
||||
"sagittis tortor molestie. Cras vestibulum leo vitae lorem"
|
||||
"feugiat, ac pretium tellus dictum. Integer tristique erat"
|
||||
"lectus, pellentesque mollis tortor tincidunt non. Sed et"
|
||||
"diam et dui dapibus egestas. Phasellus eu elementum lectus,"
|
||||
"ac pretium enim. Donec tempor, ipsum sit amet rhoncus"
|
||||
"rutrum, ante nibh volutpat nibh, eu consectetur mauris"
|
||||
"metus id tellus. Donec a mattis ex. Quisque aliquet est non"
|
||||
"erat porttitor, eget ultrices erat dignissim. Vestibulum"
|
||||
"ante ipsum primis in faucibus orci luctus et ultrices"
|
||||
"posuere cubilia curae;">>},
|
||||
{p, <<"Nunc sodales urna tortor. Vestibulum dui sem, venenatis non"
|
||||
"tellus vel, tristique tempor mauris. Mauris consectetur"
|
||||
"mauris a nisl suscipit pretium. Fusce diam odio,"
|
||||
"sollicitudin ac diam et, auctor gravida tellus. Vivamus est"
|
||||
"nisl, venenatis sit amet accumsan ac, sagittis id"
|
||||
"risus. Sed nec eros purus. Vestibulum vulputate nulla ac"
|
||||
"pellentesque tempus. Cras vehicula eros eu nulla mollis"
|
||||
"ornare. Pellentesque et tellus viverra, egestas diam et,"
|
||||
"iaculis dolor. Quisque facilisis turpis quis nibh bibendum"
|
||||
"posuere.">>}
|
||||
]}
|
||||
]}.
|
||||
7
test/erml_SUITE_data/quote_simple.erml
Normal file
7
test/erml_SUITE_data/quote_simple.erml
Normal file
@@ -0,0 +1,7 @@
|
||||
%%%===================================================================
|
||||
%%% A simple quote using blockquote and cite tags.
|
||||
%%%===================================================================
|
||||
{blockquote, [
|
||||
{p, <<"Four languages to learn: C, Prolog, Erlang, Javascript.">>},
|
||||
{cite, <<"Joe Armstrong">>}
|
||||
]}.
|
||||
7
test/erml_SUITE_data/quote_template.erml
Normal file
7
test/erml_SUITE_data/quote_template.erml
Normal file
@@ -0,0 +1,7 @@
|
||||
%%%===================================================================
|
||||
%%% A templating quote using blockquote and cite tags.
|
||||
%%%===================================================================
|
||||
{blockquote, [
|
||||
{p, [{quote}]},
|
||||
{cite, [{author}]}
|
||||
]}.
|
||||
49
test/erml_SUITE_data/raw_ascii.txt
Normal file
49
test/erml_SUITE_data/raw_ascii.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
Lorem Ipsum
|
||||
|
||||
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet,
|
||||
consectetur, adipisci velit...
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce non
|
||||
velit iaculis, congue risus non, posuere ante. Nunc gravida aliquet
|
||||
pellentesque. Nullam euismod sed dui elementum efficitur. Pellentesque
|
||||
elementum diam eget consequat rhoncus. Nunc nunc nisi, egestas sit
|
||||
amet feugiat vel, tristique eget risus. Nunc eget dui et libero
|
||||
placerat consectetur. Maecenas iaculis dui tincidunt mattis
|
||||
congue. Duis odio ligula, eleifend et ullamcorper eu, consectetur ac
|
||||
nisi. Pellentesque ut risus odio. Integer feugiat, tellus nec ultrices
|
||||
sollicitudin, nisi ex ornare tortor, in venenatis nulla ipsum cursus
|
||||
tortor.
|
||||
|
||||
Nunc molestie luctus convallis. Cras bibendum lorem convallis sapien
|
||||
iaculis feugiat. Maecenas porta dui ac elementum bibendum. Maecenas
|
||||
lobortis elit ex, vel auctor arcu ornare vitae. In hac habitasse
|
||||
platea dictumst. Morbi vitae leo sit amet lacus auctor faucibus. Nam
|
||||
bibendum ipsum turpis, sit amet posuere elit viverra eu. Maecenas
|
||||
tempus lacinia lectus, eu ultrices orci maximus nec.
|
||||
|
||||
Vivamus dignissim feugiat diam. Nunc vulputate vestibulum
|
||||
tempus. Donec semper id sapien in consectetur. Class aptent taciti
|
||||
sociosqu ad litora torquent per conubia nostra, per inceptos
|
||||
himenaeos. Nullam blandit tempus libero id lobortis. Etiam convallis
|
||||
vehicula ligula at auctor. Nullam et porta turpis, a porttitor
|
||||
odio. Aliquam imperdiet sit amet magna eu tempor. Nunc finibus porta
|
||||
ante nec ornare.
|
||||
|
||||
Interdum et malesuada fames ac ante ipsum primis in
|
||||
faucibus. Vestibulum ante ipsum primis in faucibus orci luctus et
|
||||
ultrices posuere cubilia curae; Quisque sit amet ex urna. Pellentesque
|
||||
habitant morbi tristique senectus et netus et malesuada fames ac
|
||||
turpis egestas. Quisque quis massa vel ante vestibulum rutrum. Cras eu
|
||||
sem facilisis dolor molestie ultrices. Donec luctus ante a lorem
|
||||
gravida tincidunt. Duis scelerisque quis tortor non fringilla. Morbi
|
||||
in efficitur nisl. Ut et felis at nunc tincidunt varius sit amet id
|
||||
lectus. Praesent ut nulla sed tortor volutpat imperdiet. Duis euismod
|
||||
velit et tincidunt suscipit. Nullam vitae tristique purus. Curabitur
|
||||
ut mi ipsum. Phasellus ultricies risus non odio fermentum porttitor.
|
||||
|
||||
Pellentesque non augue dolor. Ut euismod odio sit amet sapien
|
||||
fringilla sollicitudin. Vivamus vel erat quis eros tincidunt fringilla
|
||||
ac et metus. Aliquam odio justo, gravida eget aliquet feugiat,
|
||||
accumsan semper tortor. Donec pharetra massa erat, sed ullamcorper
|
||||
augue ultricies eget. Nam sit amet metus hendrerit nunc posuere porta
|
||||
non nec odio. Mauris pulvinar erat in massa dapibus mattis.
|
||||
1
test/erml_SUITE_data/raw_chinese.txt
Normal file
1
test/erml_SUITE_data/raw_chinese.txt
Normal file
@@ -0,0 +1 @@
|
||||
坏人活着是为了吃与喝,而好人却是为了活着才吃与喝。
|
||||
1
test/erml_SUITE_data/raw_html.txt
Normal file
1
test/erml_SUITE_data/raw_html.txt
Normal file
@@ -0,0 +1 @@
|
||||
<p>this is a test.</p>
|
||||
3
test/erml_SUITE_data/raw_japanese.txt
Normal file
3
test/erml_SUITE_data/raw_japanese.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
佛道をならふといふは 自己をならふ也 自己をならふといふは 自己をわする
|
||||
るなり 自己をわするるといふは 萬法に證せらるるなり 萬法に證せらるると
|
||||
いふは 自己の身心および他己の身心をして脱落せしむるなり
|
||||
10
test/erml_SUITE_data/raw_russian.txt
Normal file
10
test/erml_SUITE_data/raw_russian.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Желать вам всякого добра — я желаю, о мужи афиняне, и люблю вас, а
|
||||
слушаться буду скорее бога[комм. 1], чем вас, и, пока есть во мне
|
||||
дыхание и способность, не перестану философствовать, уговаривать и
|
||||
убеждать всякого из вас, кого только встречу, говоря то самое, что
|
||||
обыкновенно говорю: о лучший из мужей, гражданин города Афин,
|
||||
величайшего из городов и больше всех прославленного за мудрость и
|
||||
силу, не стыдно ли тебе, что ты заботишься о деньгах, чтобы их у тебя
|
||||
было как можно больше, о славе и о почестях, а о разумности, об истине
|
||||
и о душе своей, чтобы она была как можно лучше, — не заботишься и не
|
||||
помышляешь?
|
||||
Reference in New Issue
Block a user