
commit
36bc1e1299
419 changed files with 79421 additions and 0 deletions
@ -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,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 |