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
|
Copyright 2023 Mathieu Kerjouan
|
||||||
Version 2.0, January 2004
|
|
||||||
|
|
||||||
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.
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
"License" shall mean the terms and conditions for use, reproduction, and
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
distribution as defined by Sections 1 through 9 of this document.
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
owner that is granting the License.
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
"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.
|
|
||||||
|
|||||||
132
README.md
132
README.md
@@ -1,7 +1,15 @@
|
|||||||
# gabarit
|
# gabarit
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
Gabarit, a template engine manager for Erlang/OTP.
|
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
|
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
|
from the Erlang community but also to deal with multi-template engines
|
||||||
running on the same cluster.
|
running on the same cluster.
|
||||||
@@ -15,8 +23,8 @@ templates using merl.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- [ ] templates versionned during compilation
|
- [x] templates versionned during compilation
|
||||||
- [ ] templates compiled with timestamp
|
- [x] templates compiled with timestamp
|
||||||
- [ ] hardcoded templates limits
|
- [ ] hardcoded templates limits
|
||||||
- [ ] templates compilation limit during runtime
|
- [ ] templates compilation limit during runtime
|
||||||
- [ ] automatic or dynamic reload
|
- [ ] automatic or dynamic reload
|
||||||
@@ -24,6 +32,9 @@ templates using merl.
|
|||||||
- [ ] rollback template based on version
|
- [ ] rollback template based on version
|
||||||
- [ ] export feature
|
- [ ] export feature
|
||||||
- [ ] template edition facility (text editor integration)
|
- [ ] template edition facility (text editor integration)
|
||||||
|
- [ ] namespace support
|
||||||
|
- [ ] default value and options based on namespace
|
||||||
|
- [ ] partial templating
|
||||||
- [ ] project manager support
|
- [ ] project manager support
|
||||||
- [ ] rebar3 plugin
|
- [ ] rebar3 plugin
|
||||||
- [ ] erlang.mk plugin
|
- [ ] erlang.mk plugin
|
||||||
@@ -150,8 +161,121 @@ gabarit:render("path", #{}, []).
|
|||||||
gabarit:edit("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
|
Erlang modules are compiled using atoms, atoms are limited and not
|
||||||
garbage collected. That means it could crash a production system if
|
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).
|
-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
|
%%% @end
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
-module(gabarit_compiler).
|
-module(gabarit_compiler).
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-export([path/0, tree/0, tree/1]).
|
-export([path/0, tree/0, tree/1]).
|
||||||
-define(DEFAULT_PATH, "priv/templates").
|
-define(DEFAULT_PATH, "priv/templates").
|
||||||
|
-define(DEFAULT_PREFIX_FILE, "gabarit@").
|
||||||
|
-define(DEFAULT_PREFIX_STRING, "gabarit$").
|
||||||
-define(DEFAULT_MERL_TEMPLATE, "priv/gabarit/gabarit_template.erl").
|
-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) ->
|
compile_string(#{ name := Identifier } = Struct) ->
|
||||||
ModuleName = create_module_name("gabarit@", Identifier),
|
ModuleName = create_module_name(?DEFAULT_PREFIX_STRING, Identifier),
|
||||||
merl_compile_and_load(Struct#{ module_name => ModuleName }).
|
merl_compile_and_load(Struct#{ module_name => ModuleName }).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
compile(Struct) when is_map(Struct) ->
|
||||||
|
merl_compile_and_load(Struct).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%%
|
%%
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
find_and_compile() ->
|
find_and_compile() ->
|
||||||
Tree = tree(),
|
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)
|
_:_ -> erlang:list_to_atom(ModuleName)
|
||||||
end.
|
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.
|
%% @doc list the current path used to load the templates.
|
||||||
%% @end
|
%% @end
|
||||||
@@ -61,17 +93,17 @@ tree() ->
|
|||||||
%% @doc
|
%% @doc
|
||||||
%% @end
|
%% @end
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
tree(Path) -> tree(Path, []).
|
tree(Path) -> tree(Path, [], Path).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% @hidden
|
%% @hidden
|
||||||
%% @doc
|
%% @doc
|
||||||
%% @end
|
%% @end
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
tree(Path, Buffer) ->
|
tree(Path, Buffer, Root) ->
|
||||||
case file:list_dir(Path) of
|
case file:list_dir(Path) of
|
||||||
{ok, Files} ->
|
{ok, Files} ->
|
||||||
tree(Path, Files, Buffer);
|
tree(Path, Files, Buffer, Root);
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
throw({error, Error})
|
throw({error, Error})
|
||||||
end.
|
end.
|
||||||
@@ -81,36 +113,89 @@ tree(Path, Buffer) ->
|
|||||||
%% @doc
|
%% @doc
|
||||||
%% @end
|
%% @end
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
tree(_, [], Buffer) -> Buffer;
|
tree(_, [], Buffer, Root) -> Buffer;
|
||||||
tree(Path, [File|Files], Buffer) ->
|
tree(Path, [File|Files], Buffer, Root) ->
|
||||||
FilePath = filename:join([Path, File]),
|
FilePath = filename:join([Path, File]),
|
||||||
case filelib:is_dir(FilePath) of
|
case filelib:is_dir(FilePath) of
|
||||||
true -> tree(FilePath, Buffer);
|
true ->
|
||||||
false -> tree(Path, Files, [{Path, FilePath}|Buffer])
|
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.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% @hidden
|
%% @hidden
|
||||||
%% @doc paths filtering.
|
%% @doc paths filtering.
|
||||||
|
%% @see template_file/2
|
||||||
%% @end
|
%% @end
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
tree_filter({Key, Value}, Acc) ->
|
-spec tree_filter({TemplatePath, TemplateFile, RelativePath}, Acc) -> Return when
|
||||||
Origin = filename:split(Key),
|
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 => TemplatePath
|
||||||
|
, template => Content
|
||||||
|
};
|
||||||
|
{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()),
|
Root = filename:split(path()),
|
||||||
Filename = lists:last(filename:split(Value)),
|
|
||||||
AbsolutePath = filename:absname(Value),
|
|
||||||
Subtract = lists:subtract(Origin, Root),
|
Subtract = lists:subtract(Origin, Root),
|
||||||
Identifier = case Subtract of
|
template_file_identifier2(Filename, Subtract).
|
||||||
[] -> filename:join(["/", Filename]);
|
|
||||||
_ -> filename:join(["/", Subtract, Filename])
|
template_file_identifier2(Filename, []) ->
|
||||||
end,
|
filename:join(["/", Filename]);
|
||||||
{ok, Content} = file:read_file(AbsolutePath),
|
template_file_identifier2(Filename, Subtract) ->
|
||||||
[#{ name => Identifier
|
filename:join(["/", Subtract, Filename]).
|
||||||
, filename => Filename
|
|
||||||
, absolute_path => AbsolutePath
|
|
||||||
, relative_path => Key
|
|
||||||
, template => Content
|
|
||||||
}|Acc].
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% @hidden
|
%% @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