Add new license and start to do something useful.
Added also few notes and tests. That's still a draft and it should not be used...
This commit is contained in:
200
LICENSE.md
200
LICENSE.md
@@ -1,186 +1,20 @@
|
||||
# Apache License
|
||||
Version 2.0, January 2004
|
||||
Copyright 2023 Mathieu Kerjouan
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
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:
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
## 1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
## 2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
## 3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
## 4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
1. You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
|
||||
2. You must cause any modified files to carry prominent notices stating that
|
||||
You changed the files; and
|
||||
|
||||
3. You must retain, in the Source form of any Derivative Works that You
|
||||
distribute, all copyright, patent, trademark, and attribution notices from
|
||||
the Source form of the Work, excluding those notices that do not pertain to
|
||||
any part of the Derivative Works; and
|
||||
|
||||
4. If the Work includes a "NOTICE" text file as part of its distribution, then
|
||||
any Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those
|
||||
notices that do not pertain to any part of the Derivative Works, in at least
|
||||
one of the following places: within a NOTICE text file distributed as part
|
||||
of the Derivative Works; within the Source form or documentation, if
|
||||
provided along with the Derivative Works; or, within a display generated by
|
||||
the Derivative Works, if and wherever such third-party notices normally
|
||||
appear. The contents of the NOTICE file are for informational purposes only
|
||||
and do not modify the License. You may add Your own attribution notices
|
||||
within Derivative Works that You distribute, alongside or as an addendum to
|
||||
the NOTICE text from the Work, provided that such additional attribution
|
||||
notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
## 5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
## 6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
## 7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE, NON-
|
||||
INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
## 8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
## 9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2023, Mathieu Kerjouan <contact@steepath.eu>.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
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.
|
||||
|
||||
132
README.md
132
README.md
@@ -1,7 +1,15 @@
|
||||
# gabarit
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Gabarit, a template engine manager for Erlang/OTP.
|
||||
|
||||
**!!!This is a draft, please don't use it in production!!!**
|
||||
|
||||
Gabarit was created to offer an easy way to deal with template engines
|
||||
from the Erlang community but also to deal with multi-template engines
|
||||
running on the same cluster.
|
||||
@@ -15,8 +23,8 @@ templates using merl.
|
||||
|
||||
## Features
|
||||
|
||||
- [ ] templates versionned during compilation
|
||||
- [ ] templates compiled with timestamp
|
||||
- [x] templates versionned during compilation
|
||||
- [x] templates compiled with timestamp
|
||||
- [ ] hardcoded templates limits
|
||||
- [ ] templates compilation limit during runtime
|
||||
- [ ] automatic or dynamic reload
|
||||
@@ -24,6 +32,9 @@ templates using merl.
|
||||
- [ ] rollback template based on version
|
||||
- [ ] export feature
|
||||
- [ ] template edition facility (text editor integration)
|
||||
- [ ] namespace support
|
||||
- [ ] default value and options based on namespace
|
||||
- [ ] partial templating
|
||||
- [ ] project manager support
|
||||
- [ ] rebar3 plugin
|
||||
- [ ] erlang.mk plugin
|
||||
@@ -150,8 +161,121 @@ gabarit:render("path", #{}, []).
|
||||
gabarit:edit("path").
|
||||
```
|
||||
|
||||
## Limitations
|
||||
## Ideas
|
||||
|
||||
This project is side project created to play and improve template
|
||||
management on Erlang/OTP system. Here list of ideas we would like to
|
||||
work on.
|
||||
|
||||
### In Memory Compressed Templates
|
||||
|
||||
Templates can be really huge, and compressing them can save lot of
|
||||
place, even more if they are using in a loaded module in the BEAM.
|
||||
|
||||
### Limitations
|
||||
|
||||
Erlang modules are compiled using atoms, atoms are limited and not
|
||||
garbage collected. That means it could crash a production system if
|
||||
you want to dynamically
|
||||
you want to dynamically.
|
||||
|
||||
### Partial Templates
|
||||
|
||||
Sometimes, only a small part of the template needs to be dynamic, the
|
||||
other part will stay static. Partial template will create a partial
|
||||
template module with already replaced values in it.
|
||||
|
||||
### Development Environment
|
||||
|
||||
In development environment, templates can easily be upgraded and
|
||||
updated on the fly, with local version. When one or more templates are
|
||||
validated by developers, they can be exported with a fixed version.
|
||||
|
||||
### Production Environment
|
||||
|
||||
In production environment, templates are locked and can't be updated
|
||||
on the fly, except during an explicit update/upgrade of the
|
||||
application.
|
||||
|
||||
### Distributed Templates
|
||||
|
||||
In distributed environment, templates must be available on each nodes
|
||||
requiring this module, even if the distribution does not have access
|
||||
to them locally. We should probably also provide a RPC function.
|
||||
|
||||
### Custom Template Engine
|
||||
|
||||
A custom template engine with an AST representation and many way to
|
||||
modify them, but without logic in it (avoid using if, for, while
|
||||
expression). It should be developed in pure Erlang.
|
||||
|
||||
### Structured and Unstructured Template Engine Support
|
||||
|
||||
It could be great to support both structured and unstructured template
|
||||
engines to generate, for example, XML, HTML, JSON but also Mardown and
|
||||
raw textual files.
|
||||
|
||||
### What About Binary Template Engine?
|
||||
|
||||
We are used to use textual templates, but rarely binary templates. In
|
||||
fact, having binary template could be an extension of pattern matching
|
||||
in Erlang, when one specific binary pattern can be replaced, extended,
|
||||
removed on some part in data. I don't think it should be part of this
|
||||
project, but could be a great idea to implement. What kind of usage?
|
||||
Network package payload modification on the fly, data fuzzing and so
|
||||
on. Here a code example of what I have in mind:
|
||||
|
||||
```erlang
|
||||
% A mapping is a document containing address on a binary map with
|
||||
% default instructions when the pattern needs to be changed.
|
||||
Identifier = "identifier".
|
||||
Mapping = [{Identifier, {From, To}}
|
||||
,{Identifier2, {From, {absolute, To}}, Opts2}
|
||||
,{Identifier3, {From, {relative, To}}, Opts3}
|
||||
,{Identifier4, {From, To}, [reversed]}
|
||||
% set an elf_magic identifier to replace the magic
|
||||
% number used by an executable binary file using
|
||||
% ELF. It will be a fixed size.
|
||||
,{"elf_magic", {16#00, 16#05}, [fixed]}
|
||||
].
|
||||
{ok, Cat} = file:read_file("/bin/cat").
|
||||
{ok, Module} = gabarit_binary:compile(Cat, Mapping).
|
||||
|
||||
% returns the data
|
||||
Module:data().
|
||||
|
||||
Module:analyze().
|
||||
Module:analyze(fun(X) ->
|
||||
|
||||
% print the default mapping used for this file
|
||||
Module:mapping().
|
||||
|
||||
% replace a pattern by a term
|
||||
Module:replace(Identifier, Term).
|
||||
|
||||
% delete a pattern and resize the payload
|
||||
Module:delete(Identifier).
|
||||
|
||||
% nullify a pattern (replace by 0x00).
|
||||
Module:nullify(Identifier).
|
||||
|
||||
% randomize a pattern (replace with random payload)
|
||||
Module:randomize(Identifier).
|
||||
|
||||
% clone a module with new identifier and/or already
|
||||
% replaced values
|
||||
Module:clone([{Identifier, {replace, Term, Opts}}]
|
||||
,[{"newid", {From, To}, Opts}]).
|
||||
|
||||
% manual replacement
|
||||
% {replace, {From, To}, Term, Opts}
|
||||
% {nullify, {From, To}, Opts}
|
||||
% {delete, {From, To}, Opts}
|
||||
% {randomize, {From, To}, Opts}
|
||||
Module:apply(Command, Opts).
|
||||
Module:apply([Command, Command], Opts).
|
||||
|
||||
% some function to apply on the loaded payload
|
||||
Module:map(fun(Integer) -> Integer end, Size).
|
||||
Module:reduce(fun(Integer, BinAcc) -> <<Integer, BinAcc/binary>> end, Size).
|
||||
|
||||
```
|
||||
|
||||
@@ -1 +1,67 @@
|
||||
%%%===================================================================
|
||||
%%% @doc
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(gabarit).
|
||||
-export([new/1]).
|
||||
-export([template/1]).
|
||||
-export([format/2, format/3]).
|
||||
-export([render/2, render/3]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc Create a new template from file present in default template
|
||||
%% path.
|
||||
%%
|
||||
%% == Example ==
|
||||
%%
|
||||
%% ```
|
||||
%% {ok, Module} = gabaric:new("index.html").
|
||||
%% '''
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec new(Filename) -> Return when
|
||||
Filename :: string() | binary(),
|
||||
Return :: {ok, Module},
|
||||
Module :: atom().
|
||||
new(Filename) ->
|
||||
Template = gabarit_compiler:template_file(Filename),
|
||||
gabarit_compiler:compile_file(Template).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
template(Filename) ->
|
||||
ModuleName = gabarit_compiler:find_template_file_module(Filename),
|
||||
ModuleName:template().
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
format(Filename, Context) ->
|
||||
format(Filename, Context, []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
format(Filename, Context, Opts) ->
|
||||
ModuleName = gabarit_compiler:find_template_file_module(Filename),
|
||||
ModuleName:format(Context, Opts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
render(Filename, Context) ->
|
||||
render(Filename, Context, []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
render(Filename, Context, Opts) ->
|
||||
ModuleName = gabarit_compiler:find_template_file_module(Filename),
|
||||
ModuleName:render(Context, Opts).
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
%%%===================================================================
|
||||
%%% @doc
|
||||
%%% @doc private interface module.
|
||||
%%% @end
|
||||
%%%===================================================================
|
||||
-module(gabarit_compiler).
|
||||
-compile(export_all).
|
||||
-export([path/0, tree/0, tree/1]).
|
||||
-define(DEFAULT_PATH, "priv/templates").
|
||||
-define(DEFAULT_PREFIX_FILE, "gabarit@").
|
||||
-define(DEFAULT_PREFIX_STRING, "gabarit$").
|
||||
-define(DEFAULT_MERL_TEMPLATE, "priv/gabarit/gabarit_template.erl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc compile
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
compile_file(#{ name := Identifier } = Struct) ->
|
||||
ModuleName = create_module_name(?DEFAULT_PREFIX_FILE, Identifier),
|
||||
NewStruct = Struct#{ module_name => ModuleName },
|
||||
case merl_compile_and_load(NewStruct) of
|
||||
{ok, _} ->
|
||||
{ok, ModuleName};
|
||||
Elsewise -> Elsewise
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
compile(#{ name := Identifier } = Struct) ->
|
||||
ModuleName = create_module_name("gabarit@", Identifier),
|
||||
compile_string(#{ name := Identifier } = Struct) ->
|
||||
ModuleName = create_module_name(?DEFAULT_PREFIX_STRING, Identifier),
|
||||
merl_compile_and_load(Struct#{ module_name => ModuleName }).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
compile(Struct) when is_map(Struct) ->
|
||||
merl_compile_and_load(Struct).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
find_and_compile() ->
|
||||
Tree = tree(),
|
||||
lists:map(fun compile/1, Tree).
|
||||
lists:map(fun compile_file/1, Tree).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
@@ -42,6 +63,17 @@ create_module_name(Prefix, Identifier) ->
|
||||
_:_ -> erlang:list_to_atom(ModuleName)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
find_template_file_module(Identifier) ->
|
||||
ModuleName = string:concat(?DEFAULT_PREFIX_FILE, Identifier),
|
||||
try
|
||||
erlang:list_to_existing_atom(ModuleName)
|
||||
catch
|
||||
_:_ -> throw({error, ModuleName})
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc list the current path used to load the templates.
|
||||
%% @end
|
||||
@@ -61,17 +93,17 @@ tree() ->
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
tree(Path) -> tree(Path, []).
|
||||
tree(Path) -> tree(Path, [], Path).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @hidden
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
tree(Path, Buffer) ->
|
||||
tree(Path, Buffer, Root) ->
|
||||
case file:list_dir(Path) of
|
||||
{ok, Files} ->
|
||||
tree(Path, Files, Buffer);
|
||||
tree(Path, Files, Buffer, Root);
|
||||
{error, Error} ->
|
||||
throw({error, Error})
|
||||
end.
|
||||
@@ -81,36 +113,89 @@ tree(Path, Buffer) ->
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
tree(_, [], Buffer) -> Buffer;
|
||||
tree(Path, [File|Files], Buffer) ->
|
||||
tree(_, [], Buffer, Root) -> Buffer;
|
||||
tree(Path, [File|Files], Buffer, Root) ->
|
||||
FilePath = filename:join([Path, File]),
|
||||
case filelib:is_dir(FilePath) of
|
||||
true -> tree(FilePath, Buffer);
|
||||
false -> tree(Path, Files, [{Path, FilePath}|Buffer])
|
||||
true ->
|
||||
tree(FilePath, Buffer, Root);
|
||||
false ->
|
||||
F = filename:split(FilePath),
|
||||
R = filename:split(Root),
|
||||
S = lists:subtract(F, R),
|
||||
RelativePath = filename:join(S),
|
||||
tree(Path, Files, [{Path, FilePath, RelativePath}|Buffer], Root)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @hidden
|
||||
%% @doc paths filtering.
|
||||
%% @see template_file/2
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
tree_filter({Key, Value}, Acc) ->
|
||||
Origin = filename:split(Key),
|
||||
Root = filename:split(path()),
|
||||
Filename = lists:last(filename:split(Value)),
|
||||
AbsolutePath = filename:absname(Value),
|
||||
Subtract = lists:subtract(Origin, Root),
|
||||
Identifier = case Subtract of
|
||||
[] -> filename:join(["/", Filename]);
|
||||
_ -> filename:join(["/", Subtract, Filename])
|
||||
end,
|
||||
{ok, Content} = file:read_file(AbsolutePath),
|
||||
[#{ name => Identifier
|
||||
-spec tree_filter({TemplatePath, TemplateFile, RelativePath}, Acc) -> Return when
|
||||
TemplatePath :: string(),
|
||||
TemplateFile :: string(),
|
||||
RelativePath :: string(),
|
||||
Acc :: [string(), ...],
|
||||
Return :: Acc.
|
||||
tree_filter({TemplatePath, _TemplateFile, RelativePath}, Acc) ->
|
||||
Return = template_file(TemplatePath, RelativePath),
|
||||
[Return|Acc].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
template_file(Filename) ->
|
||||
TemplatePath = path(),
|
||||
template_file(TemplatePath, Filename).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @hidden
|
||||
%% @doc this function takes the default path where the file should be
|
||||
%% stored and the filename present in this path. If everything is fine,
|
||||
%% it will output a map containing all information required to use
|
||||
%% the file as template.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec template_file(TemplatePath, TemplateFile) -> Return when
|
||||
TemplatePath :: string(),
|
||||
TemplateFile :: string(),
|
||||
Return :: map().
|
||||
template_file(TemplatePath, TemplateFile) ->
|
||||
case filelib:safe_relative_path(TemplateFile, TemplatePath) of
|
||||
unsafe -> {error, unsafe};
|
||||
SafeFile ->
|
||||
SafePath = filename:join(path(), SafeFile),
|
||||
AbsolutePath = filename:absname(SafePath),
|
||||
Filename = filename:basename(SafePath),
|
||||
Identifier = template_file_identifier(TemplatePath, Filename),
|
||||
case file:read_file(SafePath) of
|
||||
{ok, Content} ->
|
||||
#{ name => Identifier
|
||||
, filename => Filename
|
||||
, absolute_path => AbsolutePath
|
||||
, relative_path => Key
|
||||
, relative_path => TemplatePath
|
||||
, template => Content
|
||||
}|Acc].
|
||||
};
|
||||
{error, Error} ->
|
||||
throw(Error)
|
||||
end
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% create a file identifier based on default path
|
||||
%%--------------------------------------------------------------------
|
||||
template_file_identifier(TemplatePath, Filename) ->
|
||||
Origin = filename:split(TemplatePath),
|
||||
Root = filename:split(path()),
|
||||
Subtract = lists:subtract(Origin, Root),
|
||||
template_file_identifier2(Filename, Subtract).
|
||||
|
||||
template_file_identifier2(Filename, []) ->
|
||||
filename:join(["/", Filename]);
|
||||
template_file_identifier2(Filename, Subtract) ->
|
||||
filename:join(["/", Subtract, Filename]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @hidden
|
||||
|
||||
19
src/gabarit_module.erl
Normal file
19
src/gabarit_module.erl
Normal file
@@ -0,0 +1,19 @@
|
||||
-module(gabarit_module).
|
||||
-compile(export_all).
|
||||
-define(DEFAULT_PREFIX_FILE, "gabarit@").
|
||||
-define(DEFAULT_PREFIX_STRING, "gabarit$").
|
||||
|
||||
name(Prefix, Identifier) ->
|
||||
ModuleName = string:concat(Prefix, Identifier),
|
||||
try
|
||||
{ok, erlang:list_to_existing_atom(ModuleName)}
|
||||
catch
|
||||
_:_ -> erlang:list_to_atom(ModuleName)
|
||||
end.
|
||||
|
||||
exist(Name) ->
|
||||
try
|
||||
_Module = erlang:list_to_existing_atom(Name)
|
||||
catch
|
||||
_:_ -> false
|
||||
end.
|
||||
19
src/gabarit_store.erl
Normal file
19
src/gabarit_store.erl
Normal file
@@ -0,0 +1,19 @@
|
||||
%%%===================================================================
|
||||
%%%
|
||||
%%%===================================================================
|
||||
-module(gabarit_store).
|
||||
-behavior(gen_statem).
|
||||
-compile(export_all).
|
||||
-record(state, { store :: reference() }).
|
||||
|
||||
callback_mode() -> state_functions.
|
||||
|
||||
init(_Args) ->
|
||||
Ets = ets:new(?MODULE, [private]),
|
||||
{ok, unlocked, #state{ store = Ets }}.
|
||||
|
||||
unlocked(_,_,Data) ->
|
||||
{keep_state, Data}.
|
||||
|
||||
locked(_,_,Data) ->
|
||||
{keep_state, Data}.
|
||||
1
test/fixtures/mustache_templates/index.html
vendored
Normal file
1
test/fixtures/mustache_templates/index.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<body>{{content}}</body>
|
||||
130
test/gabarit_SUITE.erl
Normal file
130
test/gabarit_SUITE.erl
Normal file
@@ -0,0 +1,130 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author <user@disp3269>
|
||||
%%% @copyright (C) 2023,
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 1 Aug 2023 by <user@disp3269>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(gabarit_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) ->
|
||||
application:set_env(gabarit, templates_path, "../../fixtures/mustache_templates/"),
|
||||
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() ->
|
||||
[common].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @spec TestCase() -> Info
|
||||
%% Info = [tuple()]
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
common() ->
|
||||
[].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @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
|
||||
%%--------------------------------------------------------------------
|
||||
common(_Config) ->
|
||||
{ok, Module} = gabarit:new("index.html").
|
||||
Reference in New Issue
Block a user