New upstream version 3.17.0

obs.osmocom.org
Philipp Huebner 2021-12-27 20:09:21 +01:00
commit 36bc1e1299
419 changed files with 79421 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -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

322
CONTRIBUTING.md Normal file
View File

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

43
Dockerfile Normal file
View File

@ -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"]

178
LICENSE Normal file
View File

@ -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

170
README.md Normal file
View File

@ -0,0 +1,170 @@
# Rebar3
[![Build Status](https://github.com/erlang/rebar3/workflows/Common%20Test/badge.svg)](https://github.com/erlang/rebar3/actions?query=branch%3Amaster+workflow%3A"Common+Test") [![Erlang Versions](https://img.shields.io/badge/Supported%20Erlang%2FOTP-22.0%20to%2024.0-blue)](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).

148
THANKS Normal file
View File

@ -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

View File

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

View File

@ -0,0 +1,175 @@
bbmustache
===========
[![Build Status](https://travis-ci.org/soranoba/bbmustache.svg?branch=master)](https://travis-ci.org/soranoba/bbmustache)
[![hex.pm version](https://img.shields.io/hexpm/v/bbmustache.svg)](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 &amp; 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>{&quot;nested&quot;:&quot;value&quot;}</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)

View File

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

View File

@ -0,0 +1 @@
[].

View File

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

View File

@ -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]);
<<"/</