first commit

This commit is contained in:
niamtokik
2023-11-30 17:49:34 +00:00
commit aaa3622860
33 changed files with 6053 additions and 0 deletions

20
.gitignore vendored Normal file
View 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
View 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
View File

@@ -0,0 +1,9 @@
# erml
An OTP application
## Build
```erlang
rebar3 compile
```

13
examples/htmx/card.erml Normal file
View 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
View 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
View File

7
rebar.config Normal file
View File

@@ -0,0 +1,7 @@
{erl_opts, [debug_info]}.
{deps, []}.
{shell, [
% {config, "config/sys.config"},
{apps, [erml]}
]}.

14
src/erml.app.src Normal file
View 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
View 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
View 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
View 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&apos;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=\"&amp;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

File diff suppressed because it is too large Load Diff

489
src/erml_html_tag.erl Normal file
View 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, ">">>.

View File

@@ -0,0 +1,5 @@
%%%===================================================================
%%% @doc rebar3 erml plugin.
%%% @end
%%%===================================================================
-module(erml_rebar_plugin).

18
src/erml_sup.erl Normal file
View 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}}.

View 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
View 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.

View 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);">>,
]}.

View 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">>]}
]}
]}.

View 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"
}, []}
]}.

View 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">>}
]}
]}.

View 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.">>}
]}.

View File

@@ -0,0 +1,4 @@
%%%===================================================================
%%% A template list.
%%%===================================================================
{ul, {list}}.

View 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">>}}
]}
]}
]}.

View 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"}
]}
]}
]}.

View 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.">>}
]}
]}.

View 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">>}
]}.

View File

@@ -0,0 +1,7 @@
%%%===================================================================
%%% A templating quote using blockquote and cite tags.
%%%===================================================================
{blockquote, [
{p, [{quote}]},
{cite, [{author}]}
]}.

View 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.

View File

@@ -0,0 +1 @@
坏人活着是为了吃与喝,而好人却是为了活着才吃与喝。

View File

@@ -0,0 +1 @@
<p>this is a test.</p>

View File

@@ -0,0 +1,3 @@
佛道をならふといふは 自己をならふ也 自己をならふといふは 自己をわする
るなり 自己をわするるといふは 萬法に證せらるるなり 萬法に證せらるると
いふは 自己の身心および他己の身心をして脱落せしむるなり

View File

@@ -0,0 +1,10 @@
Желать вам всякого добра — я желаю, о мужи афиняне, и люблю вас, а
слушаться буду скорее бога[комм. 1], чем вас, и, пока есть во мне
дыхание и способность, не перестану философствовать, уговаривать и
убеждать всякого из вас, кого только встречу, говоря то самое, что
обыкновенно говорю: о лучший из мужей, гражданин города Афин,
величайшего из городов и больше всех прославленного за мудрость и
силу, не стыдно ли тебе, что ты заботишься о деньгах, чтобы их у тебя
было как можно больше, о славе и о почестях, а о разумности, об истине
и о душе своей, чтобы она была как можно лучше, — не заботишься и не
помышляешь?