summaryrefslogtreecommitdiffstats
path: root/third_party/rust/askama
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/askama')
-rw-r--r--third_party/rust/askama/.cargo-checksum.json1
-rw-r--r--third_party/rust/askama/Cargo.toml126
-rw-r--r--third_party/rust/askama/LICENSE-APACHE201
-rw-r--r--third_party/rust/askama/LICENSE-MIT25
-rw-r--r--third_party/rust/askama/README.md96
-rw-r--r--third_party/rust/askama/src/error.rs95
-rw-r--r--third_party/rust/askama/src/filters/json.rs44
-rw-r--r--third_party/rust/askama/src/filters/mod.rs640
-rw-r--r--third_party/rust/askama/src/filters/yaml.rs34
-rw-r--r--third_party/rust/askama/src/helpers.rs48
-rw-r--r--third_party/rust/askama/src/lib.rs219
11 files changed, 1529 insertions, 0 deletions
diff --git a/third_party/rust/askama/.cargo-checksum.json b/third_party/rust/askama/.cargo-checksum.json
new file mode 100644
index 0000000000..889c533360
--- /dev/null
+++ b/third_party/rust/askama/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"fbab611fc3ba2204942300a534b4f030460f33b0606fa50b9ad08ea567ba81e8","LICENSE-APACHE":"87cb0d734c723c083e51c825930ff42bce28596b52dee15567f6b28f19c195e3","LICENSE-MIT":"df20e0180764bf5bd76f74d47bc9e8c0069a666401629c390003a1d5eba99c92","README.md":"6a4430cf614ff9d36ba01463a8f94085ed4b0889fd719793fa914568247acce2","src/error.rs":"1e3f8020092469090f314f60685c077347e730a88222dfdaa38aaf2396507532","src/filters/json.rs":"dccd0a3f1017da9f6cd9650bd39eb1670f4a9833d2f0968614cd8cd65d18a9dd","src/filters/mod.rs":"903d09599e62f56657b00b2aa577c9d2f963348dd12a1029e90e68549f78b1db","src/filters/yaml.rs":"4e641bedbe3666b334836fb6603fe7f718f7e90d8e33419acca624f50a580c3f","src/helpers.rs":"76e0422acd4ccba7b1735d6ab7622a93f6ec5a2fa89531111d877266784d5334","src/lib.rs":"3a6e4d0b3aadc7c391cbe59416504a719406303726122779281a3af1a7ad76a4"},"package":"47cbc3cf73fa8d9833727bbee4835ba5c421a0d65b72daf9a7b5d0e0f9cfb57e"} \ No newline at end of file
diff --git a/third_party/rust/askama/Cargo.toml b/third_party/rust/askama/Cargo.toml
new file mode 100644
index 0000000000..15129d2dd2
--- /dev/null
+++ b/third_party/rust/askama/Cargo.toml
@@ -0,0 +1,126 @@
+# 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 are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.58"
+name = "askama"
+version = "0.12.0"
+description = "Type-safe, compiled Jinja-like templates for Rust"
+homepage = "https://github.com/djc/askama"
+documentation = "https://docs.rs/askama"
+readme = "README.md"
+keywords = [
+ "markup",
+ "template",
+ "jinja2",
+ "html",
+]
+categories = ["template-engine"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/djc/askama"
+resolver = "1"
+
+[package.metadata.docs.rs]
+features = [
+ "config",
+ "humansize",
+ "num-traits",
+ "serde-json",
+ "serde-yaml",
+]
+
+[dependencies.askama_derive]
+version = "0.12.0"
+
+[dependencies.askama_escape]
+version = "0.10.3"
+
+[dependencies.comrak]
+version = "0.16"
+optional = true
+default-features = false
+
+[dependencies.dep_humansize]
+version = "2"
+optional = true
+package = "humansize"
+
+[dependencies.dep_num_traits]
+version = "0.2.6"
+optional = true
+package = "num-traits"
+
+[dependencies.percent-encoding]
+version = "2.1.0"
+optional = true
+
+[dependencies.serde]
+version = "1.0"
+features = ["derive"]
+optional = true
+
+[dependencies.serde_json]
+version = "1.0"
+optional = true
+
+[dependencies.serde_yaml]
+version = "0.9"
+optional = true
+
+[features]
+config = ["askama_derive/config"]
+default = [
+ "config",
+ "humansize",
+ "num-traits",
+ "urlencode",
+]
+humansize = [
+ "askama_derive/humansize",
+ "dep_humansize",
+]
+markdown = [
+ "askama_derive/markdown",
+ "comrak",
+]
+mime = []
+mime_guess = []
+num-traits = [
+ "askama_derive/num-traits",
+ "dep_num_traits",
+]
+serde-json = [
+ "askama_derive/serde-json",
+ "askama_escape/json",
+ "serde",
+ "serde_json",
+]
+serde-yaml = [
+ "askama_derive/serde-yaml",
+ "serde",
+ "serde_yaml",
+]
+urlencode = [
+ "askama_derive/urlencode",
+ "percent-encoding",
+]
+with-actix-web = ["askama_derive/with-actix-web"]
+with-axum = ["askama_derive/with-axum"]
+with-gotham = ["askama_derive/with-gotham"]
+with-hyper = ["askama_derive/with-hyper"]
+with-mendes = ["askama_derive/with-mendes"]
+with-rocket = ["askama_derive/with-rocket"]
+with-tide = ["askama_derive/with-tide"]
+with-warp = ["askama_derive/with-warp"]
+
+[badges.maintenance]
+status = "actively-developed"
diff --git a/third_party/rust/askama/LICENSE-APACHE b/third_party/rust/askama/LICENSE-APACHE
new file mode 100644
index 0000000000..b7ca568694
--- /dev/null
+++ b/third_party/rust/askama/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 2017-2020 Dirkjan Ochtman
+
+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/third_party/rust/askama/LICENSE-MIT b/third_party/rust/askama/LICENSE-MIT
new file mode 100644
index 0000000000..c765f2e75a
--- /dev/null
+++ b/third_party/rust/askama/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2017-2020 Dirkjan Ochtman
+
+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/third_party/rust/askama/README.md b/third_party/rust/askama/README.md
new file mode 100644
index 0000000000..9055004f06
--- /dev/null
+++ b/third_party/rust/askama/README.md
@@ -0,0 +1,96 @@
+# Askama
+
+[![Documentation](https://docs.rs/askama/badge.svg)](https://docs.rs/askama/)
+[![Latest version](https://img.shields.io/crates/v/askama.svg)](https://crates.io/crates/askama)
+[![Build Status](https://github.com/djc/askama/workflows/CI/badge.svg)](https://github.com/djc/askama/actions?query=workflow%3ACI)
+[![Chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/djc/askama)
+
+Askama implements a template rendering engine based on [Jinja](https://jinja.palletsprojects.com/).
+It generates Rust code from your templates at compile time
+based on a user-defined `struct` to hold the template's context.
+See below for an example, or read [the book][docs].
+
+**"Pretty exciting. I would love to use this already."** --
+[Armin Ronacher][mitsuhiko], creator of Jinja
+
+All feedback welcome. Feel free to file bugs, requests for documentation and
+any other feedback to the [issue tracker][issues] or [tweet me][twitter].
+
+Askama was created by and is maintained by Dirkjan Ochtman. If you are in a
+position to support ongoing maintenance and further development or use it
+in a for-profit context, please consider supporting my open source work on
+[Patreon][patreon].
+
+### Feature highlights
+
+* Construct templates using a familiar, easy-to-use syntax
+* Benefit from the safety provided by Rust's type system
+* Template code is compiled into your crate for [optimal performance][benchmarks]
+* Optional built-in support for Actix, Axum, Gotham, Mendes, Rocket, tide, and warp web frameworks
+* Debugging features to assist you in template development
+* Templates must be valid UTF-8 and produce UTF-8 when rendered
+* IDE support available in [JetBrains products](https://plugins.jetbrains.com/plugin/16591-askama-template-support)
+* Works on stable Rust
+
+### Supported in templates
+
+* Template inheritance
+* Loops, if/else statements and include support
+* Macro support
+* Variables (no mutability allowed)
+* Some built-in filters, and the ability to use your own
+* Whitespace suppressing with '-' markers
+* Opt-out HTML escaping
+* Syntax customization
+
+[docs]: https://djc.github.io/askama/
+[fafhrd91]: https://github.com/fafhrd91
+[mitsuhiko]: http://lucumr.pocoo.org/
+[issues]: https://github.com/djc/askama/issues
+[twitter]: https://twitter.com/djco/
+[patreon]: https://www.patreon.com/dochtman
+[benchmarks]: https://github.com/djc/template-benchmarks-rs
+
+
+How to get started
+------------------
+
+First, add the following to your crate's `Cargo.toml`:
+
+```toml
+# in section [dependencies]
+askama = "0.11.2"
+
+```
+
+Now create a directory called `templates` in your crate root.
+In it, create a file called `hello.html`, containing the following:
+
+```
+Hello, {{ name }}!
+```
+
+In any Rust file inside your crate, add the following:
+
+```rust
+use askama::Template; // bring trait in scope
+
+#[derive(Template)] // this will generate the code...
+#[template(path = "hello.html")] // using the template in this path, relative
+ // to the `templates` dir in the crate root
+struct HelloTemplate<'a> { // the name of the struct can be anything
+ name: &'a str, // the field name should match the variable name
+ // in your template
+}
+
+fn main() {
+ let hello = HelloTemplate { name: "world" }; // instantiate your struct
+ println!("{}", hello.render().unwrap()); // then render it.
+}
+```
+
+You should now be able to compile and run this code.
+
+Review the [test cases] for more examples.
+
+[test cases]: https://github.com/djc/askama/tree/main/testing
diff --git a/third_party/rust/askama/src/error.rs b/third_party/rust/askama/src/error.rs
new file mode 100644
index 0000000000..406b1485a7
--- /dev/null
+++ b/third_party/rust/askama/src/error.rs
@@ -0,0 +1,95 @@
+use std::fmt::{self, Display};
+
+pub type Result<I, E = Error> = ::std::result::Result<I, E>;
+
+/// askama error type
+///
+/// # Feature Interaction
+///
+/// If the feature `serde_json` is enabled an
+/// additional error variant `Json` is added.
+///
+/// # Why not `failure`/`error-chain`?
+///
+/// Error from `error-chain` are not `Sync` which
+/// can lead to problems e.g. when this is used
+/// by a crate which use `failure`. Implementing
+/// `Fail` on the other hand prevents the implementation
+/// of `std::error::Error` until specialization lands
+/// on stable. While errors impl. `Fail` can be
+/// converted to a type impl. `std::error::Error`
+/// using a adapter the benefits `failure` would
+/// bring to this crate are small, which is why
+/// `std::error::Error` was used.
+///
+#[non_exhaustive]
+#[derive(Debug)]
+pub enum Error {
+ /// formatting error
+ Fmt(fmt::Error),
+
+ /// an error raised by using `?` in a template
+ Custom(Box<dyn std::error::Error + Send + Sync>),
+
+ /// json conversion error
+ #[cfg(feature = "serde_json")]
+ Json(::serde_json::Error),
+
+ /// yaml conversion error
+ #[cfg(feature = "serde_yaml")]
+ Yaml(::serde_yaml::Error),
+}
+
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match *self {
+ Error::Fmt(ref err) => Some(err),
+ Error::Custom(ref err) => Some(err.as_ref()),
+ #[cfg(feature = "serde_json")]
+ Error::Json(ref err) => Some(err),
+ #[cfg(feature = "serde_yaml")]
+ Error::Yaml(ref err) => Some(err),
+ }
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::Fmt(err) => write!(formatter, "formatting error: {err}"),
+ Error::Custom(err) => write!(formatter, "{err}"),
+ #[cfg(feature = "serde_json")]
+ Error::Json(err) => write!(formatter, "json conversion error: {err}"),
+ #[cfg(feature = "serde_yaml")]
+ Error::Yaml(err) => write!(formatter, "yaml conversion error: {}", err),
+ }
+ }
+}
+
+impl From<fmt::Error> for Error {
+ fn from(err: fmt::Error) -> Self {
+ Error::Fmt(err)
+ }
+}
+
+#[cfg(feature = "serde_json")]
+impl From<::serde_json::Error> for Error {
+ fn from(err: ::serde_json::Error) -> Self {
+ Error::Json(err)
+ }
+}
+
+#[cfg(feature = "serde_yaml")]
+impl From<::serde_yaml::Error> for Error {
+ fn from(err: ::serde_yaml::Error) -> Self {
+ Error::Yaml(err)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Error;
+
+ trait AssertSendSyncStatic: Send + Sync + 'static {}
+ impl AssertSendSyncStatic for Error {}
+}
diff --git a/third_party/rust/askama/src/filters/json.rs b/third_party/rust/askama/src/filters/json.rs
new file mode 100644
index 0000000000..809be91b0c
--- /dev/null
+++ b/third_party/rust/askama/src/filters/json.rs
@@ -0,0 +1,44 @@
+use crate::error::{Error, Result};
+use askama_escape::JsonEscapeBuffer;
+use serde::Serialize;
+use serde_json::to_writer_pretty;
+
+/// Serialize to JSON (requires `json` feature)
+///
+/// The generated string does not contain ampersands `&`, chevrons `< >`, or apostrophes `'`.
+/// To use it in a `<script>` you can combine it with the safe filter:
+///
+/// ``` html
+/// <script>
+/// var data = {{data|json|safe}};
+/// </script>
+/// ```
+///
+/// To use it in HTML attributes, you can either use it in quotation marks `"{{data|json}}"` as is,
+/// or in apostrophes with the (optional) safe filter `'{{data|json|safe}}'`.
+/// In HTML texts the output of e.g. `<pre>{{data|json|safe}}</pre>` is safe, too.
+pub fn json<S: Serialize>(s: S) -> Result<String> {
+ let mut writer = JsonEscapeBuffer::new();
+ to_writer_pretty(&mut writer, &s).map_err(Error::from)?;
+ Ok(writer.finish())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_json() {
+ assert_eq!(json(true).unwrap(), "true");
+ assert_eq!(json("foo").unwrap(), r#""foo""#);
+ assert_eq!(json(true).unwrap(), "true");
+ assert_eq!(json("foo").unwrap(), r#""foo""#);
+ assert_eq!(
+ json(vec!["foo", "bar"]).unwrap(),
+ r#"[
+ "foo",
+ "bar"
+]"#
+ );
+ }
+}
diff --git a/third_party/rust/askama/src/filters/mod.rs b/third_party/rust/askama/src/filters/mod.rs
new file mode 100644
index 0000000000..f76a463c5d
--- /dev/null
+++ b/third_party/rust/askama/src/filters/mod.rs
@@ -0,0 +1,640 @@
+//! Module for built-in filter functions
+//!
+//! Contains all the built-in filter functions for use in templates.
+//! You can define your own filters, as well.
+//! For more information, read the [book](https://djc.github.io/askama/filters.html).
+#![allow(clippy::trivially_copy_pass_by_ref)]
+
+use std::fmt::{self, Write};
+
+#[cfg(feature = "serde-json")]
+mod json;
+#[cfg(feature = "serde-json")]
+pub use self::json::json;
+
+#[cfg(feature = "serde-yaml")]
+mod yaml;
+#[cfg(feature = "serde-yaml")]
+pub use self::yaml::yaml;
+
+#[allow(unused_imports)]
+use crate::error::Error::Fmt;
+use askama_escape::{Escaper, MarkupDisplay};
+#[cfg(feature = "humansize")]
+use dep_humansize::{format_size_i, ToF64, DECIMAL};
+#[cfg(feature = "num-traits")]
+use dep_num_traits::{cast::NumCast, Signed};
+#[cfg(feature = "percent-encoding")]
+use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
+
+use super::Result;
+
+#[cfg(feature = "percent-encoding")]
+// Urlencode char encoding set. Only the characters in the unreserved set don't
+// have any special purpose in any part of a URI and can be safely left
+// unencoded as specified in https://tools.ietf.org/html/rfc3986.html#section-2.3
+const URLENCODE_STRICT_SET: &AsciiSet = &NON_ALPHANUMERIC
+ .remove(b'_')
+ .remove(b'.')
+ .remove(b'-')
+ .remove(b'~');
+
+#[cfg(feature = "percent-encoding")]
+// Same as URLENCODE_STRICT_SET, but preserves forward slashes for encoding paths
+const URLENCODE_SET: &AsciiSet = &URLENCODE_STRICT_SET.remove(b'/');
+
+/// Marks a string (or other `Display` type) as safe
+///
+/// Use this is you want to allow markup in an expression, or if you know
+/// that the expression's contents don't need to be escaped.
+///
+/// Askama will automatically insert the first (`Escaper`) argument,
+/// so this filter only takes a single argument of any type that implements
+/// `Display`.
+pub fn safe<E, T>(e: E, v: T) -> Result<MarkupDisplay<E, T>>
+where
+ E: Escaper,
+ T: fmt::Display,
+{
+ Ok(MarkupDisplay::new_safe(v, e))
+}
+
+/// Escapes strings according to the escape mode.
+///
+/// Askama will automatically insert the first (`Escaper`) argument,
+/// so this filter only takes a single argument of any type that implements
+/// `Display`.
+///
+/// It is possible to optionally specify an escaper other than the default for
+/// the template's extension, like `{{ val|escape("txt") }}`.
+pub fn escape<E, T>(e: E, v: T) -> Result<MarkupDisplay<E, T>>
+where
+ E: Escaper,
+ T: fmt::Display,
+{
+ Ok(MarkupDisplay::new_unsafe(v, e))
+}
+
+#[cfg(feature = "humansize")]
+/// Returns adequate string representation (in KB, ..) of number of bytes
+pub fn filesizeformat(b: &(impl ToF64 + Copy)) -> Result<String> {
+ Ok(format_size_i(*b, DECIMAL))
+}
+
+#[cfg(feature = "percent-encoding")]
+/// Percent-encodes the argument for safe use in URI; does not encode `/`.
+///
+/// This should be safe for all parts of URI (paths segments, query keys, query
+/// values). In the rare case that the server can't deal with forward slashes in
+/// the query string, use [`urlencode_strict`], which encodes them as well.
+///
+/// Encodes all characters except ASCII letters, digits, and `_.-~/`. In other
+/// words, encodes all characters which are not in the unreserved set,
+/// as specified by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.3),
+/// with the exception of `/`.
+///
+/// ```none,ignore
+/// <a href="/metro{{ "/stations/Château d'Eau"|urlencode }}">Station</a>
+/// <a href="/page?text={{ "look, unicode/emojis ✨"|urlencode }}">Page</a>
+/// ```
+///
+/// To encode `/` as well, see [`urlencode_strict`](./fn.urlencode_strict.html).
+///
+/// [`urlencode_strict`]: ./fn.urlencode_strict.html
+pub fn urlencode<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ Ok(utf8_percent_encode(&s, URLENCODE_SET).to_string())
+}
+
+#[cfg(feature = "percent-encoding")]
+/// Percent-encodes the argument for safe use in URI; encodes `/`.
+///
+/// Use this filter for encoding query keys and values in the rare case that
+/// the server can't process them unencoded.
+///
+/// Encodes all characters except ASCII letters, digits, and `_.-~`. In other
+/// words, encodes all characters which are not in the unreserved set,
+/// as specified by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.3).
+///
+/// ```none,ignore
+/// <a href="/page?text={{ "look, unicode/emojis ✨"|urlencode_strict }}">Page</a>
+/// ```
+///
+/// If you want to preserve `/`, see [`urlencode`](./fn.urlencode.html).
+pub fn urlencode_strict<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ Ok(utf8_percent_encode(&s, URLENCODE_STRICT_SET).to_string())
+}
+
+/// Formats arguments according to the specified format
+///
+/// The *second* argument to this filter must be a string literal (as in normal
+/// Rust). The two arguments are passed through to the `format!()`
+/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
+/// the Askama code generator, but the order is swapped to support filter
+/// composition.
+///
+/// ```ignore
+/// {{ value | fmt("{:?}") }}
+/// ```
+///
+/// Compare with [format](./fn.format.html).
+pub fn fmt() {}
+
+/// Formats arguments according to the specified format
+///
+/// The first argument to this filter must be a string literal (as in normal
+/// Rust). All arguments are passed through to the `format!()`
+/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
+/// the Askama code generator.
+///
+/// ```ignore
+/// {{ "{:?}{:?}" | format(value, other_value) }}
+/// ```
+///
+/// Compare with [fmt](./fn.fmt.html).
+pub fn format() {}
+
+/// Replaces line breaks in plain text with appropriate HTML
+///
+/// A single newline becomes an HTML line break `<br>` and a new line
+/// followed by a blank line becomes a paragraph break `<p>`.
+pub fn linebreaks<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ let linebroken = s.replace("\n\n", "</p><p>").replace('\n', "<br/>");
+
+ Ok(format!("<p>{linebroken}</p>"))
+}
+
+/// Converts all newlines in a piece of plain text to HTML line breaks
+pub fn linebreaksbr<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ Ok(s.replace('\n', "<br/>"))
+}
+
+/// Replaces only paragraph breaks in plain text with appropriate HTML
+///
+/// A new line followed by a blank line becomes a paragraph break `<p>`.
+/// Paragraph tags only wrap content; empty paragraphs are removed.
+/// No `<br/>` tags are added.
+pub fn paragraphbreaks<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ let linebroken = s.replace("\n\n", "</p><p>").replace("<p></p>", "");
+
+ Ok(format!("<p>{linebroken}</p>"))
+}
+
+/// Converts to lowercase
+pub fn lower<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ Ok(s.to_lowercase())
+}
+
+/// Alias for the `lower()` filter
+pub fn lowercase<T: fmt::Display>(s: T) -> Result<String> {
+ lower(s)
+}
+
+/// Converts to uppercase
+pub fn upper<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ Ok(s.to_uppercase())
+}
+
+/// Alias for the `upper()` filter
+pub fn uppercase<T: fmt::Display>(s: T) -> Result<String> {
+ upper(s)
+}
+
+/// Strip leading and trailing whitespace
+pub fn trim<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ Ok(s.trim().to_owned())
+}
+
+/// Limit string length, appends '...' if truncated
+pub fn truncate<T: fmt::Display>(s: T, len: usize) -> Result<String> {
+ let mut s = s.to_string();
+ if s.len() > len {
+ let mut real_len = len;
+ while !s.is_char_boundary(real_len) {
+ real_len += 1;
+ }
+ s.truncate(real_len);
+ s.push_str("...");
+ }
+ Ok(s)
+}
+
+/// Indent lines with `width` spaces
+pub fn indent<T: fmt::Display>(s: T, width: usize) -> Result<String> {
+ let s = s.to_string();
+
+ let mut indented = String::new();
+
+ for (i, c) in s.char_indices() {
+ indented.push(c);
+
+ if c == '\n' && i < s.len() - 1 {
+ for _ in 0..width {
+ indented.push(' ');
+ }
+ }
+ }
+
+ Ok(indented)
+}
+
+#[cfg(feature = "num-traits")]
+/// Casts number to f64
+pub fn into_f64<T>(number: T) -> Result<f64>
+where
+ T: NumCast,
+{
+ number.to_f64().ok_or(Fmt(fmt::Error))
+}
+
+#[cfg(feature = "num-traits")]
+/// Casts number to isize
+pub fn into_isize<T>(number: T) -> Result<isize>
+where
+ T: NumCast,
+{
+ number.to_isize().ok_or(Fmt(fmt::Error))
+}
+
+/// Joins iterable into a string separated by provided argument
+pub fn join<T, I, S>(input: I, separator: S) -> Result<String>
+where
+ T: fmt::Display,
+ I: Iterator<Item = T>,
+ S: AsRef<str>,
+{
+ let separator: &str = separator.as_ref();
+
+ let mut rv = String::new();
+
+ for (num, item) in input.enumerate() {
+ if num > 0 {
+ rv.push_str(separator);
+ }
+
+ write!(rv, "{item}")?;
+ }
+
+ Ok(rv)
+}
+
+#[cfg(feature = "num-traits")]
+/// Absolute value
+pub fn abs<T>(number: T) -> Result<T>
+where
+ T: Signed,
+{
+ Ok(number.abs())
+}
+
+/// Capitalize a value. The first character will be uppercase, all others lowercase.
+pub fn capitalize<T: fmt::Display>(s: T) -> Result<String> {
+ let s = s.to_string();
+ match s.chars().next() {
+ Some(c) => {
+ let mut replacement: String = c.to_uppercase().collect();
+ replacement.push_str(&s[c.len_utf8()..].to_lowercase());
+ Ok(replacement)
+ }
+ _ => Ok(s),
+ }
+}
+
+/// Centers the value in a field of a given width
+pub fn center(src: &dyn fmt::Display, dst_len: usize) -> Result<String> {
+ let src = src.to_string();
+ let len = src.len();
+
+ if dst_len <= len {
+ Ok(src)
+ } else {
+ let diff = dst_len - len;
+ let mid = diff / 2;
+ let r = diff % 2;
+ let mut buf = String::with_capacity(dst_len);
+
+ for _ in 0..mid {
+ buf.push(' ');
+ }
+
+ buf.push_str(&src);
+
+ for _ in 0..mid + r {
+ buf.push(' ');
+ }
+
+ Ok(buf)
+ }
+}
+
+/// Count the words in that string
+pub fn wordcount<T: fmt::Display>(s: T) -> Result<usize> {
+ let s = s.to_string();
+
+ Ok(s.split_whitespace().count())
+}
+
+#[cfg(feature = "markdown")]
+pub fn markdown<E, S>(
+ e: E,
+ s: S,
+ options: Option<&comrak::ComrakOptions>,
+) -> Result<MarkupDisplay<E, String>>
+where
+ E: Escaper,
+ S: AsRef<str>,
+{
+ use comrak::{
+ markdown_to_html, ComrakExtensionOptions, ComrakOptions, ComrakParseOptions,
+ ComrakRenderOptions, ListStyleType,
+ };
+
+ const DEFAULT_OPTIONS: ComrakOptions = ComrakOptions {
+ extension: ComrakExtensionOptions {
+ strikethrough: true,
+ tagfilter: true,
+ table: true,
+ autolink: true,
+ // default:
+ tasklist: false,
+ superscript: false,
+ header_ids: None,
+ footnotes: false,
+ description_lists: false,
+ front_matter_delimiter: None,
+ },
+ parse: ComrakParseOptions {
+ // default:
+ smart: false,
+ default_info_string: None,
+ relaxed_tasklist_matching: false,
+ },
+ render: ComrakRenderOptions {
+ unsafe_: false,
+ escape: true,
+ // default:
+ hardbreaks: false,
+ github_pre_lang: false,
+ width: 0,
+ list_style: ListStyleType::Dash,
+ },
+ };
+
+ let s = markdown_to_html(s.as_ref(), options.unwrap_or(&DEFAULT_OPTIONS));
+ Ok(MarkupDisplay::new_safe(s, e))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[cfg(feature = "num-traits")]
+ use std::f64::INFINITY;
+
+ #[cfg(feature = "humansize")]
+ #[test]
+ fn test_filesizeformat() {
+ assert_eq!(filesizeformat(&0).unwrap(), "0 B");
+ assert_eq!(filesizeformat(&999u64).unwrap(), "999 B");
+ assert_eq!(filesizeformat(&1000i32).unwrap(), "1 kB");
+ assert_eq!(filesizeformat(&1023).unwrap(), "1.02 kB");
+ assert_eq!(filesizeformat(&1024usize).unwrap(), "1.02 kB");
+ }
+
+ #[cfg(feature = "percent-encoding")]
+ #[test]
+ fn test_urlencoding() {
+ // Unreserved (https://tools.ietf.org/html/rfc3986.html#section-2.3)
+ // alpha / digit
+ assert_eq!(urlencode("AZaz09").unwrap(), "AZaz09");
+ assert_eq!(urlencode_strict("AZaz09").unwrap(), "AZaz09");
+ // other
+ assert_eq!(urlencode("_.-~").unwrap(), "_.-~");
+ assert_eq!(urlencode_strict("_.-~").unwrap(), "_.-~");
+
+ // Reserved (https://tools.ietf.org/html/rfc3986.html#section-2.2)
+ // gen-delims
+ assert_eq!(urlencode(":/?#[]@").unwrap(), "%3A/%3F%23%5B%5D%40");
+ assert_eq!(
+ urlencode_strict(":/?#[]@").unwrap(),
+ "%3A%2F%3F%23%5B%5D%40"
+ );
+ // sub-delims
+ assert_eq!(
+ urlencode("!$&'()*+,;=").unwrap(),
+ "%21%24%26%27%28%29%2A%2B%2C%3B%3D"
+ );
+ assert_eq!(
+ urlencode_strict("!$&'()*+,;=").unwrap(),
+ "%21%24%26%27%28%29%2A%2B%2C%3B%3D"
+ );
+
+ // Other
+ assert_eq!(
+ urlencode("žŠďŤňĚáÉóŮ").unwrap(),
+ "%C5%BE%C5%A0%C4%8F%C5%A4%C5%88%C4%9A%C3%A1%C3%89%C3%B3%C5%AE"
+ );
+ assert_eq!(
+ urlencode_strict("žŠďŤňĚáÉóŮ").unwrap(),
+ "%C5%BE%C5%A0%C4%8F%C5%A4%C5%88%C4%9A%C3%A1%C3%89%C3%B3%C5%AE"
+ );
+
+ // Ferris
+ assert_eq!(urlencode("🦀").unwrap(), "%F0%9F%A6%80");
+ assert_eq!(urlencode_strict("🦀").unwrap(), "%F0%9F%A6%80");
+ }
+
+ #[test]
+ fn test_linebreaks() {
+ assert_eq!(
+ linebreaks("Foo\nBar Baz").unwrap(),
+ "<p>Foo<br/>Bar Baz</p>"
+ );
+ assert_eq!(
+ linebreaks("Foo\nBar\n\nBaz").unwrap(),
+ "<p>Foo<br/>Bar</p><p>Baz</p>"
+ );
+ }
+
+ #[test]
+ fn test_linebreaksbr() {
+ assert_eq!(linebreaksbr("Foo\nBar").unwrap(), "Foo<br/>Bar");
+ assert_eq!(
+ linebreaksbr("Foo\nBar\n\nBaz").unwrap(),
+ "Foo<br/>Bar<br/><br/>Baz"
+ );
+ }
+
+ #[test]
+ fn test_paragraphbreaks() {
+ assert_eq!(
+ paragraphbreaks("Foo\nBar Baz").unwrap(),
+ "<p>Foo\nBar Baz</p>"
+ );
+ assert_eq!(
+ paragraphbreaks("Foo\nBar\n\nBaz").unwrap(),
+ "<p>Foo\nBar</p><p>Baz</p>"
+ );
+ assert_eq!(
+ paragraphbreaks("Foo\n\n\n\n\nBar\n\nBaz").unwrap(),
+ "<p>Foo</p><p>\nBar</p><p>Baz</p>"
+ );
+ }
+
+ #[test]
+ fn test_lower() {
+ assert_eq!(lower("Foo").unwrap(), "foo");
+ assert_eq!(lower("FOO").unwrap(), "foo");
+ assert_eq!(lower("FooBar").unwrap(), "foobar");
+ assert_eq!(lower("foo").unwrap(), "foo");
+ }
+
+ #[test]
+ fn test_upper() {
+ assert_eq!(upper("Foo").unwrap(), "FOO");
+ assert_eq!(upper("FOO").unwrap(), "FOO");
+ assert_eq!(upper("FooBar").unwrap(), "FOOBAR");
+ assert_eq!(upper("foo").unwrap(), "FOO");
+ }
+
+ #[test]
+ fn test_trim() {
+ assert_eq!(trim(" Hello\tworld\t").unwrap(), "Hello\tworld");
+ }
+
+ #[test]
+ fn test_truncate() {
+ assert_eq!(truncate("hello", 2).unwrap(), "he...");
+ let a = String::from("您好");
+ assert_eq!(a.len(), 6);
+ assert_eq!(String::from("您").len(), 3);
+ assert_eq!(truncate("您好", 1).unwrap(), "您...");
+ assert_eq!(truncate("您好", 2).unwrap(), "您...");
+ assert_eq!(truncate("您好", 3).unwrap(), "您...");
+ assert_eq!(truncate("您好", 4).unwrap(), "您好...");
+ assert_eq!(truncate("您好", 6).unwrap(), "您好");
+ assert_eq!(truncate("您好", 7).unwrap(), "您好");
+ let s = String::from("🤚a🤚");
+ assert_eq!(s.len(), 9);
+ assert_eq!(String::from("🤚").len(), 4);
+ assert_eq!(truncate("🤚a🤚", 1).unwrap(), "🤚...");
+ assert_eq!(truncate("🤚a🤚", 2).unwrap(), "🤚...");
+ assert_eq!(truncate("🤚a🤚", 3).unwrap(), "🤚...");
+ assert_eq!(truncate("🤚a🤚", 4).unwrap(), "🤚...");
+ assert_eq!(truncate("🤚a🤚", 5).unwrap(), "🤚a...");
+ assert_eq!(truncate("🤚a🤚", 6).unwrap(), "🤚a🤚...");
+ assert_eq!(truncate("🤚a🤚", 9).unwrap(), "🤚a🤚");
+ assert_eq!(truncate("🤚a🤚", 10).unwrap(), "🤚a🤚");
+ }
+
+ #[test]
+ fn test_indent() {
+ assert_eq!(indent("hello", 2).unwrap(), "hello");
+ assert_eq!(indent("hello\n", 2).unwrap(), "hello\n");
+ assert_eq!(indent("hello\nfoo", 2).unwrap(), "hello\n foo");
+ assert_eq!(
+ indent("hello\nfoo\n bar", 4).unwrap(),
+ "hello\n foo\n bar"
+ );
+ }
+
+ #[cfg(feature = "num-traits")]
+ #[test]
+ #[allow(clippy::float_cmp)]
+ fn test_into_f64() {
+ assert_eq!(into_f64(1).unwrap(), 1.0_f64);
+ assert_eq!(into_f64(1.9).unwrap(), 1.9_f64);
+ assert_eq!(into_f64(-1.9).unwrap(), -1.9_f64);
+ assert_eq!(into_f64(INFINITY as f32).unwrap(), INFINITY);
+ assert_eq!(into_f64(-INFINITY as f32).unwrap(), -INFINITY);
+ }
+
+ #[cfg(feature = "num-traits")]
+ #[test]
+ fn test_into_isize() {
+ assert_eq!(into_isize(1).unwrap(), 1_isize);
+ assert_eq!(into_isize(1.9).unwrap(), 1_isize);
+ assert_eq!(into_isize(-1.9).unwrap(), -1_isize);
+ assert_eq!(into_isize(1.5_f64).unwrap(), 1_isize);
+ assert_eq!(into_isize(-1.5_f64).unwrap(), -1_isize);
+ match into_isize(INFINITY) {
+ Err(Fmt(fmt::Error)) => {}
+ _ => panic!("Should return error of type Err(Fmt(fmt::Error))"),
+ };
+ }
+
+ #[allow(clippy::needless_borrow)]
+ #[test]
+ fn test_join() {
+ assert_eq!(
+ join((&["hello", "world"]).iter(), ", ").unwrap(),
+ "hello, world"
+ );
+ assert_eq!(join((&["hello"]).iter(), ", ").unwrap(), "hello");
+
+ let empty: &[&str] = &[];
+ assert_eq!(join(empty.iter(), ", ").unwrap(), "");
+
+ let input: Vec<String> = vec!["foo".into(), "bar".into(), "bazz".into()];
+ assert_eq!(join(input.iter(), ":").unwrap(), "foo:bar:bazz");
+
+ let input: &[String] = &["foo".into(), "bar".into()];
+ assert_eq!(join(input.iter(), ":").unwrap(), "foo:bar");
+
+ let real: String = "blah".into();
+ let input: Vec<&str> = vec![&real];
+ assert_eq!(join(input.iter(), ";").unwrap(), "blah");
+
+ assert_eq!(
+ join((&&&&&["foo", "bar"]).iter(), ", ").unwrap(),
+ "foo, bar"
+ );
+ }
+
+ #[cfg(feature = "num-traits")]
+ #[test]
+ #[allow(clippy::float_cmp)]
+ fn test_abs() {
+ assert_eq!(abs(1).unwrap(), 1);
+ assert_eq!(abs(-1).unwrap(), 1);
+ assert_eq!(abs(1.0).unwrap(), 1.0);
+ assert_eq!(abs(-1.0).unwrap(), 1.0);
+ assert_eq!(abs(1.0_f64).unwrap(), 1.0_f64);
+ assert_eq!(abs(-1.0_f64).unwrap(), 1.0_f64);
+ }
+
+ #[test]
+ fn test_capitalize() {
+ assert_eq!(capitalize("foo").unwrap(), "Foo".to_string());
+ assert_eq!(capitalize("f").unwrap(), "F".to_string());
+ assert_eq!(capitalize("fO").unwrap(), "Fo".to_string());
+ assert_eq!(capitalize("").unwrap(), "".to_string());
+ assert_eq!(capitalize("FoO").unwrap(), "Foo".to_string());
+ assert_eq!(capitalize("foO BAR").unwrap(), "Foo bar".to_string());
+ assert_eq!(capitalize("äØÄÅÖ").unwrap(), "Äøäåö".to_string());
+ assert_eq!(capitalize("ß").unwrap(), "SS".to_string());
+ assert_eq!(capitalize("ßß").unwrap(), "SSß".to_string());
+ }
+
+ #[test]
+ fn test_center() {
+ assert_eq!(center(&"f", 3).unwrap(), " f ".to_string());
+ assert_eq!(center(&"f", 4).unwrap(), " f ".to_string());
+ assert_eq!(center(&"foo", 1).unwrap(), "foo".to_string());
+ assert_eq!(center(&"foo bar", 8).unwrap(), "foo bar ".to_string());
+ }
+
+ #[test]
+ fn test_wordcount() {
+ assert_eq!(wordcount("").unwrap(), 0);
+ assert_eq!(wordcount(" \n\t").unwrap(), 0);
+ assert_eq!(wordcount("foo").unwrap(), 1);
+ assert_eq!(wordcount("foo bar").unwrap(), 2);
+ }
+}
diff --git a/third_party/rust/askama/src/filters/yaml.rs b/third_party/rust/askama/src/filters/yaml.rs
new file mode 100644
index 0000000000..9f4c8021ce
--- /dev/null
+++ b/third_party/rust/askama/src/filters/yaml.rs
@@ -0,0 +1,34 @@
+use crate::error::{Error, Result};
+use askama_escape::{Escaper, MarkupDisplay};
+use serde::Serialize;
+
+/// Serialize to YAML (requires `serde_yaml` feature)
+///
+/// ## Errors
+///
+/// This will panic if `S`'s implementation of `Serialize` decides to fail,
+/// or if `T` contains a map with non-string keys.
+pub fn yaml<E: Escaper, S: Serialize>(e: E, s: S) -> Result<MarkupDisplay<E, String>> {
+ match serde_yaml::to_string(&s) {
+ Ok(s) => Ok(MarkupDisplay::new_safe(s, e)),
+ Err(e) => Err(Error::from(e)),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use askama_escape::Html;
+
+ #[test]
+ fn test_yaml() {
+ assert_eq!(yaml(Html, true).unwrap().to_string(), "true\n");
+ assert_eq!(yaml(Html, "foo").unwrap().to_string(), "foo\n");
+ assert_eq!(yaml(Html, true).unwrap().to_string(), "true\n");
+ assert_eq!(yaml(Html, "foo").unwrap().to_string(), "foo\n");
+ assert_eq!(
+ yaml(Html, &vec!["foo", "bar"]).unwrap().to_string(),
+ "- foo\n- bar\n"
+ );
+ }
+}
diff --git a/third_party/rust/askama/src/helpers.rs b/third_party/rust/askama/src/helpers.rs
new file mode 100644
index 0000000000..79a1ada206
--- /dev/null
+++ b/third_party/rust/askama/src/helpers.rs
@@ -0,0 +1,48 @@
+use std::iter::{Enumerate, Peekable};
+
+pub struct TemplateLoop<I>
+where
+ I: Iterator,
+{
+ iter: Peekable<Enumerate<I>>,
+}
+
+impl<I> TemplateLoop<I>
+where
+ I: Iterator,
+{
+ #[inline]
+ pub fn new(iter: I) -> Self {
+ TemplateLoop {
+ iter: iter.enumerate().peekable(),
+ }
+ }
+}
+
+impl<I> Iterator for TemplateLoop<I>
+where
+ I: Iterator,
+{
+ type Item = (<I as Iterator>::Item, LoopItem);
+
+ #[inline]
+ fn next(&mut self) -> Option<(<I as Iterator>::Item, LoopItem)> {
+ self.iter.next().map(|(index, item)| {
+ (
+ item,
+ LoopItem {
+ index,
+ first: index == 0,
+ last: self.iter.peek().is_none(),
+ },
+ )
+ })
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct LoopItem {
+ pub index: usize,
+ pub first: bool,
+ pub last: bool,
+}
diff --git a/third_party/rust/askama/src/lib.rs b/third_party/rust/askama/src/lib.rs
new file mode 100644
index 0000000000..17085b5d63
--- /dev/null
+++ b/third_party/rust/askama/src/lib.rs
@@ -0,0 +1,219 @@
+//! Askama implements a type-safe compiler for Jinja-like templates.
+//! It lets you write templates in a Jinja-like syntax,
+//! which are linked to a `struct` defining the template context.
+//! This is done using a custom derive implementation (implemented
+//! in [`askama_derive`](https://crates.io/crates/askama_derive)).
+//!
+//! For feature highlights and a quick start, please review the
+//! [README](https://github.com/djc/askama/blob/main/README.md).
+//!
+//! The primary documentation for this crate now lives in
+//! [the book](https://djc.github.io/askama/).
+//!
+//! # Creating Askama templates
+//!
+//! An Askama template is a `struct` definition which provides the template
+//! context combined with a UTF-8 encoded text file (or inline source, see
+//! below). Askama can be used to generate any kind of text-based format.
+//! The template file's extension may be used to provide content type hints.
+//!
+//! A template consists of **text contents**, which are passed through as-is,
+//! **expressions**, which get replaced with content while being rendered, and
+//! **tags**, which control the template's logic.
+//! The template syntax is very similar to [Jinja](http://jinja.pocoo.org/),
+//! as well as Jinja-derivatives like [Twig](http://twig.sensiolabs.org/) or
+//! [Tera](https://github.com/Keats/tera).
+//!
+//! ## The `template()` attribute
+//!
+//! Askama works by generating one or more trait implementations for any
+//! `struct` type decorated with the `#[derive(Template)]` attribute. The
+//! code generation process takes some options that can be specified through
+//! the `template()` attribute. The following sub-attributes are currently
+//! recognized:
+//!
+//! * `path` (as `path = "foo.html"`): sets the path to the template file. The
+//! path is interpreted as relative to the configured template directories
+//! (by default, this is a `templates` directory next to your `Cargo.toml`).
+//! The file name extension is used to infer an escape mode (see below). In
+//! web framework integrations, the path's extension may also be used to
+//! infer the content type of the resulting response.
+//! Cannot be used together with `source`.
+//! * `source` (as `source = "{{ foo }}"`): directly sets the template source.
+//! This can be useful for test cases or short templates. The generated path
+//! is undefined, which generally makes it impossible to refer to this
+//! template from other templates. If `source` is specified, `ext` must also
+//! be specified (see below). Cannot be used together with `path`.
+//! * `ext` (as `ext = "txt"`): lets you specify the content type as a file
+//! extension. This is used to infer an escape mode (see below), and some
+//! web framework integrations use it to determine the content type.
+//! Cannot be used together with `path`.
+//! * `print` (as `print = "code"`): enable debugging by printing nothing
+//! (`none`), the parsed syntax tree (`ast`), the generated code (`code`)
+//! or `all` for both. The requested data will be printed to stdout at
+//! compile time.
+//! * `escape` (as `escape = "none"`): override the template's extension used for
+//! the purpose of determining the escaper for this template. See the section
+//! on configuring custom escapers for more information.
+//! * `syntax` (as `syntax = "foo"`): set the syntax name for a parser defined
+//! in the configuration file. The default syntax , "default", is the one
+//! provided by Askama.
+
+#![forbid(unsafe_code)]
+#![deny(elided_lifetimes_in_paths)]
+#![deny(unreachable_pub)]
+
+mod error;
+pub mod filters;
+pub mod helpers;
+
+use std::fmt;
+
+pub use askama_derive::Template;
+pub use askama_escape::{Html, MarkupDisplay, Text};
+
+#[doc(hidden)]
+pub use crate as shared;
+pub use crate::error::{Error, Result};
+
+/// Main `Template` trait; implementations are generally derived
+///
+/// If you need an object-safe template, use [`DynTemplate`].
+pub trait Template: fmt::Display {
+ /// Helper method which allocates a new `String` and renders into it
+ fn render(&self) -> Result<String> {
+ let mut buf = String::with_capacity(Self::SIZE_HINT);
+ self.render_into(&mut buf)?;
+ Ok(buf)
+ }
+
+ /// Renders the template to the given `writer` fmt buffer
+ fn render_into(&self, writer: &mut (impl std::fmt::Write + ?Sized)) -> Result<()>;
+
+ /// Renders the template to the given `writer` io buffer
+ #[inline]
+ fn write_into(&self, writer: &mut (impl std::io::Write + ?Sized)) -> std::io::Result<()> {
+ writer.write_fmt(format_args!("{self}"))
+ }
+
+ /// The template's extension, if provided
+ const EXTENSION: Option<&'static str>;
+
+ /// Provides a conservative estimate of the expanded length of the rendered template
+ const SIZE_HINT: usize;
+
+ /// The MIME type (Content-Type) of the data that gets rendered by this Template
+ const MIME_TYPE: &'static str;
+}
+
+/// Object-safe wrapper trait around [`Template`] implementers
+///
+/// This trades reduced performance (mostly due to writing into `dyn Write`) for object safety.
+pub trait DynTemplate {
+ /// Helper method which allocates a new `String` and renders into it
+ fn dyn_render(&self) -> Result<String>;
+
+ /// Renders the template to the given `writer` fmt buffer
+ fn dyn_render_into(&self, writer: &mut dyn std::fmt::Write) -> Result<()>;
+
+ /// Renders the template to the given `writer` io buffer
+ fn dyn_write_into(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()>;
+
+ /// Helper function to inspect the template's extension
+ fn extension(&self) -> Option<&'static str>;
+
+ /// Provides a conservative estimate of the expanded length of the rendered template
+ fn size_hint(&self) -> usize;
+
+ /// The MIME type (Content-Type) of the data that gets rendered by this Template
+ fn mime_type(&self) -> &'static str;
+}
+
+impl<T: Template> DynTemplate for T {
+ fn dyn_render(&self) -> Result<String> {
+ <Self as Template>::render(self)
+ }
+
+ fn dyn_render_into(&self, writer: &mut dyn std::fmt::Write) -> Result<()> {
+ <Self as Template>::render_into(self, writer)
+ }
+
+ #[inline]
+ fn dyn_write_into(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
+ writer.write_fmt(format_args!("{self}"))
+ }
+
+ fn extension(&self) -> Option<&'static str> {
+ Self::EXTENSION
+ }
+
+ fn size_hint(&self) -> usize {
+ Self::SIZE_HINT
+ }
+
+ fn mime_type(&self) -> &'static str {
+ Self::MIME_TYPE
+ }
+}
+
+impl fmt::Display for dyn DynTemplate {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.dyn_render_into(f).map_err(|_| ::std::fmt::Error {})
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fmt;
+
+ use super::*;
+ use crate::{DynTemplate, Template};
+
+ #[test]
+ fn dyn_template() {
+ struct Test;
+ impl Template for Test {
+ fn render_into(&self, writer: &mut (impl std::fmt::Write + ?Sized)) -> Result<()> {
+ Ok(writer.write_str("test")?)
+ }
+
+ const EXTENSION: Option<&'static str> = Some("txt");
+
+ const SIZE_HINT: usize = 4;
+
+ const MIME_TYPE: &'static str = "text/plain; charset=utf-8";
+ }
+
+ impl fmt::Display for Test {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.render_into(f).map_err(|_| fmt::Error {})
+ }
+ }
+
+ fn render(t: &dyn DynTemplate) -> String {
+ t.dyn_render().unwrap()
+ }
+
+ let test = &Test as &dyn DynTemplate;
+
+ assert_eq!(render(test), "test");
+
+ assert_eq!(test.to_string(), "test");
+
+ assert_eq!(format!("{test}"), "test");
+
+ let mut vec = Vec::new();
+ test.dyn_write_into(&mut vec).unwrap();
+ assert_eq!(vec, vec![b't', b'e', b's', b't']);
+ }
+}
+
+/// Old build script helper to rebuild crates if contained templates have changed
+///
+/// This function is now deprecated and does nothing.
+#[deprecated(
+ since = "0.8.1",
+ note = "file-level dependency tracking is handled automatically without build script"
+)]
+pub fn rerun_if_templates_changed() {}