New upstream version 3.17.0
commit
36bc1e1299
|
@ -0,0 +1,13 @@
|
|||
# EditorConfig file: http://EditorConfig.org
|
||||
|
||||
# Top-most EditorConfig file.
|
||||
root = true
|
||||
|
||||
# Unix-style, newlines, indent style of 4 spaces, with a newline ending every file.
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
|
@ -0,0 +1,322 @@
|
|||
# Contributing to Rebar3
|
||||
|
||||
1. [License](#license)
|
||||
2. [Submitting a bug](#submitting-a-bug)
|
||||
3. [Requesting or implementing a feature](#requesting-or-implementing-a-feature)
|
||||
4. [Project Structure](#project-structure)
|
||||
5. [Tests](#tests)
|
||||
6. [Submitting your changes](#submitting-your-changes)
|
||||
1. [Code Style](#code-style)
|
||||
2. [Committing your changes](#committing-your-changes)
|
||||
3. [Pull Requests and Branching](#pull-requests-and-branching)
|
||||
4. [Credit](#credit)
|
||||
|
||||
## License ##
|
||||
|
||||
Rebar3 is licensed under the [Apache License 2.0](LICENSE) for all new code.
|
||||
However, since it is built from older code bases, some files still hold other
|
||||
free licenses (such as BSD). Where it is the case, the license is added in
|
||||
comments.
|
||||
|
||||
All files without specific headers can safely be assumed to be under Apache
|
||||
2.0.
|
||||
|
||||
## Submitting a Bug
|
||||
|
||||
Bugs can be submitted to the [Github issue page](https://github.com/erlang/rebar3/issues).
|
||||
|
||||
Rebar3 is not perfect software and will be buggy. When submitting a bug, be
|
||||
careful to know the following:
|
||||
|
||||
- The Erlang version you are running
|
||||
- The Rebar3 version you are using
|
||||
- The command you were attempting to run
|
||||
|
||||
This information can be automatically generated to put into your bug report
|
||||
by calling `rebar3 report "my command"`.
|
||||
|
||||
You may be asked for further information regarding:
|
||||
|
||||
- Your environment, including the Erlang version used to compile rebar3,
|
||||
details about your operating system, where your copy of Erlang was installed
|
||||
from, and so on;
|
||||
- Your project, including its structure, and possibly to remove build
|
||||
artifacts to start from a fresh build
|
||||
- What it is you are trying to do exactly; we may provide alternative
|
||||
means to do so.
|
||||
|
||||
If you can provide an example code base to reproduce the issue on, we will
|
||||
generally be able to provide more help, and faster.
|
||||
|
||||
All contributors and rebar3 maintainers are generally unpaid developers
|
||||
working on the project in their own free time with limited resources. We
|
||||
ask for respect and understanding and will try to provide the same back.
|
||||
|
||||
## Requesting or implementing a feature
|
||||
|
||||
Before requesting or implementing a new feature, please do the following:
|
||||
|
||||
- Take a look at our [list of plugins](https://rebar3.org/docs/configuration/plugins#recommended-plugins)
|
||||
to know if the feature isn't already supported by the community.
|
||||
- Verify in existing [tickets](https://github.com/erlang/rebar3/issues) whether
|
||||
the feature might already is in the works, has been moved to a plugin, or
|
||||
has already been rejected.
|
||||
|
||||
If this is done, open up a ticket. Tell us what is the feature you want,
|
||||
why you need it, and why you think it should be in rebar3 itself.
|
||||
|
||||
We may discuss details with you regarding the implementation, its inclusion
|
||||
within the project or as a plugin. Depending on the feature, we may provide
|
||||
full support for it, or ask you to help implement and/or commit to maintaining
|
||||
it in the future. We're dedicated to providing a stable build tool, and may
|
||||
also ask features to exist as a plugin before being included in core rebar3 --
|
||||
the migration path from one to the other is fairly simple and little to no code
|
||||
needs rewriting.
|
||||
|
||||
## Project Structure
|
||||
|
||||
Rebar3 is an escript built around the concept of providers. Providers are the
|
||||
modules that do the work to fulfill a user's command. They are documented in
|
||||
[the official documentation website](http://www.rebar3.org/docs/plugins#section-provider-interface).
|
||||
|
||||
Example provider:
|
||||
|
||||
```erlang
|
||||
-module(rebar_prv_something).
|
||||
|
||||
-behaviour(rebar_provider).
|
||||
|
||||
-export([init/1,
|
||||
do/1,
|
||||
format_error/1]).
|
||||
|
||||
-define(PROVIDER, something).
|
||||
-define(DEPS, []).
|
||||
|
||||
%% ===================================================================
|
||||
%% Public API
|
||||
%% ===================================================================
|
||||
|
||||
-spec init(rebar_state:state()) -> {ok, rebar_state:state()}.
|
||||
init(State) ->
|
||||
State1 = rebar_state:add_provider(State, rebar_provider:create([
|
||||
{name, ?PROVIDER},
|
||||
{module, ?MODULE},
|
||||
{bare, true},
|
||||
{deps, ?DEPS},
|
||||
{example, "rebar dummy"},
|
||||
{short_desc, "dummy plugin."},
|
||||
{desc, ""},
|
||||
{opts, []}
|
||||
])),
|
||||
{ok, State1}.
|
||||
|
||||
-spec do(rebar_state:state()) -> {ok, rebar_state:state()}.
|
||||
do(State) ->
|
||||
%% Do something
|
||||
{ok, State}.
|
||||
|
||||
-spec format_error(any()) -> iolist().
|
||||
format_error(Reason) ->
|
||||
io_lib:format("~p", [Reason]).
|
||||
```
|
||||
|
||||
Providers are then listed in `rebar.app.src`, and can be called from
|
||||
the command line or as a programmatical API.
|
||||
|
||||
All commands are therefore implemented in standalone modules. If you call
|
||||
`rebar3 <task>`, the module in charge of it is likely located in
|
||||
`src/rebar_prv_<task>.erl`.
|
||||
|
||||
Templates are included in `priv/templates/`
|
||||
|
||||
The official test suite is Common Test, and tests are located in `test/`.
|
||||
|
||||
Useful modules include:
|
||||
- `rebar_api`, providing an interface for plugins to call into core rebar3
|
||||
functionality
|
||||
- `rebar_core`, for initial boot and setup of a project
|
||||
- `rebar_config`, handling the configuration of each project.
|
||||
- `rebar_app_info`, giving access to the metadata of a specific OTP application
|
||||
in a project.
|
||||
- `rebar_base_compiler`, giving a uniform interface to compile `.erl` files.
|
||||
- `rebar_dir` for directory handling and management
|
||||
- `rebar_file_util` for cross-platform file handling
|
||||
- `rebar_state`, the glue holding together a specific build or task run;
|
||||
includes canonical versions of the configuration, profiles, applications,
|
||||
dependencies, and so on.
|
||||
- `rebar_utils` for generic tasks and functionality required across
|
||||
multiple providers or modules.
|
||||
|
||||
## Tests
|
||||
|
||||
Rebar3 tries to have as many of its features tested as possible. Everything
|
||||
that a user can do and should be repeatable in any way should be tested.
|
||||
|
||||
Tests are written using the Common Test framework. Tests for rebar3 can be run
|
||||
by calling:
|
||||
|
||||
```bash
|
||||
$ rebar3 escriptize # or bootstrap
|
||||
$ ./rebar3 ct
|
||||
```
|
||||
|
||||
Most tests are named according to their module name followed by the `_SUITE`
|
||||
suffix. Providers are made shorter, such that `rebar_prv_new` is tested in
|
||||
`rebar_new_SUITE`.
|
||||
|
||||
Most tests in the test suite will rely on calling Rebar3 in its API form,
|
||||
then investigating the build output. Because most tests have similar
|
||||
requirements, the `test/rebar_test_utils` file contains common code
|
||||
to set up test projects, run tasks, and verify artifacts at once.
|
||||
|
||||
A basic example can look like:
|
||||
|
||||
```erlang
|
||||
-module(rebar_some_SUITE).
|
||||
-compile(export_all).
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
all() -> [checks_success, checks_failure].
|
||||
|
||||
init_per_testcase(Case, Config0) ->
|
||||
%% Create a project directory in the test run's priv_dir
|
||||
Config = rebar_test_utils:init_rebar_state(Config0),
|
||||
%% Create toy applications
|
||||
AppDir = ?config(apps, Config),
|
||||
Name = rebar_test_utils:create_random_name("app1_"++atom_to_list(Case)),
|
||||
Vsn = rebar_test_utils:create_random_vsn(),
|
||||
rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
|
||||
%% Add the data to the test config
|
||||
[{name, Name} | Config].
|
||||
|
||||
end_per_testcase(_, Config) ->
|
||||
Config.
|
||||
|
||||
checks_success(Config) ->
|
||||
%% Validates that the application in `name' is successfully compiled
|
||||
Name = ?config(name, Config),
|
||||
rebar_test_utils:run_and_check(Config, [],
|
||||
["compile"],
|
||||
{ok, [{app, Name}]}).
|
||||
|
||||
checks_failure(Config) ->
|
||||
%% Checks that a result fails
|
||||
Command = ["fakecommand", "fake-arg"],
|
||||
rebar_test_utils:run_and_check(
|
||||
Config, [], Command,
|
||||
{error, io_lib:format("Command ~p not found", [fakecommand])}
|
||||
).
|
||||
```
|
||||
|
||||
The general interface to `rebar_test_utils:run_and_check` is
|
||||
`run_and_check(CTConfig, RebarConfig, Command, Expect)` where `Expect` can
|
||||
be any of:
|
||||
|
||||
```erlang
|
||||
{ok, OKRes}
|
||||
{ok, OKRes, ProfilesUsed}
|
||||
{error, Reason}
|
||||
|
||||
% where:
|
||||
ProfilesUsed :: string() % matching the profiles to validate (defaults to "*")
|
||||
OKRes :: {app, Name} % name of an app that is in the build directory
|
||||
| {app, Name, valid} % name of an app that is in the build directory and compiled properly
|
||||
| {app, Name, invalid} % name of an app that didn't compile properly
|
||||
| {dep, Name} % name of a dependency in the build directory
|
||||
| {dep, Name, Vsn} % name of a dependency in the build directory with a specific version
|
||||
| {dep_not_exist, Name} % name of a dependency missing from the build directory
|
||||
| {checkout, Name} % name of an app that is a checkout dependency
|
||||
| {plugin, Name} % name of a plugin in the build directory
|
||||
| {plugin, Name, Vsn} % name of a plugin in the build directory with a specific version
|
||||
| {global_plugin, Name} % name of a global plugin in the build directory
|
||||
| {global_plugin, Name, Vsn} % name of a global plugin in the build directory with a specific version
|
||||
| {lock, Name} % name of a locked dependency
|
||||
| {lock, Name, Vsn} % name of a locked dependency of a specific version
|
||||
| {lock, pkg, Name, Vsn}% name of a locked package of a specific version
|
||||
| {lock, src, Name, Vsn}% name of a locked source dependency of a specific version
|
||||
| {release, Name, Vsn, ExpectedDevMode} % validates a release
|
||||
| {tar, Name, Vsn} % validates a tarball's existence
|
||||
| {file, Filename} % validates the presence of a given file
|
||||
| {dir, Dirname} % validates the presence of a given directory
|
||||
Reason :: term() % the exception thrown by rebar3
|
||||
```
|
||||
|
||||
This generally lets most features be tested fine. Ask for help if you cannot
|
||||
figure out how to write tests for your feature or patch.
|
||||
|
||||
## Submitting your changes
|
||||
|
||||
While we're not too formal when it comes to pull requests to the project,
|
||||
we do appreciate users taking the time to conform to the guidelines that
|
||||
follow.
|
||||
|
||||
We do expect all pull requests submitted to come with [tests](#tests) before
|
||||
they are merged. If you cannot figure out how to write your tests properly, ask
|
||||
in the pull request for guidance.
|
||||
|
||||
### Code Style
|
||||
|
||||
* Do not introduce trailing whitespace
|
||||
* Indentation is 4 spaces wide, no tabs.
|
||||
* Try not to introduce lines longer than 80 characters
|
||||
* Write small functions whenever possible, and use descriptive names for
|
||||
functions and variables.
|
||||
* Avoid having too many clauses containing clauses containing clauses.
|
||||
Basically, avoid deeply nested `case ... of` or `try ... catch` expressions.
|
||||
Break them out into functions if possible.
|
||||
* Comment tricky or non-obvious decisions made to explain their rationale.
|
||||
|
||||
### Committing your changes
|
||||
|
||||
It helps if your commits are structured as follows:
|
||||
|
||||
- Fixing a bug is one commit.
|
||||
- Adding a feature is one commit.
|
||||
- Adding two features is two commits.
|
||||
- Two unrelated changes is two commits (and likely two Pull requests)
|
||||
|
||||
If you fix a (buggy) commit, squash (`git rebase -i`) the changes as a fixup
|
||||
commit into the original commit, unless the patch was following a
|
||||
maintainer's code review. In such cases, it helps to have separate commits.
|
||||
|
||||
The reviewer may ask you to later squash the commits together to provide
|
||||
a clean commit history before merging in the feature.
|
||||
|
||||
It's important to write a proper commit title and description. The commit title
|
||||
should be no more than 50 characters; it is the first line of the commit text. The
|
||||
second line of the commit text must be left blank. The third line and beyond is
|
||||
the commit message. You should write a commit message. If you do, wrap all
|
||||
lines at 72 characters. You should explain what the commit does, what
|
||||
references you used, and any other information that helps understanding your
|
||||
changes.
|
||||
|
||||
### Pull Requests and Branching
|
||||
|
||||
All fixes to rebar end up requiring a +1 from one or more of the project's
|
||||
maintainers. When opening a pull request, explain what the patch is doing
|
||||
and if it makes sense, why the proposed implementation was chosen.
|
||||
|
||||
Try to use well-defined commits (one feature per commit) so that reading
|
||||
them and testing them is easier for reviewers and while bisecting the code
|
||||
base for issues.
|
||||
|
||||
During the review process, you may be asked to correct or edit a few things
|
||||
before a final rebase to merge things. Do send edits as individual commits
|
||||
to allow for gradual and partial reviews to be done by reviewers. Once the +1s
|
||||
are given, rebasing is appreciated but not mandatory.
|
||||
|
||||
Please work in feature branches, and do not commit to `master` in your fork.
|
||||
|
||||
Provide a clean branch without merge commits.
|
||||
|
||||
If you can, pick a descriptive title for your pull request. When we generate
|
||||
changelogs before cutting a release, a script uses the pull request names
|
||||
to populate the entries.
|
||||
|
||||
|
||||
### Credit
|
||||
|
||||
To give everyone proper credit in addition to the git history, please feel free to append
|
||||
your name to `THANKS` in your first contribution.
|
|
@ -0,0 +1,43 @@
|
|||
# https://docs.docker.com/engine/reference/builder/#from
|
||||
# "The FROM instruction initializes a new build stage and sets the
|
||||
# Base Image for subsequent instructions."
|
||||
FROM erlang:20.3.8.1-alpine as builder
|
||||
# https://docs.docker.com/engine/reference/builder/#label
|
||||
# "The LABEL instruction adds metadata to an image."
|
||||
LABEL stage=builder
|
||||
|
||||
# Install git for fetching non-hex depenencies. Also allows rebar3
|
||||
# to find it's own git version.
|
||||
# Add any other Alpine libraries needed to compile the project here.
|
||||
# See https://wiki.alpinelinux.org/wiki/Local_APK_cache for details
|
||||
# on the local cache and need for the symlink
|
||||
RUN ln -s /var/cache/apk /etc/apk/cache && \
|
||||
apk update && \
|
||||
apk add --update openssh-client git
|
||||
|
||||
# WORKDIR is located in the image
|
||||
# https://docs.docker.com/engine/reference/builder/#workdir
|
||||
WORKDIR /root/rebar3
|
||||
|
||||
# copy the entire src over and build
|
||||
COPY . .
|
||||
RUN ./bootstrap
|
||||
|
||||
# this is the final runner layer, notice how it diverges from the original erlang
|
||||
# alpine layer, this means this layer won't have any of the other stuff that was
|
||||
# generated previously (deps, build, etc)
|
||||
FROM erlang:20.3.8.1-alpine as runner
|
||||
|
||||
# copy the generated `rebar3` binary over here
|
||||
COPY --from=builder /root/rebar3/_build/prod/bin/rebar3 .
|
||||
|
||||
# and install it
|
||||
RUN HOME=/opt ./rebar3 local install \
|
||||
&& rm -f /usr/local/bin/rebar3 \
|
||||
&& ln /opt/.cache/rebar3/bin/rebar3 /usr/local/bin/rebar3 \
|
||||
&& rm -rf rebar3
|
||||
|
||||
# simply print out the version for visibility
|
||||
ENTRYPOINT ["/usr/local/bin/rebar3"]
|
||||
CMD ["--version"]
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) 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
|
||||
|
||||
(d) 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
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
# Rebar3
|
||||
|
||||
[](https://github.com/erlang/rebar3/actions?query=branch%3Amaster+workflow%3A"Common+Test") [](http://www.erlang.org)
|
||||
|
||||
1. [What is Rebar3?](#what-is-rebar3)
|
||||
2. [Why Rebar3?](#why-rebar3)
|
||||
3. [Should I Use Rebar3?](#should-i-use-rebar3)
|
||||
4. [Getting Started](#getting-started)
|
||||
5. [Documentation](#documentation)
|
||||
6. [Features](#features)
|
||||
7. [Migrating from rebar2](#migrating-from-rebar2)
|
||||
8. [Additional Resources](#additional-resources)
|
||||
|
||||
## What is Rebar3
|
||||
|
||||
Rebar3 is an Erlang tool that makes it easy to create, develop, and
|
||||
release Erlang libraries, applications, and systems in a repeatable manner.
|
||||
|
||||
Rebar3 will:
|
||||
- respect and enforce standard Erlang/OTP conventions for project
|
||||
structure so they are easily reusable by the community;
|
||||
- manage source dependencies and Erlang [packages](https://hex.pm)
|
||||
while ensuring repeatable builds;
|
||||
- handle build artifacts, paths, and libraries such that standard
|
||||
development tools can be used without a headache;
|
||||
- adapt to projects of all sizes on almost any platform;
|
||||
- treat [documentation](https://rebar3.org/docs/) as a feature,
|
||||
and errors or lack of documentation as a bug.
|
||||
|
||||
Rebar3 is also a self-contained Erlang script. It is easy to distribute or
|
||||
embed directly in a project. Tasks or behaviours can be modified or expanded
|
||||
with a [plugin system](https://rebar3.org/docs/configuration/plugins)
|
||||
[flexible enough](https://github.com/lfe-rebar3/rebar3_lfe) that even other languages
|
||||
on the Erlang VM will use it as a build tool.
|
||||
|
||||
## Why Rebar3
|
||||
|
||||
Rebar3 is the spiritual successor to [rebar
|
||||
2.x](https://github.com/rebar/rebar), which was the first usable build tool
|
||||
for Erlang that ended up seeing widespread community adoption. It however
|
||||
had several shortcomings that made it difficult to use with larger projects
|
||||
or with teams with users new to Erlang.
|
||||
|
||||
Rebar3 was our attempt at improving over the legacy of Rebar 2.x, providing the
|
||||
features we felt it was missing, and to provide a better environment in which
|
||||
newcomers joining our teams could develop.
|
||||
|
||||
## Should I use Rebar3?
|
||||
|
||||
If your main language for your system is Erlang, that you value repeatable builds
|
||||
and want your various tools to integrate together, we do believe Rebar3 is the
|
||||
best experience you can get.
|
||||
|
||||
## Getting Started
|
||||
|
||||
A [getting started guide is maintained on the official documentation website](https://rebar3.org/docs/getting-started),
|
||||
but installing rebar3 can be done by any of the ways described below
|
||||
|
||||
Latest stable compiled version:
|
||||
```bash
|
||||
$ wget https://s3.amazonaws.com/rebar3/rebar3 && chmod +x rebar3
|
||||
```
|
||||
|
||||
From Source (assuming you have a full Erlang install):
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/erlang/rebar3.git
|
||||
$ cd rebar3
|
||||
$ ./bootstrap
|
||||
```
|
||||
|
||||
Stable versions can also be obtained from the [releases page](https://github.com/erlang/rebar3/releases).
|
||||
|
||||
The rebar3 escript can also extract itself with a run script under the user's home directory:
|
||||
|
||||
```bash
|
||||
$ ./rebar3 local install
|
||||
===> Extracting rebar3 libs to ~/.cache/rebar3/lib...
|
||||
===> Writing rebar3 run script ~/.cache/rebar3/bin/rebar3...
|
||||
===> Add to $PATH for use: export PATH=~/.cache/rebar3/bin:$PATH
|
||||
```
|
||||
|
||||
To keep it up to date after you've installed rebar3 this way you can use `rebar3 local upgrade` which
|
||||
fetches the latest stable release and extracts to the same place as above. A [nightly version can
|
||||
also be obtained](https://s3.amazonaws.com/rebar3-nightly/rebar3) if desired.
|
||||
|
||||
Rebar3 may also be available on various OS-specific package managers such as
|
||||
FreeBSD Ports. Those are maintained by the community and Rebar3 maintainers
|
||||
themselves are generally not involved in that process.
|
||||
|
||||
If you do not have a full Erlang install, we recommend using [erln8](https://erln8.github.io/erln8/)
|
||||
or [kerl](https://github.com/yrashk/kerl). For binary packages, use those provided
|
||||
by [Erlang Solutions](https://www.erlang-solutions.com/resources/download.html),
|
||||
but be sure to choose the "Standard" download option or you'll have issues building
|
||||
projects.
|
||||
|
||||
Do note that if you are planning to work with multiple Erlang versions on the same machine, you will want to build Rebar3 with the oldest one of them. The 3 newest major Erlang releases are supported at any given time: if the newest version is OTP-24, building with versions as old as OTP-22 will be supported, and produce an executable that will work with those that follow.
|
||||
|
||||
## Documentation
|
||||
|
||||
Rebar3 documentation is maintained on [https://rebar3.org/docs](https://rebar3.org/docs)
|
||||
|
||||
## Features
|
||||
|
||||
Rebar3 supports the following features or tools by default, and may provide many
|
||||
others via the plugin ecosystem:
|
||||
|
||||
| features | Description |
|
||||
|--------------------- |------------ |
|
||||
| Command composition | Rebar3 allows multiple commands to be run in sequence by calling `rebar3 do <task1>,<task2>,...,<taskN>`. |
|
||||
| Command dependencies | Rebar3 commands know their own dependencies. If a test run needs to fetch dependencies and build them, it will do so. |
|
||||
| Command namespaces | Allows multiple tools or commands to share the same name. |
|
||||
| Compiling | Build the project, including fetching all of its dependencies by calling `rebar3 compile` |
|
||||
| Clean up artifacts | Remove the compiled beam files from a project with `rebar3 clean` or just remove the `_build` directory to remove *all* compilation artifacts |
|
||||
| Code Coverage | Various commands can be instrumented to accumulate code coverage data (such as `eunit` or `ct`). Reports can be generated with `rebar3 cover` |
|
||||
| Common Test | The test framework can be run by calling `rebar3 ct` |
|
||||
| Dependencies | Rebar3 maintains local copies of dependencies on a per-project basis. They are fetched deterministically, can be locked, upgraded, fetched from source, packages, or from local directories. See [Dependencies on the documentation website](https://rebar3.org/docs/configuration/dependencies/). Call `rebar3 tree` to show the whole dependency tree. |
|
||||
| Documentation | Print help for rebar3 itself (`rebar3 help`) or for a specific task (`rebar3 help <task>`). Full reference at [rebar3.org](https://rebar3.org/docs). |
|
||||
| Dialyzer | Run the Dialyzer analyzer on the project with `rebar3 dialyzer`. Base PLTs for each version of the language will be cached and reused for faster analysis |
|
||||
| Edoc | Generate documentation using edoc with `rebar3 edoc` |
|
||||
| Escript generation | Rebar3 can be used to generate [escripts](http://www.erlang.org/doc/man/escript.html) providing an easy way to run all your applications on a system where Erlang is installed |
|
||||
| Eunit | The test framework can be run by calling `rebar3 eunit` |
|
||||
| Locked dependencies | Dependencies are going to be automatically locked to ensure repeatable builds. Versions can be changed with `rebar3 upgrade` or `rebar3 upgrade <app>`, or locks can be released altogether with `rebar3 unlock`. |
|
||||
| Packages | A given [Hex package](https://hex.pm) can be inspected `rebar3 pkgs <name>`. This will output its description and available versions |
|
||||
| Path | While paths are managed automatically, you can print paths to the current build directories with `rebar3 path`. |
|
||||
| Plugins | Rebar3 can be fully extended with [plugins](https://rebar3.org/docs/configuration/plugins/). List or upgrade plugins by using the plugin namespace (`rebar3 plugins`). |
|
||||
| Profiles | Rebar3 can have subconfiguration options for different profiles, such as `test` or `prod`. These allow specific dependencies or compile options to be used in specific contexts. See [Profiles](https://rebar3.org/docs/configuration/profiles) in the docs. |
|
||||
| Releases | Rebar3 supports [building releases](https://rebar3.org/docs/deployment/releases) with the `relx` tool, providing a way to ship fully self-contained Erlang systems. Release update scripts for live code updates can also be generated. |
|
||||
| Shell | A full shell with your applications available can be started with `rebar3 shell`. From there, call tasks as `r3:do(compile)` to automatically recompile and reload the code without interruption |
|
||||
| Tarballs | Releases can be packaged into tarballs ready to be deployed. |
|
||||
| Templates | Configurable templates ship out of the box (try `rebar3 new` for a list or `rebar3 new help <template>` for a specific one). [Custom templates](https://rebar3.org/docs/tutorials/templates) are also supported, and plugins can also add their own. |
|
||||
| Xref | Run cross-reference analysis on the project with [xref](http://www.erlang.org/doc/apps/tools/xref_chapter.html) by calling `rebar3 xref`. |
|
||||
|
||||
## Migrating From rebar2
|
||||
|
||||
The grievances we had with Rebar 2.x were not fixable without breaking
|
||||
compatibility in some very important ways.
|
||||
|
||||
A full guide titled [From Rebar 2.x to Rebar3](https://rebar3.org/docs/tutorials/from_rebar2_to_rebar3/)
|
||||
is provided on the documentation website.
|
||||
|
||||
Notable modifications include mandating a more standard set of directory
|
||||
structures, changing the handling of dependencies, moving some compilers (such
|
||||
as C, Diameter, ErlyDTL, or ProtoBuffs) to
|
||||
[plugins](https://rebar3.org/docs/configuration/plugins) rather than
|
||||
maintaining them in core rebar, and moving release builds from reltool to
|
||||
relx.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
In the case of problems that cannot be solved through documentation or examples, you
|
||||
may want to try to contact members of the community for help. The community is
|
||||
also where you want to go for questions about how to extend rebar, fill in bug
|
||||
reports, and so on.
|
||||
|
||||
If you need
|
||||
quick feedback, you can try the #rebar channel on
|
||||
[irc.freenode.net](https://freenode.net) or the #rebar3 channel on
|
||||
[erlanger.slack.com](https://erlanger.slack.com/). Be sure to check the
|
||||
[documentation](https://rebar3.org/docs) first, just to be sure you're not
|
||||
asking about things with well-known answers.
|
||||
|
||||
For bug reports, roadmaps, and issues, visit the [github issues
|
||||
page](https://github.com/erlang/rebar3/issues).
|
||||
|
||||
General rebar community resources and links can be found at
|
||||
[rebar3.org/docs/about/about-us/#community](https://rebar3.org/docs/about/about-us/#community)
|
||||
|
||||
To contribute to rebar3, please refer to [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
The following people have contributed to rebar:
|
||||
|
||||
Dave Smith
|
||||
Jon Meredith
|
||||
Tim Dysinger
|
||||
Bryan Fink
|
||||
Tuncer Ayaz
|
||||
Ian Wilkinson
|
||||
Juan Jose Comellas
|
||||
Tom Preston-Werner
|
||||
OJ Reeves
|
||||
Ruslan Babayev
|
||||
Ryan Tilder
|
||||
Kevin Smith
|
||||
David Reid
|
||||
Cliff Moon
|
||||
Chris Bernard
|
||||
Jeremy Raymond
|
||||
Bob Ippolito
|
||||
Alex Songe
|
||||
Andrew Thompson
|
||||
Russell Brown
|
||||
Chris Chew
|
||||
Klas Johansson
|
||||
Geoff Cant
|
||||
Kostis Sagonas
|
||||
Essien Ita Essien
|
||||
Manuel Duran Aguete
|
||||
Daniel Neri
|
||||
Misha Gorodnitzky
|
||||
Adam Kocoloski
|
||||
Joseph Wayne Norton
|
||||
Mihai Balea
|
||||
Matthew Batema
|
||||
Alexey Romanov
|
||||
Benjamin Nortier
|
||||
Magnus Klaar
|
||||
Anthony Ramine
|
||||
Charles McKnight
|
||||
Andrew Tunnell-Jones
|
||||
Joe Williams
|
||||
Daniel Reverri
|
||||
Jesper Louis Andersen
|
||||
Richard Jones
|
||||
Tim Watson
|
||||
Anders 'andekar'
|
||||
Christopher Brown
|
||||
Jordi Chacon
|
||||
Shunichi Shinohara
|
||||
Mickael Remond
|
||||
Evax Software
|
||||
Piotr Usewicz
|
||||
Anthony Molinaro
|
||||
Andrew Gopienko
|
||||
Steve Vinoski
|
||||
Evan Miller
|
||||
Jared Morrow
|
||||
Jan Kloetzke
|
||||
Mathias Meyer
|
||||
Steven Gravell
|
||||
Alexis Sellier
|
||||
Mattias Holmlund
|
||||
Tino Breddin
|
||||
David Nonnenmacher
|
||||
Anders Nygren
|
||||
Scott Lystig Fritchie
|
||||
Uwe Dauernheim
|
||||
Yurii Rashkovskii
|
||||
Alfonso De Gregorio
|
||||
Matt Campbell
|
||||
Benjamin Plee
|
||||
Ben Ellis
|
||||
Ignas Vysniauskas
|
||||
Anton Lavrik
|
||||
Jan Vincent Liwanag
|
||||
Przemyslaw Dabek
|
||||
Fabian Linzberger
|
||||
Smith Winston
|
||||
Jesse Gumm
|
||||
Torbjorn Tornkvist
|
||||
Ali Sabil
|
||||
Tomas Abrahamsson
|
||||
Francis Joanis
|
||||
fisher@yun.io
|
||||
Slava Yurin
|
||||
Phillip Toland
|
||||
Mike Lazar
|
||||
Loic Hoguin
|
||||
Ali Yakout
|
||||
Adam Schepis
|
||||
Amit Kapoor
|
||||
Ulf Wiger
|
||||
Nick Vatamaniuc
|
||||
Daniel Luna
|
||||
Motiejus Jakstys
|
||||
Eric B Merritt
|
||||
Fred Hebert
|
||||
Kresten Krab Thorup
|
||||
David Aaberg
|
||||
Pedram Nimreezi
|
||||
Edwin Fine
|
||||
Lev Walkin
|
||||
Roberto Ostinelli
|
||||
Joe DeVivo
|
||||
Markus Nasman
|
||||
Dmitriy Kargapolov
|
||||
Ryan Zezeski
|
||||
Daniel White
|
||||
Martin Schut
|
||||
Serge Aleynikov
|
||||
Magnus Henoch
|
||||
Artem Teslenko
|
||||
Jeremie Lasalle Ratelle
|
||||
Jose Valim
|
||||
Krzysztof Rutka
|
||||
Mats Cronqvist
|
||||
Matthew Conway
|
||||
Giacomo Olgeni
|
||||
Pedram Nimreezi
|
||||
Sylvain Benner
|
||||
Oliver Ferrigni
|
||||
Dave Thomas
|
||||
Evgeniy Khramtsov
|
||||
YeJun Su
|
||||
Yuki Ito
|
||||
alisdair sullivan
|
||||
Alexander Verbitsky
|
||||
Andras Horvath
|
||||
Drew Varner
|
||||
Omar Yasin
|
||||
Tristan Sloughter
|
||||
Kelly McLaughlin
|
||||
Martin Karlsson
|
||||
Pierre Fenoll
|
||||
David Kubecka
|
||||
Stefan Grundmann
|
||||
Carlos Eduardo de Paula
|
||||
Derek Brown
|
||||
Heinz N. Gies
|
||||
Roberto Aloi
|
||||
Andrew McRobb
|
||||
Drew Varner
|
||||
Niklas Johansson
|
||||
Bryan Paxton
|
||||
Justin Wood
|
||||
Guilherme Andrade
|
||||
Manas Chaudhari
|
||||
Luís Rascão
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Hinagiku Soranoba
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
bbmustache
|
||||
===========
|
||||
[](https://travis-ci.org/soranoba/bbmustache)
|
||||
[](https://hex.pm/packages/bbmustache)
|
||||
|
||||
Binary pattern match Based Mustache template engine for Erlang/OTP.
|
||||
|
||||
## Overview
|
||||
- Binary pattern match based mustache template engine for Erlang/OTP.
|
||||
- It means do not use regular expressions.
|
||||
- Support maps and associative arrays.
|
||||
- Officially support is OTP17 or later.
|
||||
|
||||
### What is Mustache ?
|
||||
A logic-less templates.
|
||||
- [{{mustache}}](http://mustache.github.io/)
|
||||
|
||||
## Usage
|
||||
### Quick start
|
||||
|
||||
```bash
|
||||
$ git clone git://github.com/soranoba/bbmustache.git
|
||||
$ cd bbmustache
|
||||
$ make start
|
||||
Erlang/OTP 17 [erts-6.3] [source-f9282c6] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:true]
|
||||
|
||||
Eshell V6.3 (abort with ^G)
|
||||
1> bbmustache:render(<<"{{name}}">>, #{"name" => "hoge"}).
|
||||
<<"hoge">>
|
||||
2> bbmustache:render(<<"{{name}}">>, [{"name", "hoge"}]).
|
||||
<<"hoge">>
|
||||
```
|
||||
|
||||
### Use as a library
|
||||
Add the following settings.
|
||||
|
||||
```erlang
|
||||
%% rebar (rebar.config)
|
||||
|
||||
{deps,
|
||||
[
|
||||
{bbmustache, ".*", {git, "git://github.com/soranoba/bbmustache.git", {branch, "master"}}}
|
||||
]}.
|
||||
|
||||
%% rebar3 (rebar.config)
|
||||
|
||||
{deps, [bbmustache]}.
|
||||
```
|
||||
|
||||
### How to use simple Mustache
|
||||
|
||||
Map
|
||||
```erlang
|
||||
1> bbmustache:render(<<"{{name}}">>, #{"name" => "hoge"}).
|
||||
<<"hoge">>
|
||||
|
||||
2> Template1 = bbmustache:parse_binary(<<"{{name}}">>).
|
||||
...
|
||||
3> bbmustache:compile(Template1, #{"name" => "hoge"}).
|
||||
<<"hoge">>
|
||||
|
||||
4> Template2 = bbmustache:parse_file(<<"./hoge.mustache">>).
|
||||
...
|
||||
5> bbmustache:compile(Template2, #{"name" => "hoge"}).
|
||||
<<"hoge">>
|
||||
```
|
||||
|
||||
Associative array
|
||||
```erlang
|
||||
1> bbmustache:render(<<"{{name}}">>, [{"name", "hoge"}]).
|
||||
<<"hoge">>
|
||||
|
||||
2> Template1 = bbmustache:parse_binary(<<"{{name}}">>).
|
||||
...
|
||||
3> bbmustache:compile(Template1, [{"name", "hoge"}]).
|
||||
<<"hoge">>
|
||||
|
||||
4> Template2 = bbmustache:parse_file(<<"./hoge.mustache">>).
|
||||
...
|
||||
5> bbmustache:compile(Template2, [{"name", "hoge"}]).
|
||||
<<"hoge">>
|
||||
```
|
||||
|
||||
### Use as a command-line tool
|
||||
|
||||
```bash
|
||||
make escriptize
|
||||
echo '{"name", "hoge"}.' > vars.config
|
||||
echo '{{name}}' > template.mustache
|
||||
./bbmustache -d vars.config template.mustache
|
||||
hoge
|
||||
```
|
||||
|
||||
Data files (-d) support a single assoc list, a single map, and [consult](https://erlang.org/doc/man/file.html#consult-1) format.<br>
|
||||
Note: the behind term has a high priority in all cases. it is a result of supporting to allow for embedding relative file paths as in [config](http://erlang.org/doc/man/config.html).
|
||||
|
||||
### More information
|
||||
- For the alias of mustache, Please refer to [ManPage](http://mustache.github.io/mustache.5.html) and [Specification](https://github.com/mustache/spec)
|
||||
- For the options of this library, please see [doc](doc)
|
||||
- For the functions supported by this library, please see [here](benchmarks/README.md)
|
||||
|
||||
## FAQ
|
||||
|
||||
### Avoid http escaping
|
||||
|
||||
```erlang
|
||||
%% Please use `{{{tag}}}`
|
||||
1> bbmustache:render(<<"<h1>{{{title}}}</h1>">>, #{"title" => "I like Erlang & mustache"}).
|
||||
<<"<h1>I like Erlang & mustache</h1>">>
|
||||
|
||||
%% If you should not want to use `{{{tag}}}`, escape_fun can be use.
|
||||
1> bbmustache:render(<<"<h1>{{title}}</h1>">>, #{"title" => "I like Erlang & mustache"}, [{escape_fun, fun(X) -> X end}]).
|
||||
<<"<h1>I like Erlang & mustache</h1>">>
|
||||
```
|
||||
|
||||
### Already used `{` and `}` for other uses (like escript)
|
||||
|
||||
```erlang
|
||||
1> io:format(bbmustache:render(<<"
|
||||
1> {{=<< >>=}}
|
||||
1> {deps, [
|
||||
1> <<#deps>>
|
||||
1> {<<name>>, \"<<version>>\"}<<^last?>>,<</last?>>
|
||||
1> <</deps>>
|
||||
1> ]}.
|
||||
1> ">>, #{"deps" => [
|
||||
1> #{"name" => "bbmustache", "version" => "1.6.0"},
|
||||
1> #{"name" => "jsone", "version" => "1.4.6", "last?" => true}
|
||||
1> ]})).
|
||||
|
||||
{deps, [
|
||||
{bbmustache, "1.6.0"},
|
||||
{jsone, "1.4.6"}
|
||||
]}.
|
||||
ok
|
||||
```
|
||||
|
||||
### Want to use something other than string for key
|
||||
|
||||
```erlang
|
||||
1> bbmustache:render(<<"<h1>{{{title}}}</h1>">>, #{title => "I like Erlang & mustache"}, [{key_type, atom}]).
|
||||
<<"<h1>I like Erlang & mustache</h1>">>
|
||||
|
||||
2> bbmustache:render(<<"<h1>{{{title}}}</h1>">>, #{<<"title">> => "I like Erlang & mustache"}, [{key_type, binary}]).
|
||||
<<"<h1>I like Erlang & mustache</h1>">>
|
||||
```
|
||||
|
||||
### Want to provide a custom serializer for Erlang Terms
|
||||
|
||||
```erlang
|
||||
1> bbmustache:render(<<"<h1>{{title}}</h1>">>, #{title => "I like Erlang & mustache"}, [{key_type, atom}, {value_serializer, fun(X) -> X end}]).
|
||||
<<"<h1>I like Erlang & mustache</h1>">>
|
||||
|
||||
2> bbmustache:render(<<"<h1>{{{title}}}</h1>">>, #{<<"title">> => "I like Erlang & mustache"}, [{key_type, binary}, {value_serializer, fun(X) -> <<"replaced">> end}]).
|
||||
<<"<h1>replaced</h1>">>
|
||||
|
||||
3> bbmustache:render(<<"<h1>{{{title}}}</h1>">>, #{<<"title">> => #{<<"nested">> => <<"value">>}}, [{key_type, binary}, {value_serializer, fun(X) -> jsone:encode(X) end}]).
|
||||
<<"<h1>{\"nested\": \"value\"}</h1>">>
|
||||
|
||||
4> bbmustache:render(<<"<h1>{{title}}</h1>">>, #{<<"title">> => #{<<"nested">> => <<"value">>}}, [{key_type, binary}, {value_serializer, fun(X) -> jsone:encode(X) end}]).
|
||||
<<"<h1>{"nested":"value"}</h1>">>
|
||||
```
|
||||
|
||||
## Attention
|
||||
- Lambda expression is included wasted processing.
|
||||
- Because it is optimized to `parse_binary/1` + `compile/2`.
|
||||
|
||||
## Comparison with other libraries
|
||||
[Benchmarks and check the reference implementation](benchmarks/README.md)
|
||||
|
||||
## Contribute
|
||||
Pull request is welcome =D
|
||||
|
||||
## License
|
||||
[MIT License](LICENSE)
|
|
@ -0,0 +1,63 @@
|
|||
%% vim: set filetype=erlang : -*- erlang -*-
|
||||
|
||||
{erl_opts, [
|
||||
{platform_define, "^[0-9]+", namespaced_types},
|
||||
warnings_as_errors,
|
||||
warn_export_all,
|
||||
warn_untyped_record
|
||||
]}.
|
||||
|
||||
{xref_checks, [
|
||||
fail_on_warning,
|
||||
undefined_function_calls
|
||||
]}.
|
||||
|
||||
{cover_enabled, true}.
|
||||
|
||||
{edoc_opts, [
|
||||
{doclet, edown_doclet},
|
||||
{dialyzer_specs, all},
|
||||
{report_missing_type, true},
|
||||
{report_type_mismatch, true},
|
||||
{pretty_print, erl_pp},
|
||||
{preprocess, true}
|
||||
]}.
|
||||
{validate_app_modules, true}.
|
||||
|
||||
{ct_opts, [{dir, "ct"}]}.
|
||||
|
||||
{git_vsn, [{env_key, git_vsn},
|
||||
{describe_opt, "--tags --abbrev=10"},
|
||||
{separate, true}]}.
|
||||
|
||||
{escript_name, bbmustache}.
|
||||
{escript_incl_apps, [getopt]}.
|
||||
{escript_comment, "%% https://github.com/soranoba/bbmustache \n"}.
|
||||
|
||||
{profiles, [{test, [{erl_opts, [export_all]},
|
||||
{deps,
|
||||
[
|
||||
{jsone, "1.4.6"},
|
||||
{mustache_spec, ".*", {git, "git://github.com/soranoba/spec.git", {tag, "v1.1.3-erl"}}}
|
||||
]},
|
||||
{plugins, [rebar3_raw_deps]}
|
||||
]},
|
||||
{dev, [{erl_opts, [{d, bbmustache_escriptize}]},
|
||||
{deps,
|
||||
[
|
||||
{getopt, "1.0.1"}
|
||||
]},
|
||||
{plugins, [rebar3_git_vsn]},
|
||||
{provider_hooks, [{post, [{compile, git_vsn}]}]}
|
||||
]},
|
||||
{doc, [{deps,
|
||||
[
|
||||
{edown, ".*", {git, "git://github.com/uwiger/edown.git", {branch, "master"}}}
|
||||
]}
|
||||
]},
|
||||
{bench, [{deps,
|
||||
[
|
||||
{mustache, ".*", {git, "git://github.com/mojombo/mustache.erl", {tag, "v0.1.1"}}}
|
||||
]}
|
||||
]}
|
||||
]}.
|
|
@ -0,0 +1 @@
|
|||
[].
|
|
@ -0,0 +1,9 @@
|
|||
{application,bbmustache,
|
||||
[{description,"Binary pattern match Based Mustache template engine for Erlang/OTP"},
|
||||
{vsn,"1.10.0"},
|
||||
{registered,[]},
|
||||
{applications,[kernel,stdlib]},
|
||||
{maintainers,["Hinagiku Soranoba"]},
|
||||
{licenses,["MIT"]},
|
||||
{links,[{"GitHub","https://github.com/soranoba/bbmustache"}]},
|
||||
{env,[]}]}.
|
|
@ -0,0 +1,799 @@
|
|||
%% @copyright 2015 Hinagiku Soranoba All Rights Reserved.
|
||||
%%
|
||||
%% @doc Binary pattern match Based Mustach template engine for Erlang/OTP.
|
||||
%%
|
||||
%% Please refer to [the man page](http://mustache.github.io/mustache.5.html) and [the spec](https://github.com/mustache/spec) of mustache as the need arises.<br />
|
||||
%%
|
||||
%% Please see [this](../benchmarks/README.md) for a list of features that bbmustache supports.
|
||||
%%
|
||||
|
||||
-module(bbmustache).
|
||||
|
||||
%%----------------------------------------------------------------------------------------------------------------------
|
||||
%% Exported API
|
||||
%%----------------------------------------------------------------------------------------------------------------------
|
||||
-export([
|
||||
render/2,
|
||||
render/3,
|
||||
parse_binary/1,
|
||||
parse_binary/2,
|
||||
parse_file/1,
|
||||
parse_file/2,
|
||||
compile/2,
|
||||
compile/3,
|
||||
default_value_serializer/1,
|
||||
default_partial_file_reader/2
|
||||
]).
|
||||
|
||||
-ifdef(bbmustache_escriptize).
|
||||
-export([main/1]).
|
||||
-endif.
|
||||
|
||||
-export_type([
|
||||
key/0,
|
||||
template/0,
|
||||
data/0,
|
||||
recursive_data/0,
|
||||
option/0, % deprecated
|
||||
compile_option/0,
|
||||
parse_option/0,
|
||||
render_option/0
|
||||
]).
|
||||
|
||||
%%----------------------------------------------------------------------------------------------------------------------
|
||||
%% Defines & Records & Types
|
||||
%%----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-define(PARSE_ERROR, incorrect_format).
|
||||
-define(FILE_ERROR, file_not_found).
|
||||
-define(CONTEXT_MISSING_ERROR(Msg), {context_missing, Msg}).
|
||||
|
||||
-define(IIF(Cond, TValue, FValue),
|
||||
case Cond of true -> TValue; false -> FValue end).
|
||||
|
||||
-define(ADD(X, Y), ?IIF(X =:= <<>>, Y, [X | Y])).
|
||||
-define(START_TAG, <<"{{">>).
|
||||
-define(STOP_TAG, <<"}}">>).
|
||||
|
||||
-define(RAISE_ON_CONTEXT_MISS_ENABLED(Options),
|
||||
proplists:get_bool(raise_on_context_miss, Options)).
|
||||
-define(RAISE_ON_PARTIAL_MISS_ENABLED(Options),
|
||||
proplists:get_bool(raise_on_partial_miss, Options)).
|
||||
|
||||
-define(PARSE_OPTIONS, [
|
||||
partial_file_reader,
|
||||
raise_on_partial_miss
|
||||
]).
|
||||
|
||||
-type key() :: binary().
|
||||
%% Key MUST be a non-whitespace character sequence NOT containing the current closing delimiter. <br />
|
||||
%%
|
||||
%% In addition, `.' have a special meaning. <br />
|
||||
%% (1) `parent.child' ... find the child in the parent. <br />
|
||||
%% (2) `.' ... It means this. However, the type of correspond is only `[integer() | float() | binary() | string() | atom()]'. Otherwise, the behavior is undefined.
|
||||
%%
|
||||
|
||||
-type source() :: binary().
|
||||
%% If you use lamda expressions, the original text is necessary.
|
||||
%%
|
||||
%% ```
|
||||
%% e.g.
|
||||
%% template:
|
||||
%% {{#lamda}}a{{b}}c{{/lamda}}
|
||||
%% parse result:
|
||||
%% {'#', <<"lamda">>, [<<"a">>, {'n', <<"b">>}, <<"c">>], <<"a{{b}}c">>}
|
||||
%% '''
|
||||
%%
|
||||
%% NOTE:
|
||||
%% Since the binary reference is used internally, it is not a capacitively large waste.
|
||||
%% However, the greater the number of tags used, it should use the wasted memory.
|
||||
|
||||
-type tag() :: {n, [key()]}
|
||||
| {'&', [key()]}
|
||||
| {'#', [key()], [tag()], source()}
|
||||
| {'^', [key()], [tag()]}
|
||||
| {'>', key(), Indent :: source()}
|
||||
| binary(). % plain text
|
||||
|
||||
-record(?MODULE,
|
||||
{
|
||||
data :: [tag()],
|
||||
|
||||
partials = [] :: [{key(), [tag()]} | key()],
|
||||
%% The `{key(), [tag()]}` indicates that `key()` already parsed and `[tag()]` is the result of parsing.
|
||||
%% The `key()` indicates that the file did not exist.
|
||||
|
||||
options = [] :: [compile_option()],
|
||||
indents = [] :: [binary()],
|
||||
context_stack = [] :: [data()]
|
||||
}).
|
||||
|
||||
-opaque template() :: #?MODULE{}.
|
||||
%% @see parse_binary/1
|
||||
%% @see parse_file/1
|
||||
|
||||
-record(state,
|
||||
{
|
||||
dirname = <<>> :: file:filename_all(),
|
||||
start = ?START_TAG :: binary(),
|
||||
stop = ?STOP_TAG :: binary(),
|
||||
partials = [] :: [key()],
|
||||
standalone = true :: boolean()
|
||||
}).
|
||||
-type state() :: #state{}.
|
||||
|
||||
-type parse_option() :: {partial_file_reader, fun((Dirname :: binary(), key()) -> Data :: binary())}
|
||||
| raise_on_partial_miss.
|
||||
%% |key |description |
|
||||
%% |:-- |:---------- |
|
||||
%% |partial_file_reader | When you specify this, it delegate reading of file to the function by `partial'.<br/> This can be used when you want to read from files other than local files.|
|
||||
%% |raise_on_partial_miss| If the template used in partials does not found, it will throw an exception (error). |
|
||||
|
||||
-type compile_option() :: {key_type, atom | binary | string}
|
||||
| raise_on_context_miss
|
||||
| {escape_fun, fun((binary()) -> binary())}
|
||||
| {value_serializer, fun((any()) -> iodata())}.
|
||||
%% |key |description |
|
||||
%% |:-- |:---------- |
|
||||
%% |key_type | Specify the type of the key in {@link data/0}. Default value is `string'. |
|
||||
%% |raise_on_context_miss| If key exists in template does not exist in data, it will throw an exception (error).|
|
||||
%% |escape_fun | Specify your own escape function. |
|
||||
%% |value_serializer | specify how terms are converted to iodata when templating. |
|
||||
|
||||
-type render_option() :: compile_option() | parse_option().
|
||||
%% @see compile_option/0
|
||||
%% @see parse_option/0
|
||||
|
||||
-type option() :: compile_option().
|
||||
%% This type has been deprecated since 1.6.0. It will remove in 2.0.0.
|
||||
%% @see compile_option/0
|
||||
|
||||
-type data() :: term().
|
||||
%% Beginners should consider {@link data/0} as {@link recursive_data/0}.
|
||||
%% By specifying options, the type are greatly relaxed and equal to `term/0'.
|
||||
%%
|
||||
%% @see render/2
|
||||
%% @see compile/2
|
||||
|
||||
-type data_key() :: atom() | binary() | string().
|
||||
%% You can choose one from these as the type of key in {@link recursive_data/0}.
|
||||
%% The default is `string/0'.
|
||||
%% If you want to change this, you need to specify `key_type' in {@link compile_option/0}.
|
||||
|
||||
-ifdef(namespaced_types).
|
||||
-type recursive_data() :: #{data_key() => term()} | [{data_key(), term()}].
|
||||
-else.
|
||||
-type recursive_data() :: [{data_key(), term()}].
|
||||
-endif.
|
||||
%% It is a part of {@link data/0} that can have child elements.
|
||||
|
||||
-type endtag() :: {endtag, {state(), [key()], LastTagSize :: non_neg_integer(), Rest :: binary(), Result :: [tag()]}}.
|
||||
|
||||
%%----------------------------------------------------------------------------------------------------------------------
|
||||
%% Exported Functions
|
||||
%%----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
%% @equiv render(Bin, Data, [])
|
||||
-spec render(binary(), data()) -> binary().
|
||||
render(Bin, Data) ->
|
||||
render(Bin, Data, []).
|
||||
|
||||
%% @equiv compile(parse_binary(Bin), Data, Options)
|
||||
-spec render(binary(), data(), [render_option()]) -> binary().
|
||||
render(Bin, Data, Options) ->
|
||||
{ParseOptions, CompileOptions}
|
||||
= lists:partition(fun(X) ->
|
||||
lists:member(?IIF(is_tuple(X), element(1, X), X), ?PARSE_OPTIONS)
|
||||
end, Options),
|
||||
compile(parse_binary(Bin, ParseOptions), Data, CompileOptions).
|
||||
|
||||
%% @equiv parse_binary(Bin, [])
|
||||
-spec parse_binary(binary()) -> template().
|
||||
parse_binary(Bin) when is_binary(Bin) ->
|
||||
parse_binary(Bin, []).
|
||||
|
||||
%% @doc Create a {@link template/0} from a binary.
|
||||
-spec parse_binary(binary(), [parse_option()]) -> template().
|
||||
parse_binary(Bin, Options) ->
|
||||
{State, Data} = parse(#state{}, Bin),
|
||||
parse_remaining_partials(State, #?MODULE{data = Data}, Options).
|
||||
|
||||
%% @equiv parse_file(Filename, [])
|
||||
-spec parse_file(file:filename_all()) -> template().
|
||||
parse_file(Filename) ->
|
||||
parse_file(Filename, []).
|
||||
|
||||
%% @doc Create a {@link template/0} from a file.
|
||||
-spec parse_file(file:filename_all(), [parse_option()]) -> template().
|
||||
parse_file(Filename, Options) ->
|
||||
State = #state{dirname = filename:dirname(Filename)},
|
||||
case file:read_file(Filename) of
|
||||
{ok, Bin} ->
|
||||
{State1, Data} = parse(State, Bin),
|
||||
Template = case to_binary(filename:extension(Filename)) of
|
||||
<<".mustache">> = Ext -> #?MODULE{partials = [{filename:basename(Filename, Ext), Data}], data = Data};
|
||||
_ -> #?MODULE{data = Data}
|
||||
end,
|
||||
parse_remaining_partials(State1, Template, Options);
|
||||
_ ->
|
||||
error(?FILE_ERROR, [Filename, Options])
|
||||
end.
|
||||
|
||||
%% @equiv compile(Template, Data, [])
|
||||
-spec compile(template(), data()) -> binary().
|
||||
compile(Template, Data) ->
|
||||
compile(Template, Data, []).
|
||||
|
||||
%% @doc Embed the data in the template.
|
||||
%%
|
||||
%% ```
|
||||
%% 1> Template = bbmustache:parse_binary(<<"{{name}}">>).
|
||||
%% 2> bbmustache:compile(Template, #{"name" => "Alice"}).
|
||||
%% <<"Alice">>
|
||||
%% '''
|
||||
%% Data support an associative array or a map. <br />
|
||||
%% All keys MUST be same type.
|
||||
-spec compile(template(), data(), [compile_option()]) -> binary().
|
||||
compile(#?MODULE{data = Tags} = T, Data, Options) ->
|
||||
Ret = compile_impl(Tags, Data, [], T#?MODULE{options = Options, data = []}),
|
||||
iolist_to_binary(lists:reverse(Ret)).
|
||||
|
||||
%% @doc Default value serializer for templtated values
|
||||
-spec default_value_serializer(number() | binary() | string() | atom()) -> iodata().
|
||||
default_value_serializer(Integer) when is_integer(Integer) ->
|
||||
list_to_binary(integer_to_list(Integer));
|
||||
default_value_serializer(Float) when is_float(Float) ->
|
||||
%% NOTE: It is the same behaviour as io_lib:format("~p", [Float]), but it is fast than.
|
||||
%% http://www.cs.indiana.edu/~dyb/pubs/FP-Printing-PLDI96.pdf
|
||||
io_lib_format:fwrite_g(Float);
|
||||
default_value_serializer(Atom) when is_atom(Atom) ->
|
||||
list_to_binary(atom_to_list(Atom));
|
||||
default_value_serializer(X) when is_map(X); is_tuple(X) ->
|
||||
error(unsupported_term, [X]);
|
||||
default_value_serializer(X) ->
|
||||
X.
|
||||
|
||||
%% @doc Default partial file reader
|
||||
-spec default_partial_file_reader(binary(), binary()) -> {ok, binary()} | {error, Reason :: term()}.
|
||||
default_partial_file_reader(Dirname, Key) ->
|
||||
Filename0 = <<Key/binary, ".mustache">>,
|
||||
Filename = ?IIF(Dirname =:= <<>>, Filename0, filename:join([Dirname, Filename0])),
|
||||
file:read_file(Filename).
|
||||
|
||||
%%----------------------------------------------------------------------------------------------------------------------
|
||||
%% Internal Function
|
||||
%%----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
%% @doc {@link compile/2}
|
||||
%%
|
||||
%% ATTENTION: The result is a list that is inverted.
|
||||
-spec compile_impl(Template :: [tag()], data(), Result :: iodata(), template()) -> iodata().
|
||||
compile_impl([], _, Result, _) ->
|
||||
Result;
|
||||
compile_impl([{n, Keys} | T], Data, Result, State) ->
|
||||
ValueSerializer = proplists:get_value(value_serializer, State#?MODULE.options, fun default_value_serializer/1),
|
||||
Value = iolist_to_binary(ValueSerializer(get_data_recursive(Keys, Data, <<>>, State))),
|
||||
EscapeFun = proplists:get_value(escape_fun, State#?MODULE.options, fun escape/1),
|
||||
compile_impl(T, Data, ?ADD(EscapeFun(Value), Result), State);
|
||||
compile_impl([{'&', Keys} | T], Data, Result, State) ->
|
||||
ValueSerializer = proplists:get_value(value_serializer, State#?MODULE.options, fun default_value_serializer/1),
|
||||
compile_impl(T, Data, ?ADD(ValueSerializer(get_data_recursive(Keys, Data, <<>>, State)), Result), State);
|
||||
compile_impl([{'#', Keys, Tags, Source} | T], Data, Result, State) ->
|
||||
Value = get_data_recursive(Keys, Data, false, State),
|
||||
NestedState = State#?MODULE{context_stack = [Data | State#?MODULE.context_stack]},
|
||||
case is_recursive_data(Value) of
|
||||
true ->
|
||||
compile_impl(T, Data, compile_impl(Tags, Value, Result, NestedState), State);
|
||||
_ when is_list(Value) ->
|
||||
compile_impl(T, Data, lists:foldl(fun(X, Acc) -> compile_impl(Tags, X, Acc, NestedState) end,
|
||||
Result, Value), State);
|
||||
_ when Value =:= false ->
|
||||
compile_impl(T, Data, Result, State);
|
||||
_ when is_function(Value, 2) ->
|
||||
Ret = Value(Source, fun(Text) -> render(Text, Data, State#?MODULE.options) end),
|
||||
compile_impl(T, Data, ?ADD(Ret, Result), State);
|
||||
_ ->
|
||||
compile_impl(T, Data, compile_impl(Tags, Data, Result, State), State)
|
||||
end;
|
||||
compile_impl([{'^', Keys, Tags} | T], Data, Result, State) ->
|
||||
Value = get_data_recursive(Keys, Data, false, State),
|
||||
case Value =:= [] orelse Value =:= false of
|
||||
true -> compile_impl(T, Data, compile_impl(Tags, Data, Result, State), State);
|
||||
false -> compile_impl(T, Data, Result, State)
|
||||
end;
|
||||
compile_impl([{'>', Key, Indent} | T], Data, Result0, #?MODULE{partials = Partials} = State) ->
|
||||
case proplists:get_value(Key, Partials) of
|
||||
undefined ->
|
||||
case ?RAISE_ON_CONTEXT_MISS_ENABLED(State#?MODULE.options) of
|
||||
true -> error(?CONTEXT_MISSING_ERROR({?FILE_ERROR, Key}));
|
||||
false -> compile_impl(T, Data, Result0, State)
|
||||
end;
|
||||
PartialT ->
|
||||
Indents = State#?MODULE.indents ++ [Indent],
|
||||
Result1 = compile_impl(PartialT, Data, [Indent | Result0], State#?MODULE{indents = Indents}),
|
||||
compile_impl(T, Data, Result1, State)
|
||||
end;
|
||||
compile_impl([B1 | [_|_] = T], Data, Result, #?MODULE{indents = Indents} = State) when Indents =/= [] ->
|
||||
%% NOTE: indent of partials
|
||||
case byte_size(B1) > 0 andalso binary:last(B1) of
|
||||
$\n -> compile_impl(T, Data, [Indents, B1 | Result], State);
|
||||
_ -> compile_impl(T, Data, [B1 | Result], State)
|
||||
end;
|
||||
compile_impl([Bin | T], Data, Result, State) ->
|
||||
compile_impl(T, Data, [Bin | Result], State).
|
||||
|
||||
%% @doc Parse remaining partials in State. It returns {@link template/0}.
|
||||
-spec parse_remaining_partials(state(), template(), [parse_option()]) -> template().
|
||||
parse_remaining_partials(#state{partials = []}, Template = #?MODULE{}, _Options) ->
|
||||
Template;
|
||||
parse_remaining_partials(State = #state{partials = [P | PartialKeys]}, Template = #?MODULE{partials = Partials}, Options) ->
|
||||
case proplists:is_defined(P, Partials) of
|
||||
true -> parse_remaining_partials(State#state{partials = PartialKeys}, Template, Options);
|
||||
false ->
|
||||
FileReader = proplists:get_value(partial_file_reader, Options, fun default_partial_file_reader/2),
|
||||
Dirname = State#state.dirname,
|
||||
case FileReader(Dirname, P) of
|
||||
{ok, Input} ->
|
||||
{State1, Data} = parse(State, Input),
|
||||
parse_remaining_partials(State1, Template#?MODULE{partials = [{P, Data} | Partials]}, Options);
|
||||
{error, Reason} ->
|
||||
case ?RAISE_ON_PARTIAL_MISS_ENABLED(Options) of
|
||||
true -> error({?FILE_ERROR, P, Reason});
|
||||
false -> parse_remaining_partials(State#state{partials = PartialKeys},
|
||||
Template#?MODULE{partials = [P | Partials]}, Options)
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
%% @doc Analyze the syntax of the mustache.
|
||||
-spec parse(state(), binary()) -> {#state{}, [tag()]}.
|
||||
parse(State0, Bin) ->
|
||||
case parse1(State0, Bin, []) of
|
||||
{endtag, {_, Keys, _, _, _}} ->
|
||||
error({?PARSE_ERROR, {section_is_incorrect, binary_join(Keys, <<".">>)}});
|
||||
{#state{partials = Partials} = State, Tags} ->
|
||||
{State#state{partials = lists:usort(Partials), start = ?START_TAG, stop = ?STOP_TAG},
|
||||
lists:reverse(Tags)}
|
||||
end.
|
||||
|
||||
%% @doc Part of the `parse/1'
|
||||
%%
|
||||
%% ATTENTION: The result is a list that is inverted.
|
||||
-spec parse1(state(), Input :: binary(), Result :: [tag()]) -> {state(), [tag()]} | endtag().
|
||||
parse1(#state{start = Start} = State, Bin, Result) ->
|
||||
case binary:match(Bin, [Start, <<"\n">>]) of
|
||||
nomatch -> {State, ?ADD(Bin, Result)};
|
||||
{S, L} ->
|
||||
Pos = S + L,
|
||||
B2 = binary:part(Bin, Pos, byte_size(Bin) - Pos),
|
||||
case binary:at(Bin, S) of
|
||||
$\n -> parse1(State#state{standalone = true}, B2, ?ADD(binary:part(Bin, 0, Pos), Result)); % \n
|
||||
_ -> parse2(State, split_tag(State, Bin), Result)
|
||||
end
|
||||
end.
|
||||
|
||||
%% @doc Part of the `parse/1'
|
||||
%%
|
||||
%% ATTENTION: The result is a list that is inverted.
|
||||
-spec parse2(state(), iolist(), Result :: [tag()]) -> {state(), [tag()]} | endtag().
|
||||
parse2(State, [B1, B2, B3], Result) ->
|
||||
case remove_space_from_head(B2) of
|
||||
<<T, Tag/binary>> when T =:= $&; T =:= ${ ->
|
||||
parse1(State#state{standalone = false}, B3, [{'&', keys(Tag)} | ?ADD(B1, Result)]);
|
||||
<<T, Tag/binary>> when T =:= $#; T =:= $^ ->
|
||||
parse_loop(State, ?IIF(T =:= $#, '#', '^'), keys(Tag), B3, [B1 | Result]);
|
||||
<<"=", Tag0/binary>> ->
|
||||
Tag1 = remove_space_from_tail(Tag0),
|
||||
Size = byte_size(Tag1) - 1,
|
||||
case Size >= 0 andalso Tag1 of
|
||||
<<Tag2:Size/binary, "=">> -> parse_delimiter(State, Tag2, B3, [B1 | Result]);
|
||||
_ -> error({?PARSE_ERROR, {unsupported_tag, <<"=", Tag0/binary>>}})
|
||||
end;
|
||||
<<"!", _/binary>> ->
|
||||
parse3(State, B3, [B1 | Result]);
|
||||
<<"/ |