summaryrefslogtreecommitdiffstats
path: root/vendor/tinytemplate
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
commit9835e2ae736235810b4ea1c162ca5e65c547e770 (patch)
tree3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/tinytemplate
parentReleasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff)
downloadrustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz
rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/tinytemplate')
-rw-r--r--vendor/tinytemplate/.cargo-checksum.json1
-rwxr-xr-xvendor/tinytemplate/CHANGELOG.md51
-rwxr-xr-xvendor/tinytemplate/CONTRIBUTING.md67
-rw-r--r--vendor/tinytemplate/Cargo.toml41
-rwxr-xr-xvendor/tinytemplate/LICENSE-APACHE201
-rwxr-xr-xvendor/tinytemplate/LICENSE-MIT25
-rwxr-xr-xvendor/tinytemplate/README.md130
-rwxr-xr-xvendor/tinytemplate/benches/benchmarks.rs58
-rwxr-xr-xvendor/tinytemplate/src/compiler.rs698
-rwxr-xr-xvendor/tinytemplate/src/error.rs246
-rwxr-xr-xvendor/tinytemplate/src/instruction.rs85
-rwxr-xr-xvendor/tinytemplate/src/lib.rs260
-rwxr-xr-xvendor/tinytemplate/src/syntax.rs184
-rwxr-xr-xvendor/tinytemplate/src/template.rs944
14 files changed, 2991 insertions, 0 deletions
diff --git a/vendor/tinytemplate/.cargo-checksum.json b/vendor/tinytemplate/.cargo-checksum.json
new file mode 100644
index 000000000..4ef4c9063
--- /dev/null
+++ b/vendor/tinytemplate/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"59b98fa1a2681e85842dea1e7f6424e57322da775d90a49c4de18169b2551caf","CONTRIBUTING.md":"d9b4e8d9a4449b075d618f3f0c1454558f3140fb51a8013610dd9a9e882ecef1","Cargo.toml":"f5e4758f59eb8cebd7cfb8553d2158223f24fc4716327e2a1de3a8726b8bc441","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"b6d10b8851a36aa6ec9612a186e56c2fcea0ad7db7b90c3dda1d16ae27c0077e","README.md":"46a42e03b08a4d9910650bdeb4dd708f80c97000066fab4b3eac0ee14f5c7f9b","benches/benchmarks.rs":"43e0f13f15cb13edb0dfc8c155e86eec966b13ed36f524c2988afd04938f3f92","src/compiler.rs":"d0da5a0f0048c1ef64d89f51c8a7c4500a431353897007e55d158398bcd591b9","src/error.rs":"7aacecb663cf36dea3b685354b735790527ac7dac64a5dc0668281b46ea751e9","src/instruction.rs":"f8bf799553a5a60266d33fbde10468e7b27571f0af96703914bf5c39294444ea","src/lib.rs":"57fca7200d4316f73e75ca3610b558b8c4e6d5e3f5d6857310af122a43d7105b","src/syntax.rs":"f2077403cc8dd3ec5f53513d59dbbca3fbae0e14bcab6690f27ad45f39a484fe","src/template.rs":"6096b2cfe303365d09cf3773b9427dc5d2c86c7fd92e326f8089e9a9123b3557"},"package":"be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"} \ No newline at end of file
diff --git a/vendor/tinytemplate/CHANGELOG.md b/vendor/tinytemplate/CHANGELOG.md
new file mode 100755
index 000000000..2c757ac82
--- /dev/null
+++ b/vendor/tinytemplate/CHANGELOG.md
@@ -0,0 +1,51 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [1.2.1] - 2021-03-03
+### Fixed
+- Fixed a compile error on some nightly compiler versions.
+
+## [1.2.0] - 2020-01-03
+### Fixed
+ - Fixed numeric values being truthy when zero, rather than when non-zero. (For real this time)
+### Added
+ - Allow numeric indexes to be used in paths, to index into JSON arrays.
+
+## [1.1.0] - 2020-05-31
+ - Added `TinyTemplate::set_default_formatter` which, for example, allows to dissable HTML-scaping
+
+## [1.0.4] - 2020-04-25
+### Added
+- Added `@root` keyword which allows printing, branching on or iterating over the root context
+ object. This is saves having to wrap simple context values in a struct.
+
+## [1.0.3] - 2019-12-26
+### Fixed
+- Fixed the @last keyword never evaluating to true
+- Fixed numeric values being truthy when zero, rather than when non-zero.
+
+## [1.0.2] - 2019-05-16
+### Fixed
+- Fixed possible panic when compiling templates with escaped curly braces.
+
+## [1.0.1] - 2019-01-19
+### Added
+- Added support for older versions of Rust (back to 1.26).
+
+## 1.0.0 - 2019-01-19
+### Added
+- Initial release on Crates.io.
+
+[Unreleased]: https://github.com/bheisler/TinyTemplate/compare/1.2.0...HEAD
+[1.0.1]: https://github.com/bheisler/TinyTemplate/compare/1.0.0...1.0.1
+[1.0.2]: https://github.com/bheisler/TinyTemplate/compare/1.0.1...1.0.2
+[1.0.3]: https://github.com/bheisler/TinyTemplate/compare/1.0.2...1.0.3
+[1.0.4]: https://github.com/bheisler/TinyTemplate/compare/1.0.3...1.0.4
+[1.1.0]: https://github.com/bheisler/TinyTemplate/compare/1.0.4...1.1.0
+[1.2.0]: https://github.com/bheisler/TinyTemplate/compare/1.1.0...1.2.0
+[1.2.1]: https://github.com/bheisler/TinyTemplate/compare/1.2.0...1.2.1
diff --git a/vendor/tinytemplate/CONTRIBUTING.md b/vendor/tinytemplate/CONTRIBUTING.md
new file mode 100755
index 000000000..e6af4b791
--- /dev/null
+++ b/vendor/tinytemplate/CONTRIBUTING.md
@@ -0,0 +1,67 @@
+# Contributing to TinyTemplate
+
+## Ideas, Experiences and Questions
+
+The easiest way to contribute to TinyTemplate is to use it and report your experiences, ask questions and contribute ideas. We'd love to hear your thoughts on how to make TinyTemplate better, or your comments on why you are or are not currently using it.
+
+Issues, ideas, requests and questions should be posted on the issue tracker at:
+
+https://github.com/bheisler/TinyTemplate/issues
+
+## Code
+
+Pull requests are welcome, though please raise an issue or post a comment for discussion first. We're happy to assist new contributors.
+
+If you're not sure what to work on, try checking the [Beginner label](https://github.com/bheisler/TinyTemplate/labels/Beginner)
+
+To make changes to the code, fork the repo and clone it:
+
+`git clone git@github.com:your-username/TinyTemplate.git`
+
+Then make your changes to the code. When you're done, run the tests:
+
+```
+cargo test
+```
+
+It's a good idea to run clippy and fix any warnings as well:
+
+```
+rustup component add clippy-preview
+cargo clippy
+```
+
+Finally, run Rustfmt to maintain a common code style:
+
+```
+rustup component add rustfmt-preview
+cargo fmt
+```
+
+Don't forget to update the CHANGELOG.md file and any appropriate documentation. Once you're finished, push to your fork and submit a pull request. We try to respond to new issues and pull requests quickly, so if there hasn't been any response for more than a few days feel free to ping @bheisler.
+
+Some things that will increase the chance that your pull request is accepted:
+
+* Write tests
+* Clearly document public methods, with examples if possible
+* Write a good commit message
+
+Good documentation is one of the core goals of the TinyTemplate project, so new code in pull requests should have clear and complete documentation.
+
+## Github Labels
+
+TinyTemplate uses a simple set of labels to track issues. Most important are the difficulty labels:
+
+- Beginner - Suitable for people new to TinyTemplate
+- Intermediate - More challenging, likely involves some non-obvious design decisions or knowledge of CUDA
+- Bigger Project - Large and/or complex project such as designing a safe, Rusty wrapper around a complex part of the CUDA API
+
+Additionally, there are a few other noteworthy labels:
+
+- Breaking Change - Fixing this will have to wait until the next breaking-change release
+- Enhancement - Enhancements to existing functionality or documentation
+- Help Wanted - Input and ideas requested
+
+## Code of Conduct
+
+We follow the [Rust Code of Conduct](http://www.rust-lang.org/conduct.html).
diff --git a/vendor/tinytemplate/Cargo.toml b/vendor/tinytemplate/Cargo.toml
new file mode 100644
index 000000000..799556559
--- /dev/null
+++ b/vendor/tinytemplate/Cargo.toml
@@ -0,0 +1,41 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+name = "tinytemplate"
+version = "1.2.1"
+authors = ["Brook Heisler <brookheisler@gmail.com>"]
+description = "Simple, lightweight template engine"
+readme = "README.md"
+keywords = ["template", "html"]
+categories = ["template-engine"]
+license = "Apache-2.0 OR MIT"
+repository = "https://github.com/bheisler/TinyTemplate"
+
+[[bench]]
+name = "benchmarks"
+harness = false
+[dependencies.serde]
+version = "1.0"
+
+[dependencies.serde_json]
+version = "1.0"
+[dev-dependencies.criterion]
+version = "0.3"
+
+[dev-dependencies.serde_derive]
+version = "1.0"
+[badges.maintenance]
+status = "passively-maintained"
+
+[badges.travis-ci]
+repository = "bheisler/TinyTemplate"
diff --git a/vendor/tinytemplate/LICENSE-APACHE b/vendor/tinytemplate/LICENSE-APACHE
new file mode 100755
index 000000000..16fe87b06
--- /dev/null
+++ b/vendor/tinytemplate/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ 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
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/vendor/tinytemplate/LICENSE-MIT b/vendor/tinytemplate/LICENSE-MIT
new file mode 100755
index 000000000..74edb9fb9
--- /dev/null
+++ b/vendor/tinytemplate/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2019 Brook Heisler
+
+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.
diff --git a/vendor/tinytemplate/README.md b/vendor/tinytemplate/README.md
new file mode 100755
index 000000000..c5c793f58
--- /dev/null
+++ b/vendor/tinytemplate/README.md
@@ -0,0 +1,130 @@
+<h1 align="center">TinyTemplate</h1>
+
+<div align="center">Minimal Lightweight Text Templating</div>
+
+<div align="center">
+ <a href="https://docs.rs/tinytemplate/">API Documentation</a>
+ |
+ <a href="https://github.com/bheisler/TinyTemplate/blob/master/CHANGELOG.md">Changelog</a>
+</div>
+
+<div align="center">
+ <a href="https://github.com/bheisler/TinyTemplate/actions">
+ <img src="https://github.com/bheisler/TinyTemplate/workflows/Continuous%20integration/badge.svg" alt="Continuous integration">
+ </a>
+ <a href="https://crates.io/crates/tinytemplate">
+ <img src="https://img.shields.io/crates/v/tinytemplate.svg" alt="Crates.io">
+ </a>
+</div>
+
+TinyTemplate is a small, minimalistic text templating system with limited dependencies.
+
+## Table of Contents
+- [Table of Contents](#table-of-contents)
+ - [Goals](#goals)
+ - [Why TinyTemplate?](#why-tinytemplate)
+ - [Quickstart](#quickstart)
+ - [Compatibility Policy](#compatibility-policy)
+ - [Contributing](#contributing)
+ - [Maintenance](#maintenance)
+ - [License](#license)
+
+### Goals
+
+ The primary design goals are:
+
+ - __Small__: TinyTemplate deliberately does not support many features of more powerful template engines.
+ - __Simple__: TinyTemplate presents a minimal but well-documented user-facing API.
+ - __Lightweight__: TinyTemplate has minimal required dependencies.
+
+Non-goals include:
+
+- __Extensibility__: TinyTemplate supports custom value formatters, but that is all.
+- __Performance__: TinyTemplate provides decent performance, but other template engines are faster.
+
+### Why TinyTemplate?
+
+I created TinyTemplate after noticing that none of the existing template libraries really suited my
+needs for Criterion.rs. Some had large dependency trees to support features that I didn't use. Some
+required adding a build script to convert templates into code at runtime, in search of extreme
+performance that I didn't need. Some had elaborate macro-based DSL's to generate HTML, where I just
+wanted plain text with some markup. Some expect the templates to be provided in a directory of text
+files, but I wanted the template to be included in the binary. I just wanted something small and
+minimal with good documentation but there was nothing like that out there so I wrote my own.
+
+TinyTemplate is well-suited to generating HTML reports and similar text files. It could be used for
+generating HTML or other text in a web-server, but for more-complex use cases another template
+engine may be a better fit.
+
+### Quickstart
+
+First, add TinyTemplate and serde-derive to your `Cargo.toml` file:
+
+```toml
+[dependencies]
+tinytemplate = "1.1"
+serde = { version = "1.0", features = ["derive"] }
+```
+
+Then add this code to "src.rs":
+
+```rust
+use serde::Serialize;
+
+use tinytemplate::TinyTemplate;
+use std::error::Error;
+
+#[derive(Serialize)]
+struct Context {
+ name: String,
+}
+
+static TEMPLATE : &'static str = "Hello {name}!";
+
+pub fn main() -> Result<(), Box<dyn Error>> {
+ let mut tt = TinyTemplate::new();
+ tt.add_template("hello", TEMPLATE)?;
+
+ let context = Context {
+ name: "World".to_string(),
+ };
+
+ let rendered = tt.render("hello", &context)?;
+ println!("{}", rendered);
+
+ Ok(())
+}
+```
+
+This should print "Hello World!" to stdout.
+
+### Compatibility Policy
+
+TinyTemplate supports the last three stable minor releases of Rust. At time of writing, this means
+Rust 1.38 or later. Older versions may work, but are not tested or guaranteed.
+
+Currently, the oldest version of Rust believed to work is 1.36. Future versions of TinyTemplate may
+break support for such old versions, and this will not be considered a breaking change. If you
+require TinyTemplate to work on old versions of Rust, you will need to stick to a
+specific patch version of TinyTemplate.
+
+### Contributing
+
+Thanks for your interest! Contributions are welcome.
+
+Issues, feature requests, questions and bug reports should be reported via the issue tracker above.
+In particular, becuase TinyTemplate aims to be well-documented, please report anything you find
+confusing or incorrect in the documentation.
+
+Code or documentation improvements in the form of pull requests are also welcome. Please file or
+comment on an issue to allow for discussion before doing a lot of work, though.
+
+For more details, see the [CONTRIBUTING.md file](https://github.com/bheisler/TinyTemplate/blob/master/CONTRIBUTING.md).
+
+### Maintenance
+
+TinyTemplate was created and is currently maintained by Brook Heisler (@bheisler).
+
+### License
+
+TinyTemplate is dual-licensed under the Apache 2.0 license and the MIT license.
diff --git a/vendor/tinytemplate/benches/benchmarks.rs b/vendor/tinytemplate/benches/benchmarks.rs
new file mode 100755
index 000000000..5170f9810
--- /dev/null
+++ b/vendor/tinytemplate/benches/benchmarks.rs
@@ -0,0 +1,58 @@
+#[macro_use]
+extern crate criterion;
+extern crate tinytemplate;
+#[macro_use]
+extern crate serde_derive;
+
+use criterion::Criterion;
+use tinytemplate::TinyTemplate;
+
+static TABLE_SOURCE: &'static str = "<html>
+ {{ for row in table }}
+ <tr>{{ for value in row }}<td>{value}</td>{{ endfor }}</tr>
+ {{ endfor }}
+</html>";
+
+#[derive(Serialize)]
+struct TableContext {
+ table: Vec<Vec<usize>>,
+}
+
+fn make_table_context(size: usize) -> TableContext {
+ let mut table = Vec::with_capacity(size);
+ for _ in 0..size {
+ let mut inner = Vec::with_capacity(size);
+ for i in 0..size {
+ inner.push(i);
+ }
+ table.push(inner);
+ }
+ TableContext { table }
+}
+
+fn parse(criterion: &mut Criterion) {
+ criterion.bench_function("parse-table", |b| {
+ b.iter(|| {
+ let mut tt = TinyTemplate::new();
+ tt.add_template("table", TABLE_SOURCE).unwrap()
+ });
+ });
+}
+
+fn render(criterion: &mut Criterion) {
+ let mut tt = TinyTemplate::new();
+ tt.add_template("table", TABLE_SOURCE).unwrap();
+
+ criterion.bench_function_over_inputs(
+ "render-table",
+ move |b, size| {
+ let data = make_table_context(*size);
+
+ b.iter(|| tt.render("table", &data).unwrap());
+ },
+ vec![1usize, 5, 10, 50, 100, 200],
+ );
+}
+
+criterion_group!(benchmarks, parse, render);
+criterion_main!(benchmarks);
diff --git a/vendor/tinytemplate/src/compiler.rs b/vendor/tinytemplate/src/compiler.rs
new file mode 100755
index 000000000..df37947df
--- /dev/null
+++ b/vendor/tinytemplate/src/compiler.rs
@@ -0,0 +1,698 @@
+#![allow(deprecated)]
+
+/// The compiler module houses the code which parses and compiles templates. TinyTemplate implements
+/// a simple bytecode interpreter (see the [instruction] module for more details) to render templates.
+/// The [`TemplateCompiler`](struct.TemplateCompiler.html) struct is responsible for parsing the
+/// template strings and generating the appropriate bytecode instructions.
+use error::Error::*;
+use error::{get_offset, Error, Result};
+use instruction::{Instruction, Path, PathStep};
+
+/// The end point of a branch or goto instruction is not known.
+const UNKNOWN: usize = ::std::usize::MAX;
+
+/// The compiler keeps a stack of the open blocks so that it can ensure that blocks are closed in
+/// the right order. The Block type is a simple enumeration of the kinds of blocks that could be
+/// open. It may contain the instruction index corresponding to the start of the block.
+enum Block {
+ Branch(usize),
+ For(usize),
+ With,
+}
+
+/// List of the known @-keywords so that we can error if the user spells them wrong.
+static KNOWN_KEYWORDS: [&str; 4] = ["@index", "@first", "@last", "@root"];
+
+/// The TemplateCompiler struct is responsible for parsing a template string and generating bytecode
+/// instructions based on it. The parser is a simple hand-written pattern-matching parser with no
+/// recursion, which makes it relatively easy to read.
+pub(crate) struct TemplateCompiler<'template> {
+ original_text: &'template str,
+ remaining_text: &'template str,
+ instructions: Vec<Instruction<'template>>,
+ block_stack: Vec<(&'template str, Block)>,
+
+ /// When we see a `{foo -}` or similar, we need to remember to left-trim the next text block we
+ /// encounter.
+ trim_next: bool,
+}
+impl<'template> TemplateCompiler<'template> {
+ /// Create a new template compiler to parse and compile the given template.
+ pub fn new(text: &'template str) -> TemplateCompiler<'template> {
+ TemplateCompiler {
+ original_text: text,
+ remaining_text: text,
+ instructions: vec![],
+ block_stack: vec![],
+ trim_next: false,
+ }
+ }
+
+ /// Consume the template compiler to parse the template and return the generated bytecode.
+ pub fn compile(mut self) -> Result<Vec<Instruction<'template>>> {
+ while !self.remaining_text.is_empty() {
+ // Comment, denoted by {# comment text #}
+ if self.remaining_text.starts_with("{#") {
+ self.trim_next = false;
+
+ let tag = self.consume_tag("#}")?;
+ let comment = tag[2..(tag.len() - 2)].trim();
+ if comment.starts_with('-') {
+ self.trim_last_whitespace();
+ }
+ if comment.ends_with('-') {
+ self.trim_next_whitespace();
+ }
+ // Block tag. Block tags are wrapped in {{ }} and always have one word at the start
+ // to identify which kind of tag it is. Depending on the tag type there may be more.
+ } else if self.remaining_text.starts_with("{{") {
+ self.trim_next = false;
+
+ let (discriminant, rest) = self.consume_block()?;
+ match discriminant {
+ "if" => {
+ let (path, negated) = if rest.starts_with("not") {
+ (self.parse_path(&rest[4..])?, true)
+ } else {
+ (self.parse_path(rest)?, false)
+ };
+ self.block_stack
+ .push((discriminant, Block::Branch(self.instructions.len())));
+ self.instructions
+ .push(Instruction::Branch(path, !negated, UNKNOWN));
+ }
+ "else" => {
+ self.expect_empty(rest)?;
+ let num_instructions = self.instructions.len() + 1;
+ self.close_branch(num_instructions, discriminant)?;
+ self.block_stack
+ .push((discriminant, Block::Branch(self.instructions.len())));
+ self.instructions.push(Instruction::Goto(UNKNOWN))
+ }
+ "endif" => {
+ self.expect_empty(rest)?;
+ let num_instructions = self.instructions.len();
+ self.close_branch(num_instructions, discriminant)?;
+ }
+ "with" => {
+ let (path, name) = self.parse_with(rest)?;
+ let instruction = Instruction::PushNamedContext(path, name);
+ self.instructions.push(instruction);
+ self.block_stack.push((discriminant, Block::With));
+ }
+ "endwith" => {
+ self.expect_empty(rest)?;
+ if let Some((_, Block::With)) = self.block_stack.pop() {
+ self.instructions.push(Instruction::PopContext)
+ } else {
+ return Err(self.parse_error(
+ discriminant,
+ "Found a closing endwith that doesn't match with a preceeding with.".to_string()
+ ));
+ }
+ }
+ "for" => {
+ let (path, name) = self.parse_for(rest)?;
+ self.instructions
+ .push(Instruction::PushIterationContext(path, name));
+ self.block_stack
+ .push((discriminant, Block::For(self.instructions.len())));
+ self.instructions.push(Instruction::Iterate(UNKNOWN));
+ }
+ "endfor" => {
+ self.expect_empty(rest)?;
+ let num_instructions = self.instructions.len() + 1;
+ let goto_target = self.close_for(num_instructions, discriminant)?;
+ self.instructions.push(Instruction::Goto(goto_target));
+ self.instructions.push(Instruction::PopContext);
+ }
+ "call" => {
+ let (name, path) = self.parse_call(rest)?;
+ self.instructions.push(Instruction::Call(name, path));
+ }
+ _ => {
+ return Err(self.parse_error(
+ discriminant,
+ format!("Unknown block type '{}'", discriminant),
+ ));
+ }
+ }
+ // Values, of the form { dotted.path.to.value.in.context }
+ // Note that it is not (currently) possible to escape curly braces in the templates to
+ // prevent them from being interpreted as values.
+ } else if self.remaining_text.starts_with('{') {
+ self.trim_next = false;
+
+ let (path, name) = self.consume_value()?;
+ let instruction = match name {
+ Some(name) => Instruction::FormattedValue(path, name),
+ None => Instruction::Value(path),
+ };
+ self.instructions.push(instruction);
+ // All other text - just consume characters until we see a {
+ } else {
+ let mut escaped = false;
+ loop {
+ let mut text = self.consume_text(escaped);
+ if self.trim_next {
+ text = text.trim_left();
+ self.trim_next = false;
+ }
+ escaped = text.ends_with('\\');
+ if escaped {
+ text = &text[0..(text.len() - 1)];
+ }
+ self.instructions.push(Instruction::Literal(text));
+
+ if !escaped {
+ break;
+ }
+ }
+ }
+ }
+
+ if let Some((text, _)) = self.block_stack.pop() {
+ return Err(self.parse_error(
+ text,
+ "Expected block-closing tag, but reached the end of input.".to_string(),
+ ));
+ }
+
+ Ok(self.instructions)
+ }
+
+ /// Splits a string into a list of named segments which can later be used to look up values in the
+ /// context.
+ fn parse_path(&self, text: &'template str) -> Result<Path<'template>> {
+ if !text.starts_with('@') {
+ Ok(text
+ .split('.')
+ .map(|s| match s.parse::<usize>() {
+ Ok(n) => PathStep::Index(s, n),
+ Err(_) => PathStep::Name(s),
+ })
+ .collect::<Vec<_>>())
+ } else if KNOWN_KEYWORDS.iter().any(|k| *k == text) {
+ Ok(vec![PathStep::Name(text)])
+ } else {
+ Err(self.parse_error(text, format!("Invalid keyword name '{}'", text)))
+ }
+ }
+
+ /// Finds the line number and column where an error occurred. Location is the substring of
+ /// self.original_text where the error was found, and msg is the error message.
+ fn parse_error(&self, location: &str, msg: String) -> Error {
+ let (line, column) = get_offset(self.original_text, location);
+ ParseError { msg, line, column }
+ }
+
+ /// Tags which should have no text after the discriminant use this to raise an error if
+ /// text is found.
+ fn expect_empty(&self, text: &str) -> Result<()> {
+ if text.is_empty() {
+ Ok(())
+ } else {
+ Err(self.parse_error(text, format!("Unexpected text '{}'", text)))
+ }
+ }
+
+ /// Close the branch that is on top of the block stack by setting its target instruction
+ /// and popping it from the stack. Returns an error if the top of the block stack is not a
+ /// branch.
+ fn close_branch(&mut self, new_target: usize, discriminant: &str) -> Result<()> {
+ let branch_block = self.block_stack.pop();
+ if let Some((_, Block::Branch(index))) = branch_block {
+ match &mut self.instructions[index] {
+ Instruction::Branch(_, _, target) => {
+ *target = new_target;
+ Ok(())
+ }
+ Instruction::Goto(target) => {
+ *target = new_target;
+ Ok(())
+ }
+ _ => panic!(),
+ }
+ } else {
+ Err(self.parse_error(
+ discriminant,
+ "Found a closing endif or else which doesn't match with a preceding if."
+ .to_string(),
+ ))
+ }
+ }
+
+ /// Close the for loop that is on top of the block stack by setting its target instruction and
+ /// popping it from the stack. Returns an error if the top of the stack is not a for loop.
+ /// Returns the index of the loop's Iterate instruction for further processing.
+ fn close_for(&mut self, new_target: usize, discriminant: &str) -> Result<usize> {
+ let branch_block = self.block_stack.pop();
+ if let Some((_, Block::For(index))) = branch_block {
+ match &mut self.instructions[index] {
+ Instruction::Iterate(target) => {
+ *target = new_target;
+ Ok(index)
+ }
+ _ => panic!(),
+ }
+ } else {
+ Err(self.parse_error(
+ discriminant,
+ "Found a closing endfor which doesn't match with a preceding for.".to_string(),
+ ))
+ }
+ }
+
+ /// Advance the cursor to the next { and return the consumed text. If `escaped` is true, skips
+ /// a { at the start of the text.
+ fn consume_text(&mut self, escaped: bool) -> &'template str {
+ let search_substr = if escaped {
+ &self.remaining_text[1..]
+ } else {
+ self.remaining_text
+ };
+
+ let mut position = search_substr
+ .find('{')
+ .unwrap_or_else(|| search_substr.len());
+ if escaped {
+ position += 1;
+ }
+
+ let (text, remaining) = self.remaining_text.split_at(position);
+ self.remaining_text = remaining;
+ text
+ }
+
+ /// Advance the cursor to the end of the value tag and return the value's path and optional
+ /// formatter name.
+ fn consume_value(&mut self) -> Result<(Path<'template>, Option<&'template str>)> {
+ let tag = self.consume_tag("}")?;
+ let mut tag = tag[1..(tag.len() - 1)].trim();
+ if tag.starts_with('-') {
+ tag = tag[1..].trim();
+ self.trim_last_whitespace();
+ }
+ if tag.ends_with('-') {
+ tag = tag[0..tag.len() - 1].trim();
+ self.trim_next_whitespace();
+ }
+
+ if let Some(index) = tag.find('|') {
+ let (path_str, name_str) = tag.split_at(index);
+ let name = name_str[1..].trim();
+ let path = self.parse_path(path_str.trim())?;
+ Ok((path, Some(name)))
+ } else {
+ Ok((self.parse_path(tag)?, None))
+ }
+ }
+
+ /// Right-trim whitespace from the last text block we parsed.
+ fn trim_last_whitespace(&mut self) {
+ if let Some(Instruction::Literal(text)) = self.instructions.last_mut() {
+ *text = text.trim_right();
+ }
+ }
+
+ /// Make a note to left-trim whitespace from the next text block we parse.
+ fn trim_next_whitespace(&mut self) {
+ self.trim_next = true;
+ }
+
+ /// Advance the cursor to the end of the current block tag and return the discriminant substring
+ /// and the rest of the text in the tag. Also handles trimming whitespace where needed.
+ fn consume_block(&mut self) -> Result<(&'template str, &'template str)> {
+ let tag = self.consume_tag("}}")?;
+ let mut block = tag[2..(tag.len() - 2)].trim();
+ if block.starts_with('-') {
+ block = block[1..].trim();
+ self.trim_last_whitespace();
+ }
+ if block.ends_with('-') {
+ block = block[0..block.len() - 1].trim();
+ self.trim_next_whitespace();
+ }
+ let discriminant = block.split_whitespace().next().unwrap_or(block);
+ let rest = block[discriminant.len()..].trim();
+ Ok((discriminant, rest))
+ }
+
+ /// Advance the cursor to after the given expected_close string and return the text in between
+ /// (including the expected_close characters), or return an error message if we reach the end
+ /// of a line of text without finding it.
+ fn consume_tag(&mut self, expected_close: &str) -> Result<&'template str> {
+ if let Some(line) = self.remaining_text.lines().next() {
+ if let Some(pos) = line.find(expected_close) {
+ let (tag, remaining) = self.remaining_text.split_at(pos + expected_close.len());
+ self.remaining_text = remaining;
+ Ok(tag)
+ } else {
+ Err(self.parse_error(
+ line,
+ format!(
+ "Expected a closing '{}' but found end-of-line instead.",
+ expected_close
+ ),
+ ))
+ }
+ } else {
+ Err(self.parse_error(
+ self.remaining_text,
+ format!(
+ "Expected a closing '{}' but found end-of-text instead.",
+ expected_close
+ ),
+ ))
+ }
+ }
+
+ /// Parse a with tag to separate the value path from the (optional) name.
+ fn parse_with(&self, with_text: &'template str) -> Result<(Path<'template>, &'template str)> {
+ if let Some(index) = with_text.find(" as ") {
+ let (path_str, name_str) = with_text.split_at(index);
+ let path = self.parse_path(path_str.trim())?;
+ let name = name_str[" as ".len()..].trim();
+ Ok((path, name))
+ } else {
+ Err(self.parse_error(
+ with_text,
+ format!(
+ "Expected 'as <path>' in with block, but found \"{}\" instead",
+ with_text
+ ),
+ ))
+ }
+ }
+
+ /// Parse a for tag to separate the value path from the name.
+ fn parse_for(&self, for_text: &'template str) -> Result<(Path<'template>, &'template str)> {
+ if let Some(index) = for_text.find(" in ") {
+ let (name_str, path_str) = for_text.split_at(index);
+ let name = name_str.trim();
+ let path = self.parse_path(path_str[" in ".len()..].trim())?;
+ Ok((path, name))
+ } else {
+ Err(self.parse_error(
+ for_text,
+ format!("Unable to parse for block text '{}'", for_text),
+ ))
+ }
+ }
+
+ /// Parse a call tag to separate the template name and context value.
+ fn parse_call(&self, call_text: &'template str) -> Result<(&'template str, Path<'template>)> {
+ if let Some(index) = call_text.find(" with ") {
+ let (name_str, path_str) = call_text.split_at(index);
+ let name = name_str.trim();
+ let path = self.parse_path(path_str[" with ".len()..].trim())?;
+ Ok((name, path))
+ } else {
+ Err(self.parse_error(
+ call_text,
+ format!("Unable to parse call block text '{}'", call_text),
+ ))
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use instruction::Instruction::*;
+
+ fn compile(text: &'static str) -> Result<Vec<Instruction<'static>>> {
+ TemplateCompiler::new(text).compile()
+ }
+
+ #[test]
+ fn test_compile_literal() {
+ let text = "Test String";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(&Literal(text), &instructions[0]);
+ }
+
+ #[test]
+ fn test_compile_value() {
+ let text = "{ foobar }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(&Value(vec![PathStep::Name("foobar")]), &instructions[0]);
+ }
+
+ #[test]
+ fn test_compile_value_with_formatter() {
+ let text = "{ foobar | my_formatter }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &FormattedValue(vec![PathStep::Name("foobar")], "my_formatter"),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_dotted_path() {
+ let text = "{ foo.bar }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &Value(vec![PathStep::Name("foo"), PathStep::Name("bar")]),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_indexed_path() {
+ let text = "{ foo.0.bar }";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &Value(vec![
+ PathStep::Name("foo"),
+ PathStep::Index("0", 0),
+ PathStep::Name("bar")
+ ]),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_mixture() {
+ let text = "Hello { name }, how are you?";
+ let instructions = compile(text).unwrap();
+ assert_eq!(3, instructions.len());
+ assert_eq!(&Literal("Hello "), &instructions[0]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[1]);
+ assert_eq!(&Literal(", how are you?"), &instructions[2]);
+ }
+
+ #[test]
+ fn test_if_endif() {
+ let text = "{{ if foo }}Hello!{{ endif }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], true, 2),
+ &instructions[0]
+ );
+ assert_eq!(&Literal("Hello!"), &instructions[1]);
+ }
+
+ #[test]
+ fn test_if_not_endif() {
+ let text = "{{ if not foo }}Hello!{{ endif }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], false, 2),
+ &instructions[0]
+ );
+ assert_eq!(&Literal("Hello!"), &instructions[1]);
+ }
+
+ #[test]
+ fn test_if_else_endif() {
+ let text = "{{ if foo }}Hello!{{ else }}Goodbye!{{ endif }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(4, instructions.len());
+ assert_eq!(
+ &Branch(vec![PathStep::Name("foo")], true, 3),
+ &instructions[0]
+ );
+ assert_eq!(&Literal("Hello!"), &instructions[1]);
+ assert_eq!(&Goto(4), &instructions[2]);
+ assert_eq!(&Literal("Goodbye!"), &instructions[3]);
+ }
+
+ #[test]
+ fn test_with() {
+ let text = "{{ with foo as bar }}Hello!{{ endwith }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(3, instructions.len());
+ assert_eq!(
+ &PushNamedContext(vec![PathStep::Name("foo")], "bar"),
+ &instructions[0]
+ );
+ assert_eq!(&Literal("Hello!"), &instructions[1]);
+ assert_eq!(&PopContext, &instructions[2]);
+ }
+
+ #[test]
+ fn test_foreach() {
+ let text = "{{ for foo in bar.baz }}{ foo }{{ endfor }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(5, instructions.len());
+ assert_eq!(
+ &PushIterationContext(vec![PathStep::Name("bar"), PathStep::Name("baz")], "foo"),
+ &instructions[0]
+ );
+ assert_eq!(&Iterate(4), &instructions[1]);
+ assert_eq!(&Value(vec![PathStep::Name("foo")]), &instructions[2]);
+ assert_eq!(&Goto(1), &instructions[3]);
+ assert_eq!(&PopContext, &instructions[4]);
+ }
+
+ #[test]
+ fn test_strip_whitespace_value() {
+ let text = "Hello, {- name -} , how are you?";
+ let instructions = compile(text).unwrap();
+ assert_eq!(3, instructions.len());
+ assert_eq!(&Literal("Hello,"), &instructions[0]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[1]);
+ assert_eq!(&Literal(", how are you?"), &instructions[2]);
+ }
+
+ #[test]
+ fn test_strip_whitespace_block() {
+ let text = "Hello, {{- if name -}} {name} {{- endif -}} , how are you?";
+ let instructions = compile(text).unwrap();
+ assert_eq!(6, instructions.len());
+ assert_eq!(&Literal("Hello,"), &instructions[0]);
+ assert_eq!(
+ &Branch(vec![PathStep::Name("name")], true, 5),
+ &instructions[1]
+ );
+ assert_eq!(&Literal(""), &instructions[2]);
+ assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[3]);
+ assert_eq!(&Literal(""), &instructions[4]);
+ assert_eq!(&Literal(", how are you?"), &instructions[5]);
+ }
+
+ #[test]
+ fn test_comment() {
+ let text = "Hello, {# foo bar baz #} there!";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(&Literal("Hello, "), &instructions[0]);
+ assert_eq!(&Literal(" there!"), &instructions[1]);
+ }
+
+ #[test]
+ fn test_strip_whitespace_comment() {
+ let text = "Hello, \t\n {#- foo bar baz -#} \t there!";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(&Literal("Hello,"), &instructions[0]);
+ assert_eq!(&Literal("there!"), &instructions[1]);
+ }
+
+ #[test]
+ fn test_strip_whitespace_followed_by_another_tag() {
+ let text = "{value -}{value} Hello";
+ let instructions = compile(text).unwrap();
+ assert_eq!(3, instructions.len());
+ assert_eq!(&Value(vec![PathStep::Name("value")]), &instructions[0]);
+ assert_eq!(&Value(vec![PathStep::Name("value")]), &instructions[1]);
+ assert_eq!(&Literal(" Hello"), &instructions[2]);
+ }
+
+ #[test]
+ fn test_call() {
+ let text = "{{ call my_macro with foo.bar }}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(1, instructions.len());
+ assert_eq!(
+ &Call(
+ "my_macro",
+ vec![PathStep::Name("foo"), PathStep::Name("bar")]
+ ),
+ &instructions[0]
+ );
+ }
+
+ #[test]
+ fn test_curly_brace_escaping() {
+ let text = "body \\{ \nfont-size: {fontsize} \n}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(4, instructions.len());
+ assert_eq!(&Literal("body "), &instructions[0]);
+ assert_eq!(&Literal("{ \nfont-size: "), &instructions[1]);
+ assert_eq!(&Value(vec![PathStep::Name("fontsize")]), &instructions[2]);
+ assert_eq!(&Literal(" \n}"), &instructions[3]);
+ }
+
+ #[test]
+ fn test_unclosed_tags() {
+ let tags = vec![
+ "{",
+ "{ foo.bar",
+ "{ foo.bar\n }",
+ "{{",
+ "{{ if foo.bar",
+ "{{ if foo.bar \n}}",
+ "{#",
+ "{# if foo.bar",
+ "{# if foo.bar \n#}",
+ ];
+ for tag in tags {
+ compile(tag).unwrap_err();
+ }
+ }
+
+ #[test]
+ fn test_mismatched_blocks() {
+ let text = "{{ if foo }}{{ with bar }}{{ endif }} {{ endwith }}";
+ compile(text).unwrap_err();
+ }
+
+ #[test]
+ fn test_disallows_invalid_keywords() {
+ let text = "{ @foo }";
+ compile(text).unwrap_err();
+ }
+
+ #[test]
+ fn test_diallows_unknown_block_type() {
+ let text = "{{ foobar }}";
+ compile(text).unwrap_err();
+ }
+
+ #[test]
+ fn test_parse_error_line_column_num() {
+ let text = "\n\n\n{{ foobar }}";
+ let err = compile(text).unwrap_err();
+ if let ParseError { line, column, .. } = err {
+ assert_eq!(4, line);
+ assert_eq!(3, column);
+ } else {
+ panic!("Should have returned a parse error");
+ }
+ }
+
+ #[test]
+ fn test_parse_error_on_unclosed_if() {
+ let text = "{{ if foo }}";
+ compile(text).unwrap_err();
+ }
+
+ #[test]
+ fn test_parse_escaped_open_curly_brace() {
+ let text: &str = r"hello \{world}";
+ let instructions = compile(text).unwrap();
+ assert_eq!(2, instructions.len());
+ assert_eq!(&Literal("hello "), &instructions[0]);
+ assert_eq!(&Literal("{world}"), &instructions[1]);
+ }
+}
diff --git a/vendor/tinytemplate/src/error.rs b/vendor/tinytemplate/src/error.rs
new file mode 100755
index 000000000..730c64891
--- /dev/null
+++ b/vendor/tinytemplate/src/error.rs
@@ -0,0 +1,246 @@
+//! Module containing the error type returned by TinyTemplate if an error occurs.
+
+use instruction::{path_to_str, PathSlice};
+use serde_json::Error as SerdeJsonError;
+use serde_json::Value;
+use std::error::Error as StdError;
+use std::fmt;
+
+/// Enum representing the potential errors that TinyTemplate can encounter.
+#[derive(Debug)]
+pub enum Error {
+ ParseError {
+ msg: String,
+ line: usize,
+ column: usize,
+ },
+ RenderError {
+ msg: String,
+ line: usize,
+ column: usize,
+ },
+ SerdeError {
+ err: SerdeJsonError,
+ },
+ GenericError {
+ msg: String,
+ },
+ StdFormatError {
+ err: fmt::Error,
+ },
+ CalledTemplateError {
+ name: String,
+ err: Box<Error>,
+ line: usize,
+ column: usize,
+ },
+ CalledFormatterError {
+ name: String,
+ err: Box<Error>,
+ line: usize,
+ column: usize,
+ },
+
+ #[doc(hidden)]
+ __NonExhaustive,
+}
+impl From<SerdeJsonError> for Error {
+ fn from(err: SerdeJsonError) -> Error {
+ Error::SerdeError { err }
+ }
+}
+impl From<fmt::Error> for Error {
+ fn from(err: fmt::Error) -> Error {
+ Error::StdFormatError { err }
+ }
+}
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::ParseError { msg, line, column } => write!(
+ f,
+ "Failed to parse the template (line {}, column {}). Reason: {}",
+ line, column, msg
+ ),
+ Error::RenderError { msg, line, column } => {
+ write!(
+ f,
+ "Encountered rendering error on line {}, column {}. Reason: {}",
+ line, column, msg
+ )
+ }
+ Error::SerdeError { err } => {
+ write!(f, "Unexpected serde error while converting the context to a serde_json::Value. Error: {}", err)
+ }
+ Error::GenericError { msg } => {
+ write!(f, "{}", msg)
+ }
+ Error::StdFormatError { err } => {
+ write!(f, "Unexpected formatting error: {}", err)
+ }
+ Error::CalledTemplateError {
+ name,
+ err,
+ line,
+ column,
+ } => {
+ write!(
+ f,
+ "Call to sub-template \"{}\" on line {}, column {} failed. Reason: {}",
+ name, line, column, err
+ )
+ }
+ Error::CalledFormatterError {
+ name,
+ err,
+ line,
+ column,
+ } => {
+ write!(
+ f,
+ "Call to value formatter \"{}\" on line {}, column {} failed. Reason: {}",
+ name, line, column, err
+ )
+ }
+ Error::__NonExhaustive => unreachable!(),
+ }
+ }
+}
+impl StdError for Error {
+ fn description(&self) -> &str {
+ match self {
+ Error::ParseError { .. } => "ParseError",
+ Error::RenderError { .. } => "RenderError",
+ Error::SerdeError { .. } => "SerdeError",
+ Error::GenericError { msg } => &msg,
+ Error::StdFormatError { .. } => "StdFormatError",
+ Error::CalledTemplateError { .. } => "CalledTemplateError",
+ Error::CalledFormatterError { .. } => "CalledFormatterError",
+ Error::__NonExhaustive => unreachable!(),
+ }
+ }
+}
+
+pub type Result<T> = ::std::result::Result<T, Error>;
+
+pub(crate) fn lookup_error(source: &str, step: &str, path: PathSlice, current: &Value) -> Error {
+ let avail_str = if let Value::Object(object_map) = current {
+ let mut avail_str = " Available values at this level are ".to_string();
+ for (i, key) in object_map.keys().enumerate() {
+ if i > 0 {
+ avail_str.push_str(", ");
+ }
+ avail_str.push('\'');
+ avail_str.push_str(key);
+ avail_str.push('\'');
+ }
+ avail_str
+ } else {
+ "".to_string()
+ };
+
+ let (line, column) = get_offset(source, step);
+
+ Error::RenderError {
+ msg: format!(
+ "Failed to find value '{}' from path '{}'.{}",
+ step,
+ path_to_str(path),
+ avail_str
+ ),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn truthiness_error(source: &str, path: PathSlice) -> Error {
+ let (line, column) = get_offset(source, path.last().unwrap());
+ Error::RenderError {
+ msg: format!(
+ "Path '{}' produced a value which could not be checked for truthiness.",
+ path_to_str(path)
+ ),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn unprintable_error() -> Error {
+ Error::GenericError {
+ msg: "Expected a printable value but found array or object.".to_string(),
+ }
+}
+
+pub(crate) fn not_iterable_error(source: &str, path: PathSlice) -> Error {
+ let (line, column) = get_offset(source, path.last().unwrap());
+ Error::RenderError {
+ msg: format!(
+ "Expected an array for path '{}' but found a non-iterable value.",
+ path_to_str(path)
+ ),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn unknown_template(source: &str, name: &str) -> Error {
+ let (line, column) = get_offset(source, name);
+ Error::RenderError {
+ msg: format!("Tried to call an unknown template '{}'", name),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn unknown_formatter(source: &str, name: &str) -> Error {
+ let (line, column) = get_offset(source, name);
+ Error::RenderError {
+ msg: format!("Tried to call an unknown formatter '{}'", name),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn called_template_error(source: &str, template_name: &str, err: Error) -> Error {
+ let (line, column) = get_offset(source, template_name);
+ Error::CalledTemplateError {
+ name: template_name.to_string(),
+ err: Box::new(err),
+ line,
+ column,
+ }
+}
+
+pub(crate) fn called_formatter_error(source: &str, formatter_name: &str, err: Error) -> Error {
+ let (line, column) = get_offset(source, formatter_name);
+ Error::CalledFormatterError {
+ name: formatter_name.to_string(),
+ err: Box::new(err),
+ line,
+ column,
+ }
+}
+
+/// Find the line number and column of the target string within the source string. Will panic if
+/// target is not a substring of source.
+pub(crate) fn get_offset(source: &str, target: &str) -> (usize, usize) {
+ let offset = target.as_ptr() as isize - source.as_ptr() as isize;
+ let to_scan = &source[0..(offset as usize)];
+
+ let mut line = 1;
+ let mut column = 0;
+
+ for byte in to_scan.bytes() {
+ match byte as char {
+ '\n' => {
+ line += 1;
+ column = 0;
+ }
+ _ => {
+ column += 1;
+ }
+ }
+ }
+
+ (line, column)
+}
diff --git a/vendor/tinytemplate/src/instruction.rs b/vendor/tinytemplate/src/instruction.rs
new file mode 100755
index 000000000..0e1981465
--- /dev/null
+++ b/vendor/tinytemplate/src/instruction.rs
@@ -0,0 +1,85 @@
+use std::ops::Deref;
+
+/// TinyTemplate implements a simple bytecode interpreter for its template engine. Instructions
+/// for this interpreter are represented by the Instruction enum and typically contain various
+/// parameters such as the path to context values or name strings.
+///
+/// In TinyTemplate, the template string itself is assumed to be statically available (or at least
+/// longer-lived than the TinyTemplate instance) so paths and instructions simply borrow string
+/// slices from the template text. These string slices can then be appended directly to the output
+/// string.
+
+/// Enum for a step in a path which optionally contains a parsed index.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub(crate) enum PathStep<'template> {
+ Name(&'template str),
+ Index(&'template str, usize),
+}
+impl<'template> Deref for PathStep<'template> {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ PathStep::Name(s) => s,
+ PathStep::Index(s, _) => s,
+ }
+ }
+}
+
+/// Sequence of named steps used for looking up values in the context
+pub(crate) type Path<'template> = Vec<PathStep<'template>>;
+
+/// Path, but as a slice.
+pub(crate) type PathSlice<'a, 'template> = &'a [PathStep<'template>];
+
+/// Enum representing the bytecode instructions.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub(crate) enum Instruction<'template> {
+ /// Emit a literal string into the output buffer
+ Literal(&'template str),
+
+ /// Look up the value for the given path and render it into the output buffer using the default
+ /// formatter
+ Value(Path<'template>),
+
+ /// Look up the value for the given path and pass it to the formatter with the given name
+ FormattedValue(Path<'template>, &'template str),
+
+ /// Look up the value at the given path and jump to the given instruction index if that value
+ /// is truthy (if the boolean is true) or falsy (if the boolean is false)
+ Branch(Path<'template>, bool, usize),
+
+ /// Push a named context on the stack, shadowing only that name.
+ PushNamedContext(Path<'template>, &'template str),
+
+ /// Push an iteration context on the stack, shadowing the given name with the current value from
+ /// the vec pointed to by the path. The current value will be updated by the Iterate instruction.
+ /// This is always generated before an Iterate instruction which actually starts the iterator.
+ PushIterationContext(Path<'template>, &'template str),
+
+ /// Pop a context off the stack
+ PopContext,
+
+ /// Advance the topmost iterator on the context stack by one and update that context. If the
+ /// iterator is empty, jump to the given instruction.
+ Iterate(usize),
+
+ /// Unconditionally jump to the given instruction. Used to skip else blocks and repeat loops.
+ Goto(usize),
+
+ /// Look up the named template and render it into the output buffer with the value pointed to
+ /// by the path as its context.
+ Call(&'template str, Path<'template>),
+}
+
+/// Convert a path back into a dotted string.
+pub(crate) fn path_to_str(path: PathSlice) -> String {
+ let mut path_str = "".to_string();
+ for (i, step) in path.iter().enumerate() {
+ if i > 0 {
+ path_str.push('.');
+ }
+ path_str.push_str(step);
+ }
+ path_str
+}
diff --git a/vendor/tinytemplate/src/lib.rs b/vendor/tinytemplate/src/lib.rs
new file mode 100755
index 000000000..396be217c
--- /dev/null
+++ b/vendor/tinytemplate/src/lib.rs
@@ -0,0 +1,260 @@
+//! ## TinyTemplate
+//!
+//! TinyTemplate is a minimal templating library originally designed for use in [Criterion.rs].
+//! It deliberately does not provide all of the features of a full-power template engine, but in
+//! return it provides a simple API, clear templating syntax, decent performance and very few
+//! dependencies.
+//!
+//! ## Features
+//!
+//! The most important features are as follows (see the [syntax](syntax/index.html) module for full
+//! details on the template syntax):
+//!
+//! * Rendering values - `{ myvalue }`
+//! * Conditionals - `{{ if foo }}Foo is true{{ else }}Foo is false{{ endif }}`
+//! * Loops - `{{ for value in row }}{value}{{ endfor }}`
+//! * Customizable value formatters `{ value | my_formatter }`
+//! * Macros `{{ call my_template with foo }}`
+//!
+//! ## Restrictions
+//!
+//! TinyTemplate was designed with the assumption that the templates are available as static strings,
+//! either using string literals or the `include_str!` macro. Thus, it borrows `&str` slices from the
+//! template text itself and uses them during the rendering process. Although it is possible to use
+//! TinyTemplate with template strings loaded at runtime, this is not recommended.
+//!
+//! Additionally, TinyTemplate can only render templates into Strings. If you need to render a
+//! template directly to a socket or file, TinyTemplate may not be right for you.
+//!
+//! ## Example
+//!
+//! ```
+//! #[macro_use]
+//! extern crate serde_derive;
+//! extern crate tinytemplate;
+//!
+//! use tinytemplate::TinyTemplate;
+//! use std::error::Error;
+//!
+//! #[derive(Serialize)]
+//! struct Context {
+//! name: String,
+//! }
+//!
+//! static TEMPLATE : &'static str = "Hello {name}!";
+//!
+//! pub fn main() -> Result<(), Box<Error>> {
+//! let mut tt = TinyTemplate::new();
+//! tt.add_template("hello", TEMPLATE)?;
+//!
+//! let context = Context {
+//! name: "World".to_string(),
+//! };
+//!
+//! let rendered = tt.render("hello", &context)?;
+//! # assert_eq!("Hello World!", &rendered);
+//! println!("{}", rendered);
+//!
+//! Ok(())
+//! }
+//! ```
+//!
+//! [Criterion.rs]: https://github.com/bheisler/criterion.rs
+//!
+
+extern crate serde;
+extern crate serde_json;
+
+#[cfg(test)]
+#[cfg_attr(test, macro_use)]
+extern crate serde_derive;
+
+mod compiler;
+pub mod error;
+mod instruction;
+pub mod syntax;
+mod template;
+
+use error::*;
+use serde::Serialize;
+use serde_json::Value;
+use std::collections::HashMap;
+use std::fmt::Write;
+use template::Template;
+
+/// Type alias for closures which can be used as value formatters.
+pub type ValueFormatter = dyn Fn(&Value, &mut String) -> Result<()>;
+
+/// Appends `value` to `output`, performing HTML-escaping in the process.
+pub fn escape(value: &str, output: &mut String) {
+ // Algorithm taken from the rustdoc source code.
+ let value_str = value;
+ let mut last_emitted = 0;
+ for (i, ch) in value.bytes().enumerate() {
+ match ch as char {
+ '<' | '>' | '&' | '\'' | '"' => {
+ output.push_str(&value_str[last_emitted..i]);
+ let s = match ch as char {
+ '>' => "&gt;",
+ '<' => "&lt;",
+ '&' => "&amp;",
+ '\'' => "&#39;",
+ '"' => "&quot;",
+ _ => unreachable!(),
+ };
+ output.push_str(s);
+ last_emitted = i + 1;
+ }
+ _ => {}
+ }
+ }
+
+ if last_emitted < value_str.len() {
+ output.push_str(&value_str[last_emitted..]);
+ }
+}
+
+/// The format function is used as the default value formatter for all values unless the user
+/// specifies another. It is provided publicly so that it can be called as part of custom formatters.
+/// Values are formatted as follows:
+///
+/// * `Value::Null` => the empty string
+/// * `Value::Bool` => true|false
+/// * `Value::Number` => the number, as formatted by `serde_json`.
+/// * `Value::String` => the string, HTML-escaped
+///
+/// Arrays and objects are not formatted, and attempting to do so will result in a rendering error.
+pub fn format(value: &Value, output: &mut String) -> Result<()> {
+ match value {
+ Value::Null => Ok(()),
+ Value::Bool(b) => {
+ write!(output, "{}", b)?;
+ Ok(())
+ }
+ Value::Number(n) => {
+ write!(output, "{}", n)?;
+ Ok(())
+ }
+ Value::String(s) => {
+ escape(s, output);
+ Ok(())
+ }
+ _ => Err(unprintable_error()),
+ }
+}
+
+/// Identical to [`format`](fn.format.html) except that this does not perform HTML escaping.
+pub fn format_unescaped(value: &Value, output: &mut String) -> Result<()> {
+ match value {
+ Value::Null => Ok(()),
+ Value::Bool(b) => {
+ write!(output, "{}", b)?;
+ Ok(())
+ }
+ Value::Number(n) => {
+ write!(output, "{}", n)?;
+ Ok(())
+ }
+ Value::String(s) => {
+ output.push_str(s);
+ Ok(())
+ }
+ _ => Err(unprintable_error()),
+ }
+}
+
+/// The TinyTemplate struct is the entry point for the TinyTemplate library. It contains the
+/// template and formatter registries and provides functions to render templates as well as to
+/// register templates and formatters.
+pub struct TinyTemplate<'template> {
+ templates: HashMap<&'template str, Template<'template>>,
+ formatters: HashMap<&'template str, Box<ValueFormatter>>,
+ default_formatter: &'template ValueFormatter,
+}
+impl<'template> TinyTemplate<'template> {
+ /// Create a new TinyTemplate registry. The returned registry contains no templates, and has
+ /// [`format_unescaped`](fn.format_unescaped.html) registered as a formatter named "unescaped".
+ pub fn new() -> TinyTemplate<'template> {
+ let mut tt = TinyTemplate {
+ templates: HashMap::default(),
+ formatters: HashMap::default(),
+ default_formatter: &format,
+ };
+ tt.add_formatter("unescaped", format_unescaped);
+ tt
+ }
+
+ /// Parse and compile the given template, then register it under the given name.
+ pub fn add_template(&mut self, name: &'template str, text: &'template str) -> Result<()> {
+ let template = Template::compile(text)?;
+ self.templates.insert(name, template);
+ Ok(())
+ }
+
+ /// Changes the default formatter from [`format`](fn.format.html) to `formatter`. Usefull in combination with [`format_unescaped`](fn.format_unescaped.html) to deactivate HTML-escaping
+ pub fn set_default_formatter<F>(&mut self, formatter: &'template F)
+ where
+ F: 'static + Fn(&Value, &mut String) -> Result<()>,
+ {
+ self.default_formatter = formatter;
+ }
+
+ /// Register the given formatter function under the given name.
+ pub fn add_formatter<F>(&mut self, name: &'template str, formatter: F)
+ where
+ F: 'static + Fn(&Value, &mut String) -> Result<()>,
+ {
+ self.formatters.insert(name, Box::new(formatter));
+ }
+
+ /// Render the template with the given name using the given context object. The context
+ /// object must implement `serde::Serialize` as it will be converted to `serde_json::Value`.
+ pub fn render<C>(&self, template: &str, context: &C) -> Result<String>
+ where
+ C: Serialize,
+ {
+ let value = serde_json::to_value(context)?;
+ match self.templates.get(template) {
+ Some(tmpl) => tmpl.render(
+ &value,
+ &self.templates,
+ &self.formatters,
+ self.default_formatter,
+ ),
+ None => Err(Error::GenericError {
+ msg: format!("Unknown template '{}'", template),
+ }),
+ }
+ }
+}
+impl<'template> Default for TinyTemplate<'template> {
+ fn default() -> TinyTemplate<'template> {
+ TinyTemplate::new()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[derive(Serialize)]
+ struct Context {
+ name: String,
+ }
+
+ static TEMPLATE: &'static str = "Hello {name}!";
+
+ #[test]
+ pub fn test_set_default_formatter() {
+ let mut tt = TinyTemplate::new();
+ tt.add_template("hello", TEMPLATE).unwrap();
+ tt.set_default_formatter(&format_unescaped);
+
+ let context = Context {
+ name: "<World>".to_string(),
+ };
+
+ let rendered = tt.render("hello", &context).unwrap();
+ assert_eq!(rendered, "Hello <World>!")
+ }
+}
diff --git a/vendor/tinytemplate/src/syntax.rs b/vendor/tinytemplate/src/syntax.rs
new file mode 100755
index 000000000..2e8eedd1e
--- /dev/null
+++ b/vendor/tinytemplate/src/syntax.rs
@@ -0,0 +1,184 @@
+//! Documentation of TinyTemplate's template syntax.
+//!
+//! ### Context Types
+//!
+//! TinyTemplate uses `serde_json`'s Value structure to represent the context. Therefore, any
+//! `Serializable` structure can be used as a context. All values in such structures are mapped to
+//! their JSON representations - booleans, numbers, strings, arrays, objects and nulls.
+//!
+//! ### Values
+//!
+//! Template values are marked with `{...}`. For example, this will look up the "name" field in
+//! the context structure and insert it into the rendered string:
+//!
+//! ```text
+//! Hello, {name}, how are you?
+//! ```
+//!
+//! Optionally, a value formatter may be provided. One formatter, "unescaped", is provided by
+//! default. Any other formatters must be registered with the
+//! [`TinyTemplate.add_formatter`](../struct.TinyTemplate.html#method.add_formatter)
+//! function prior to rendering or an error will be generated. This will call the formatter function
+//! registered as "percent_formatter" with the value of the "percentage" field:
+//!
+//! ```text
+//! Give it {percentage | percent_formatter}!
+//! ```
+//!
+//! The value may be a dotted path through a hierarchy of context objects. This will look up the
+//! "friend" field in the context structure, then substitute the "name" field from the "friend"
+//! object.
+//!
+//! ```text
+//! And hello to {friend.name} as well!
+//! ```
+//!
+//! Additionally, you may use the `@root` keyword to refer to the root object of your context.
+//! Since TinyTemplate can't normally print complex context objects, this is only useful if the
+//! context is a simple object like an integer or string.
+//!
+//! ### Conditionals
+//!
+//! TinyTemplate blocks are marked with `{{...}}` - double-braces where values are single-braces.
+//!
+//! Conditionals are denoted by "{{ if path }}...{{ else }}...{{ endif }}". The Else block is
+//! optional. Else-if is not currently supported. If "path" evaluates to a truthy expression
+//! (true if boolean, non-zero if numeric, non-empty for strings and arrays, and non-null for
+//! objects) then the section of the template between "if" and "else" is evaluated, otherwise the
+//! section between "else" and "endif" (if present) is evaluated.
+//!
+//! ```text
+//! {{ if user.is_birthday }}
+//! Happy Birthday!
+//! {{ else }}
+//! Have a nice day!
+//! {{ endif }}
+//! ```
+//!
+//! The condition can be negated by using "{{ if not path }}":
+//!
+//! ```text
+//! {{ if not user.is_birthday }}
+//! Have a nice day!
+//! {{ else }}
+//! Happy Birthday!
+//! {{ endif }}
+//! ```
+//!
+//! If desired, the `@root` keyword can be used to branch on the root context object.
+//!
+//! ### Loops
+//!
+//! TinyTemplate supports iterating over the values of arrays. Only arrays are supported. Loops
+//! are denoted by "{{ for value_name in value.path }}...{{ endfor }}". The section of the template between
+//! the two tags will be executed once for each value in the array denoted by "value.path".
+//!
+//! ```text
+//! Hello to {{ for name in guests }}
+//! {name}
+//! {{ endfor }}
+//! ```
+//!
+//! If the iteration value chosen in the "for" tag is the same as that of a regular context value,
+//! the name in the tag will shadow the context value for the scope of the loop. For nested loops,
+//! inner loops will shadow the values of outer loops.
+//!
+//! ```text
+//! {{ for person in guests }}
+//! Hello to {person}{{ for person in person.friends }} and your friend {person}{{ endfor }}
+//! {{ endfor }}
+//! ```
+//!
+//! There are three special values which are available within a loop:
+//!
+//! * `@index` - zero-based index of the current value within the array.
+//! * `@first` - true if this is the first iteration of the loop, otherwise false.
+//! * `@last` - true if this is the last iteration of the loop, otherwise false.
+//!
+//! ```text
+//! Hello to {{ for name in guests -}}
+//! { @index }. {name},
+//! {{- endfor }}
+//! ```
+//!
+//!
+//! In case of nested loops, these values refer to the innermost loop which contains them.
+//!
+//! If the root context object is an array, the `@root` keyword can be used to iterate over the
+//! root object.
+//!
+//! ### With Blocks
+//!
+//! Templates can use with blocks to partially shadows the outer context, the same way that
+//! for-loops do. These are formed like so:
+//!
+//! "{{ with path.to.value as name }}..{{ endwith }}""
+//!
+//! For example:
+//!
+//! ```text
+//! {{ with person.spouse as s }}
+//! Hello { s.name }!
+//! {{ endwith }}
+//! ```
+//!
+//! This looks up "person.spouse" and adds that to the context as "s" within the block. Only the
+//! name "s" is shadowed within the with block and otherwise the outer context is still accessible.
+//!
+//! ### Trimming Whitespace
+//!
+//! If a block tag, comment or value tag includes a "-" character at the start, the trailing
+//! whitespace of the previous text section will be skipped in the output. Likewise, if the tag
+//! ends with a "-", the leading whitespace of the following text will be skipped.
+//!
+//! ```text
+//! Hello { friend.name -}
+//! , how are you?
+//!
+//! {{- if status.good }} I am fine. {{- endif }}
+//! ```
+//!
+//! This will print "Hello friend, how are you? I am fine." without the newlines or extra spaces.
+//!
+//! ### Calling other Templates
+//!
+//! Templates may call other templates by name. The other template must have been registered using
+//! the [`TinyTemplate.add_template`](../struct.TinyTemplate.html#method.add_template) function
+//! before rendering or an error will be generated. This is done with the "call" tag:
+//!
+//! "{{ call template_name with path.to.context }}"
+//!
+//! The call tag has no closing tag. This will look up the "path.to.context" path in the current
+//! context, then render the "template_name" template using the value at that path as the context
+//! for the other template. The string produced by the called template is then inserted into the
+//! output from the calling template. This can be used for a limited form of template code reuse.
+//!
+//! ### Comments
+//!
+//! Comments in the templates are denoted by "{# comment text #}". Comments will be skipped when
+//! rendering the template, though whitespace adjacent to comments will not be stripped unless the
+//! "-" is added. For example:
+//!
+//! ```text
+//! Hello
+//!
+//! {#- This is a comment #} world!
+//! ```
+//!
+//! This will print "Hello world!".
+//!
+//! ### Escaping Curly Braces
+//!
+//! If your template contains opening curly-braces (`{`), they must be escaped using a leading `\`
+//! character. For example:
+//!
+//! ```text
+//! h2 \{
+//! font-size: {fontsize};
+//! }
+//! ```
+//!
+//! If using a string literal in rust source code, the `\` itself must be escaped, producing `\\{`.
+//!
+
+// There's nothing here, this module is solely for documentation.
diff --git a/vendor/tinytemplate/src/template.rs b/vendor/tinytemplate/src/template.rs
new file mode 100755
index 000000000..6f0162d22
--- /dev/null
+++ b/vendor/tinytemplate/src/template.rs
@@ -0,0 +1,944 @@
+//! This module implements the bytecode interpreter that actually renders the templates.
+
+use compiler::TemplateCompiler;
+use error::Error::*;
+use error::*;
+use instruction::{Instruction, PathSlice, PathStep};
+use serde_json::Value;
+use std::collections::HashMap;
+use std::fmt::Write;
+use std::slice;
+use ValueFormatter;
+
+/// Enum defining the different kinds of records on the context stack.
+enum ContextElement<'render, 'template> {
+ /// Object contexts shadow everything below them on the stack, because every name is looked up
+ /// in this object.
+ Object(&'render Value),
+ /// Named contexts shadow only one name. Any path that starts with that name is looked up in
+ /// this object, and all others are passed on down the stack.
+ Named(&'template str, &'render Value),
+ /// Iteration contexts shadow one name with the current value of the iteration. They also
+ /// store the iteration state. The two usizes are the index of the current value and the length
+ /// of the array that we're iterating over.
+ Iteration(
+ &'template str,
+ &'render Value,
+ usize,
+ usize,
+ slice::Iter<'render, Value>,
+ ),
+}
+
+/// Helper struct which mostly exists so that I have somewhere to put functions that access the
+/// rendering context stack.
+struct RenderContext<'render, 'template> {
+ original_text: &'template str,
+ context_stack: Vec<ContextElement<'render, 'template>>,
+}
+impl<'render, 'template> RenderContext<'render, 'template> {
+ /// Look up the given path in the context stack and return the value (if found) or an error (if
+ /// not)
+ fn lookup(&self, path: PathSlice) -> Result<&'render Value> {
+ for stack_layer in self.context_stack.iter().rev() {
+ match stack_layer {
+ ContextElement::Object(obj) => return self.lookup_in(path, obj),
+ ContextElement::Named(name, obj) => {
+ if *name == &*path[0] {
+ return self.lookup_in(&path[1..], obj);
+ }
+ }
+ ContextElement::Iteration(name, obj, _, _, _) => {
+ if *name == &*path[0] {
+ return self.lookup_in(&path[1..], obj);
+ }
+ }
+ }
+ }
+ panic!("Attempted to do a lookup with an empty context stack. That shouldn't be possible.")
+ }
+
+ /// Look up a path within a given value object and return the resulting value (if found) or
+ /// an error (if not)
+ fn lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value> {
+ let mut current = object;
+ for step in path.iter() {
+ if let PathStep::Index(_, n) = step {
+ if let Some(next) = current.get(n) {
+ current = next;
+ continue;
+ }
+ }
+
+ let step: &str = &*step;
+
+ match current.get(step) {
+ Some(next) => current = next,
+ None => return Err(lookup_error(self.original_text, step, path, current)),
+ }
+ }
+ Ok(current)
+ }
+
+ /// Look up the index and length values for the top iteration context on the stack.
+ fn lookup_index(&self) -> Result<(usize, usize)> {
+ for stack_layer in self.context_stack.iter().rev() {
+ match stack_layer {
+ ContextElement::Iteration(_, _, index, length, _) => return Ok((*index, *length)),
+ _ => continue,
+ }
+ }
+ Err(GenericError {
+ msg: "Used @index outside of a foreach block.".to_string(),
+ })
+ }
+
+ /// Look up the root context object
+ fn lookup_root(&self) -> Result<&'render Value> {
+ match self.context_stack.get(0) {
+ Some(ContextElement::Object(obj)) => Ok(obj),
+ Some(_) => {
+ panic!("Expected Object value at root of context stack, but was something else.")
+ }
+ None => panic!(
+ "Attempted to do a lookup with an empty context stack. That shouldn't be possible."
+ ),
+ }
+ }
+}
+
+/// Structure representing a parsed template. It holds the bytecode program for rendering the
+/// template as well as the length of the original template string, which is used as a guess to
+/// pre-size the output string buffer.
+pub(crate) struct Template<'template> {
+ original_text: &'template str,
+ instructions: Vec<Instruction<'template>>,
+ template_len: usize,
+}
+impl<'template> Template<'template> {
+ /// Create a Template from the given template string.
+ pub fn compile(text: &'template str) -> Result<Template> {
+ Ok(Template {
+ original_text: text,
+ template_len: text.len(),
+ instructions: TemplateCompiler::new(text).compile()?,
+ })
+ }
+
+ /// Render this template into a string and return it (or any error if one is encountered).
+ pub fn render(
+ &self,
+ context: &Value,
+ template_registry: &HashMap<&str, Template>,
+ formatter_registry: &HashMap<&str, Box<ValueFormatter>>,
+ default_formatter: &ValueFormatter,
+ ) -> Result<String> {
+ // The length of the original template seems like a reasonable guess at the length of the
+ // output.
+ let mut output = String::with_capacity(self.template_len);
+ self.render_into(
+ context,
+ template_registry,
+ formatter_registry,
+ default_formatter,
+ &mut output,
+ )?;
+ Ok(output)
+ }
+
+ /// Render this template into a given string. Used for calling other templates.
+ pub fn render_into(
+ &self,
+ context: &Value,
+ template_registry: &HashMap<&str, Template>,
+ formatter_registry: &HashMap<&str, Box<ValueFormatter>>,
+ default_formatter: &ValueFormatter,
+ output: &mut String,
+ ) -> Result<()> {
+ let mut program_counter = 0;
+ let mut render_context = RenderContext {
+ original_text: self.original_text,
+ context_stack: vec![ContextElement::Object(context)],
+ };
+
+ while program_counter < self.instructions.len() {
+ match &self.instructions[program_counter] {
+ Instruction::Literal(text) => {
+ output.push_str(text);
+ program_counter += 1;
+ }
+ Instruction::Value(path) => {
+ let first = path.first().unwrap();
+ if first.starts_with('@') {
+ // Currently we just hard-code the special @-keywords and have special
+ // lookup functions to use them because there are lifetime complexities with
+ // looking up values that don't live for as long as the given context object.
+ let first: &str = &*first;
+ match first {
+ "@index" => {
+ write!(output, "{}", render_context.lookup_index()?.0).unwrap()
+ }
+ "@first" => {
+ write!(output, "{}", render_context.lookup_index()?.0 == 0).unwrap()
+ }
+ "@last" => {
+ let (index, length) = render_context.lookup_index()?;
+ write!(output, "{}", index == length - 1).unwrap()
+ }
+ "@root" => {
+ let value_to_render = render_context.lookup_root()?;
+ default_formatter(value_to_render, output)?;
+ }
+ _ => panic!(), // This should have been caught by the parser.
+ }
+ } else {
+ let value_to_render = render_context.lookup(path)?;
+ default_formatter(value_to_render, output)?;
+ }
+ program_counter += 1;
+ }
+ Instruction::FormattedValue(path, name) => {
+ // The @ keywords aren't supported for formatted values. Should they be?
+ let value_to_render = render_context.lookup(path)?;
+ match formatter_registry.get(name) {
+ Some(formatter) => {
+ let formatter_result = formatter(value_to_render, output);
+ if let Err(err) = formatter_result {
+ return Err(called_formatter_error(self.original_text, name, err));
+ }
+ }
+ None => return Err(unknown_formatter(self.original_text, name)),
+ }
+ program_counter += 1;
+ }
+ Instruction::Branch(path, negate, target) => {
+ let first = path.first().unwrap();
+ let mut truthy = if first.starts_with('@') {
+ let first: &str = &*first;
+ match &*first {
+ "@index" => render_context.lookup_index()?.0 != 0,
+ "@first" => render_context.lookup_index()?.0 == 0,
+ "@last" => {
+ let (index, length) = render_context.lookup_index()?;
+ index == (length - 1)
+ }
+ "@root" => self.value_is_truthy(render_context.lookup_root()?, path)?,
+ other => panic!("Unknown keyword {}", other), // This should have been caught by the parser.
+ }
+ } else {
+ let value_to_render = render_context.lookup(path)?;
+ self.value_is_truthy(value_to_render, path)?
+ };
+ if *negate {
+ truthy = !truthy;
+ }
+
+ if truthy {
+ program_counter = *target;
+ } else {
+ program_counter += 1;
+ }
+ }
+ Instruction::PushNamedContext(path, name) => {
+ let context_value = render_context.lookup(path)?;
+ render_context
+ .context_stack
+ .push(ContextElement::Named(name, context_value));
+ program_counter += 1;
+ }
+ Instruction::PushIterationContext(path, name) => {
+ // We push a context with an invalid index and no value and then wait for the
+ // following Iterate instruction to set the index and value properly.
+ let first = path.first().unwrap();
+ let context_value = match first {
+ PathStep::Name("@root") => render_context.lookup_root()?,
+ PathStep::Name(other) if other.starts_with('@') => {
+ return Err(not_iterable_error(self.original_text, path))
+ }
+ _ => render_context.lookup(path)?,
+ };
+ match context_value {
+ Value::Array(ref arr) => {
+ render_context.context_stack.push(ContextElement::Iteration(
+ name,
+ &Value::Null,
+ ::std::usize::MAX,
+ arr.len(),
+ arr.iter(),
+ ))
+ }
+ _ => return Err(not_iterable_error(self.original_text, path)),
+ };
+ program_counter += 1;
+ }
+ Instruction::PopContext => {
+ render_context.context_stack.pop();
+ program_counter += 1;
+ }
+ Instruction::Goto(target) => {
+ program_counter = *target;
+ }
+ Instruction::Iterate(target) => {
+ match render_context.context_stack.last_mut() {
+ Some(ContextElement::Iteration(_, val, index, _, iter)) => {
+ match iter.next() {
+ Some(new_val) => {
+ *val = new_val;
+ // On the first iteration, this will be usize::MAX so it will
+ // wrap around to zero.
+ *index = index.wrapping_add(1);
+ program_counter += 1;
+ }
+ None => {
+ program_counter = *target;
+ }
+ }
+ }
+ _ => panic!("Malformed program."),
+ };
+ }
+ Instruction::Call(template_name, path) => {
+ let context_value = render_context.lookup(path)?;
+ match template_registry.get(template_name) {
+ Some(templ) => {
+ let called_templ_result = templ.render_into(
+ context_value,
+ template_registry,
+ formatter_registry,
+ default_formatter,
+ output,
+ );
+ if let Err(err) = called_templ_result {
+ return Err(called_template_error(
+ self.original_text,
+ template_name,
+ err,
+ ));
+ }
+ }
+ None => return Err(unknown_template(self.original_text, template_name)),
+ }
+ program_counter += 1;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn value_is_truthy(&self, value: &Value, path: PathSlice) -> Result<bool> {
+ let truthy = match value {
+ Value::Null => false,
+ Value::Bool(b) => *b,
+ Value::Number(n) => match n.as_f64() {
+ Some(float) => float != 0.0,
+ None => {
+ return Err(truthiness_error(self.original_text, path));
+ }
+ },
+ Value::String(s) => !s.is_empty(),
+ Value::Array(arr) => !arr.is_empty(),
+ Value::Object(_) => true,
+ };
+ Ok(truthy)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use compiler::TemplateCompiler;
+
+ fn compile(text: &'static str) -> Template<'static> {
+ Template {
+ original_text: text,
+ template_len: text.len(),
+ instructions: TemplateCompiler::new(text).compile().unwrap(),
+ }
+ }
+
+ #[derive(Serialize)]
+ struct NestedContext {
+ value: usize,
+ }
+
+ #[derive(Serialize)]
+ struct TestContext {
+ number: usize,
+ string: &'static str,
+ boolean: bool,
+ null: Option<usize>,
+ array: Vec<usize>,
+ nested: NestedContext,
+ escapes: &'static str,
+ }
+
+ fn context() -> Value {
+ let ctx = TestContext {
+ number: 5,
+ string: "test",
+ boolean: true,
+ null: None,
+ array: vec![1, 2, 3],
+ nested: NestedContext { value: 10 },
+ escapes: "1:< 2:> 3:& 4:' 5:\"",
+ };
+ ::serde_json::to_value(&ctx).unwrap()
+ }
+
+ fn other_templates() -> HashMap<&'static str, Template<'static>> {
+ let mut map = HashMap::new();
+ map.insert("my_macro", compile("{value}"));
+ map
+ }
+
+ fn format(value: &Value, output: &mut String) -> Result<()> {
+ output.push_str("{");
+ ::format(value, output)?;
+ output.push_str("}");
+ Ok(())
+ }
+
+ fn formatters() -> HashMap<&'static str, Box<ValueFormatter>> {
+ let mut map = HashMap::<&'static str, Box<ValueFormatter>>::new();
+ map.insert("my_formatter", Box::new(format));
+ map
+ }
+
+ pub fn default_formatter() -> &'static ValueFormatter {
+ &::format
+ }
+
+ #[test]
+ fn test_literal() {
+ let template = compile("Hello!");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_value() {
+ let template = compile("{ number }");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("5", &string);
+ }
+
+ #[test]
+ fn test_path() {
+ let template = compile("The number of the day is { nested.value }.");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("The number of the day is 10.", &string);
+ }
+
+ #[test]
+ fn test_if_taken() {
+ let template = compile("{{ if boolean }}Hello!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_if_untaken() {
+ let template = compile("{{ if null }}Hello!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("", &string);
+ }
+
+ #[test]
+ fn test_if_else_taken() {
+ let template = compile("{{ if boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_if_else_untaken() {
+ let template = compile("{{ if null }}Hello!{{ else }}Goodbye!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Goodbye!", &string);
+ }
+
+ #[test]
+ fn test_ifnot_taken() {
+ let template = compile("{{ if not boolean }}Hello!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("", &string);
+ }
+
+ #[test]
+ fn test_ifnot_untaken() {
+ let template = compile("{{ if not null }}Hello!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_ifnot_else_taken() {
+ let template = compile("{{ if not boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Goodbye!", &string);
+ }
+
+ #[test]
+ fn test_ifnot_else_untaken() {
+ let template = compile("{{ if not null }}Hello!{{ else }}Goodbye!{{ endif }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello!", &string);
+ }
+
+ #[test]
+ fn test_nested_ifs() {
+ let template = compile(
+ "{{ if boolean }}Hi, {{ if null }}there!{{ else }}Hello!{{ endif }}{{ endif }}",
+ );
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hi, Hello!", &string);
+ }
+
+ #[test]
+ fn test_with() {
+ let template = compile("{{ with nested as n }}{ n.value } { number }{{endwith}}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("10 5", &string);
+ }
+
+ #[test]
+ fn test_for_loop() {
+ let template = compile("{{ for a in array }}{ a }{{ endfor }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("123", &string);
+ }
+
+ #[test]
+ fn test_for_loop_index() {
+ let template = compile("{{ for a in array }}{ @index }{{ endfor }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("012", &string);
+ }
+
+ #[test]
+ fn test_for_loop_first() {
+ let template =
+ compile("{{ for a in array }}{{if @first }}{ @index }{{ endif }}{{ endfor }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("0", &string);
+ }
+
+ #[test]
+ fn test_for_loop_last() {
+ let template =
+ compile("{{ for a in array }}{{ if @last}}{ @index }{{ endif }}{{ endfor }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("2", &string);
+ }
+
+ #[test]
+ fn test_whitespace_stripping_value() {
+ let template = compile("1 \n\t {- number -} \n 1");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("151", &string);
+ }
+
+ #[test]
+ fn test_call() {
+ let template = compile("{{ call my_macro with nested }}");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("10", &string);
+ }
+
+ #[test]
+ fn test_formatter() {
+ let template = compile("{ nested.value | my_formatter }");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("{10}", &string);
+ }
+
+ #[test]
+ fn test_unknown() {
+ let template = compile("{ foobar }");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap_err();
+ }
+
+ #[test]
+ fn test_escaping() {
+ let template = compile("{ escapes }");
+ let context = context();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("1:&lt; 2:&gt; 3:&amp; 4:&#39; 5:&quot;", &string);
+ }
+
+ #[test]
+ fn test_unescaped() {
+ let template = compile("{ escapes | unescaped }");
+ let context = context();
+ let template_registry = other_templates();
+ let mut formatter_registry = formatters();
+ formatter_registry.insert("unescaped", Box::new(::format_unescaped));
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("1:< 2:> 3:& 4:' 5:\"", &string);
+ }
+
+ #[test]
+ fn test_root_print() {
+ let template = compile("{ @root }");
+ let context = "Hello World!";
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello World!", &string);
+ }
+
+ #[test]
+ fn test_root_branch() {
+ let template = compile("{{ if @root }}Hello World!{{ endif }}");
+ let context = true;
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("Hello World!", &string);
+ }
+
+ #[test]
+ fn test_root_iterate() {
+ let template = compile("{{ for a in @root }}{ a }{{ endfor }}");
+ let context = vec!["foo", "bar"];
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("foobar", &string);
+ }
+
+ #[test]
+ fn test_number_truthiness_zero() {
+ let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
+ let context = 0;
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("not truthy", &string);
+ }
+
+ #[test]
+ fn test_number_truthiness_one() {
+ let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
+ let context = 1;
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("truthy", &string);
+ }
+
+ #[test]
+ fn test_indexed_paths() {
+ #[derive(Serialize)]
+ struct Context {
+ foo: (usize, usize),
+ }
+
+ let template = compile("{ foo.1 }{ foo.0 }");
+ let context = Context { foo: (123, 456) };
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("456123", &string);
+ }
+
+ #[test]
+ fn test_indexed_paths_fall_back_to_string_lookup() {
+ #[derive(Serialize)]
+ struct Context {
+ foo: HashMap<&'static str, usize>,
+ }
+
+ let template = compile("{ foo.1 }{ foo.0 }");
+ let mut foo = HashMap::new();
+ foo.insert("0", 123);
+ foo.insert("1", 456);
+ let context = Context { foo };
+ let context = ::serde_json::to_value(&context).unwrap();
+ let template_registry = other_templates();
+ let formatter_registry = formatters();
+ let string = template
+ .render(
+ &context,
+ &template_registry,
+ &formatter_registry,
+ &default_formatter(),
+ )
+ .unwrap();
+ assert_eq!("456123", &string);
+ }
+}