minijinja-1.0.3/.cargo_vcs_info.json0000644000000001470000000000100130070ustar { "git": { "sha1": "e4b91cb76107c875ab2dc12be78d033d01f1ef3e" }, "path_in_vcs": "minijinja" }minijinja-1.0.3/Cargo.toml0000644000000051540000000000100110100ustar # 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.61" name = "minijinja" version = "1.0.3" authors = ["Armin Ronacher "] exclude = ["tests"] description = "a powerful template engine for Rust with minimal dependencies" homepage = "https://github.com/mitsuhiko/minijinja" readme = "README.md" keywords = [ "jinja", "jinja2", "templates", ] license = "Apache-2.0" repository = "https://github.com/mitsuhiko/minijinja" resolver = "1" [package.metadata.docs.rs] features = [ "loader", "json", "urlencode", "custom_syntax", ] rustdoc-args = [ "--cfg", "docsrs", "--html-in-header", "doc-header.html", ] [dependencies.aho-corasick] version = "1.0" optional = true default-features = false [dependencies.indexmap] version = "1.9.0" optional = true [dependencies.memo-map] version = "0.3.1" optional = true [dependencies.percent-encoding] version = "2.2.0" optional = true [dependencies.self_cell] version = "1.0.0" optional = true [dependencies.serde] version = "1.0.130" [dependencies.serde_json] version = "1.0.68" optional = true [dependencies.unicase] version = "2.6.0" optional = true [dependencies.unicode-ident] version = "1.0.5" optional = true [dependencies.v_htmlescape] version = "0.15.8" optional = true [dev-dependencies.insta] version = "1.26.0" features = [ "glob", "serde", ] [dev-dependencies.serde] version = "1.0.130" features = ["derive"] [dev-dependencies.serde_json] version = "1.0.68" [dev-dependencies.similar-asserts] version = "1.4.2" [features] adjacent_loop_items = [] builtins = [] custom_syntax = ["dep:aho-corasick"] debug = [] default = [ "builtins", "debug", "deserialization", "macros", "multi_template", "adjacent_loop_items", ] deserialization = [] fuel = [] internal_debug = [] json = ["serde_json"] key_interning = [] loader = [ "self_cell", "memo-map", ] macros = [] multi_template = [] preserve_order = ["indexmap"] speedups = ["v_htmlescape"] unicode = [ "unicode-ident", "unicase", ] unstable_machinery = ["internal_debug"] unstable_machinery_serde = [ "unstable_machinery", "serde/derive", ] urlencode = ["percent-encoding"] minijinja-1.0.3/Cargo.toml.orig000064400000000000000000000037451046102023000144750ustar 00000000000000[package] name = "minijinja" version = "1.0.3" edition = "2021" license = "Apache-2.0" authors = ["Armin Ronacher "] description = "a powerful template engine for Rust with minimal dependencies" homepage = "https://github.com/mitsuhiko/minijinja" repository = "https://github.com/mitsuhiko/minijinja" keywords = ["jinja", "jinja2", "templates"] readme = "README.md" rust-version = "1.61" exclude = ["tests"] [package.metadata.docs.rs] features = ["loader", "json", "urlencode", "custom_syntax"] rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "doc-header.html"] [features] default = ["builtins", "debug", "deserialization", "macros", "multi_template", "adjacent_loop_items"] # API features preserve_order = ["indexmap"] deserialization = [] debug = [] loader = ["self_cell", "memo-map"] unicode = ["unicode-ident", "unicase"] custom_syntax = ["dep:aho-corasick"] # Speedups key_interning = [] speedups = ["v_htmlescape"] # Engine Features builtins = [] macros = [] multi_template = [] adjacent_loop_items = [] fuel = [] # Extra Filters json = ["serde_json"] urlencode = ["percent-encoding"] # Internal Features that should not be used internal_debug = [] unstable_machinery = ["internal_debug"] unstable_machinery_serde = ["unstable_machinery", "serde/derive"] [dependencies] aho-corasick = { version = "1.0", default-features = false, optional = true } serde = "1.0.130" v_htmlescape = { version = "0.15.8", optional = true } self_cell = { version = "1.0.0", optional = true } serde_json = { version = "1.0.68", optional = true } percent-encoding = { version = "2.2.0", optional = true } indexmap = { version = "1.9.0", optional = true } memo-map = { version = "0.3.1", optional = true } unicode-ident = { version = "1.0.5", optional = true } unicase = { version = "2.6.0", optional = true } [dev-dependencies] insta = { version = "1.26.0", features = ["glob", "serde"] } serde = { version = "1.0.130", features = ["derive"] } serde_json = "1.0.68" similar-asserts = "1.4.2" minijinja-1.0.3/LICENSE000064400000000000000000000251371046102023000126120ustar 00000000000000 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. minijinja-1.0.3/README.md000064400000000000000000000123441046102023000130600ustar 00000000000000

MiniJinja: a powerful template engine for Rust with minimal dependencies

[![Build Status](https://github.com/mitsuhiko/minijinja/workflows/Tests/badge.svg?branch=main)](https://github.com/mitsuhiko/minijinja/actions?query=workflow%3ATests) [![License](https://img.shields.io/github/license/mitsuhiko/minijinja)](https://github.com/mitsuhiko/minijinja/blob/main/LICENSE) [![Crates.io](https://img.shields.io/crates/d/minijinja.svg)](https://crates.io/crates/minijinja) [![rustc 1.61.0](https://img.shields.io/badge/rust-1.61%2B-orange.svg)](https://img.shields.io/badge/rust-1.61%2B-orange.svg) [![Documentation](https://docs.rs/minijinja/badge.svg)](https://docs.rs/minijinja)
MiniJinja is a powerful but minimal dependency template engine for Rust which is based on the syntax and behavior of the [Jinja2](https://jinja.palletsprojects.com/) template engine for Python. It's implemented on top of `serde` and only has it as a single required dependency. It supports [a range of features from Jinja2](https://github.com/mitsuhiko/minijinja/blob/main/COMPATIBILITY.md) including inheritance, filters and more. The goal is that it should be possible to use some templates in Rust programs without the fear of pulling in complex dependencies for a small problem. Additionally it tries not to re-invent something but stay in line with prior art to leverage an already existing ecosystem of editor integrations. ``` $ cargo tree minimal v0.1.0 (examples/minimal) └── minijinja v1.0.3 (minijinja) └── serde v1.0.144 ``` You can play with MiniJinja online [in the browser playground](https://mitsuhiko.github.io/minijinja-playground/) powered by a WASM build of MiniJinja. **Goals:** * [Well documented](https://docs.rs/minijinja), compact API * Minimal dependencies, reasonable compile times and [decent runtime performance](https://github.com/mitsuhiko/minijinja/tree/main/benchmarks#comparison-results) * [Stay close as possible](https://github.com/mitsuhiko/minijinja/blob/main/COMPATIBILITY.md) to Jinja2 * Support for [expression evaluation](https://docs.rs/minijinja/latest/minijinja/struct.Expression.html) which allows the use [as a DSL](https://github.com/mitsuhiko/minijinja/tree/main/examples/dsl) * Support for all [`serde`](https://serde.rs) compatible types * [Well tested](https://github.com/mitsuhiko/minijinja/tree/main/minijinja/tests) * Support for [dynamic runtime objects](https://docs.rs/minijinja/latest/minijinja/value/trait.Object.html) with methods and dynamic attributes * [Descriptive errors](https://github.com/mitsuhiko/minijinja/tree/main/examples/error) * [Compiles to WebAssembly](https://github.com/mitsuhiko/minijinja-playground/blob/main/src/lib.rs) * [Works with Python](https://github.com/mitsuhiko/minijinja/tree/main/minijinja-py) ## Example Template ```jinja {% extends "layout.html" %} {% block body %}

Hello {{ name }}!

{% endblock %} ``` ## API ```rust use minijinja::{Environment, context}; fn main() { let mut env = Environment::new(); env.add_template("hello.txt", "Hello {{ name }}!").unwrap(); let template = env.get_template("hello.txt").unwrap(); println!("{}", template.render(context! { name => "World" }).unwrap()); } ``` ## Getting Help If you are stuck with `MiniJinja`, have suggestions or need help, you can use the [GitHub Discussions](https://github.com/mitsuhiko/minijinja/discussions). ## Related Crates * [minijinja-autoreload](https://github.com/mitsuhiko/minijinja/tree/main/minijinja-autoreload): provides auto reloading functionality of environments * [minijinja-contrib](https://github.com/mitsuhiko/minijinja/tree/main/minijinja-contrib): provides additional utilities too specific for the core * [minijinja-stack-ref](https://github.com/mitsuhiko/minijinja/tree/main/minijinja-stack-ref): provides functionality to pass values from the stack * [minijinja-py](https://github.com/mitsuhiko/minijinja/tree/main/minijinja-py): makes MiniJinja available to Python ## Similar Projects These are related template engines for Rust: * [Askama](https://crates.io/crates/askama): Jinja inspired, type-safe, requires template precompilation. Has significant divergence from Jinja syntax in parts. * [Tera](https://crates.io/crates/tera): Jinja inspired, dynamic, has divergences from Jinja. * [TinyTemplate](https://crates.io/crates/tinytemplate): minimal footprint template engine with syntax that takes lose inspiration from Jinja and handlebars. * [Liquid](https://crates.io/crates/liquid): an implementation of Liquid templates for Rust. Liquid was inspired by Django from which Jinja took it's inspiration. ## Sponsor If you like the project and find it useful you can [become a sponsor](https://github.com/sponsors/mitsuhiko). ## License and Links - [Documentation](https://docs.rs/minijinja/) - [Discussions](https://github.com/mitsuhiko/minijinja/discussions) - [Examples](https://github.com/mitsuhiko/minijinja/tree/main/examples) - [Issue Tracker](https://github.com/mitsuhiko/minijinja/issues) - [MiniJinja Playground](https://mitsuhiko.github.io/minijinja-playground/) - License: [Apache-2.0](https://github.com/mitsuhiko/minijinja/blob/main/LICENSE) minijinja-1.0.3/doc-header.html000064400000000000000000000011261046102023000144560ustar 00000000000000 minijinja-1.0.3/src/compiler/ast.rs000064400000000000000000000417761046102023000153520ustar 00000000000000use std::ops::Deref; #[cfg(feature = "internal_debug")] use std::fmt; use crate::compiler::tokens::Span; use crate::value::{value_map_with_capacity, KeyRef, MapType, Value, ValueRepr}; /// Container for nodes with location info. /// /// This container fulfills two purposes: it adds location information /// to nodes, but it also ensures the nodes is heap allocated. The /// latter is useful to ensure that enum variants do not cause the enum /// to become too large. #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Spanned { node: Box, span: Span, } impl Spanned { /// Creates a new spanned node. pub fn new(node: T, span: Span) -> Spanned { Spanned { node: Box::new(node), span, } } /// Accesses the span. pub fn span(&self) -> Span { self.span } } impl Deref for Spanned { type Target = T; fn deref(&self) -> &Self::Target { &self.node } } #[cfg(feature = "internal_debug")] impl fmt::Debug for Spanned { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ok!(fmt::Debug::fmt(&self.node, f)); write!(f, "{:?}", self.span) } } /// A statement node. #[cfg_attr( feature = "unstable_machinery_serde", derive(serde::Serialize), serde(tag = "stmt") )] pub enum Stmt<'a> { Template(Spanned>), EmitExpr(Spanned>), EmitRaw(Spanned>), ForLoop(Spanned>), IfCond(Spanned>), WithBlock(Spanned>), Set(Spanned>), SetBlock(Spanned>), AutoEscape(Spanned>), FilterBlock(Spanned>), #[cfg(feature = "multi_template")] Block(Spanned>), #[cfg(feature = "multi_template")] Import(Spanned>), #[cfg(feature = "multi_template")] FromImport(Spanned>), #[cfg(feature = "multi_template")] Extends(Spanned>), #[cfg(feature = "multi_template")] Include(Spanned>), #[cfg(feature = "macros")] Macro(Spanned>), #[cfg(feature = "macros")] CallBlock(Spanned>), Do(Spanned>), } #[cfg(feature = "internal_debug")] impl<'a> fmt::Debug for Stmt<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Stmt::Template(s) => fmt::Debug::fmt(s, f), Stmt::EmitExpr(s) => fmt::Debug::fmt(s, f), Stmt::EmitRaw(s) => fmt::Debug::fmt(s, f), Stmt::ForLoop(s) => fmt::Debug::fmt(s, f), Stmt::IfCond(s) => fmt::Debug::fmt(s, f), Stmt::WithBlock(s) => fmt::Debug::fmt(s, f), Stmt::Set(s) => fmt::Debug::fmt(s, f), Stmt::SetBlock(s) => fmt::Debug::fmt(s, f), Stmt::AutoEscape(s) => fmt::Debug::fmt(s, f), Stmt::FilterBlock(s) => fmt::Debug::fmt(s, f), #[cfg(feature = "multi_template")] Stmt::Block(s) => fmt::Debug::fmt(s, f), #[cfg(feature = "multi_template")] Stmt::Extends(s) => fmt::Debug::fmt(s, f), #[cfg(feature = "multi_template")] Stmt::Include(s) => fmt::Debug::fmt(s, f), #[cfg(feature = "multi_template")] Stmt::Import(s) => fmt::Debug::fmt(s, f), #[cfg(feature = "multi_template")] Stmt::FromImport(s) => fmt::Debug::fmt(s, f), #[cfg(feature = "macros")] Stmt::Macro(s) => fmt::Debug::fmt(s, f), #[cfg(feature = "macros")] Stmt::CallBlock(s) => fmt::Debug::fmt(s, f), Stmt::Do(s) => fmt::Debug::fmt(s, f), } } } /// An expression node. #[allow(clippy::enum_variant_names)] #[cfg_attr( feature = "unstable_machinery_serde", derive(serde::Serialize), serde(tag = "expr") )] pub enum Expr<'a> { Var(Spanned>), Const(Spanned), Slice(Spanned>), UnaryOp(Spanned>), BinOp(Spanned>), IfExpr(Spanned>), Filter(Spanned>), Test(Spanned>), GetAttr(Spanned>), GetItem(Spanned>), Call(Spanned>), List(Spanned>), Map(Spanned>), Kwargs(Spanned>), } #[cfg(feature = "internal_debug")] impl<'a> fmt::Debug for Expr<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Expr::Var(s) => fmt::Debug::fmt(s, f), Expr::Const(s) => fmt::Debug::fmt(s, f), Expr::Slice(s) => fmt::Debug::fmt(s, f), Expr::UnaryOp(s) => fmt::Debug::fmt(s, f), Expr::BinOp(s) => fmt::Debug::fmt(s, f), Expr::IfExpr(s) => fmt::Debug::fmt(s, f), Expr::Filter(s) => fmt::Debug::fmt(s, f), Expr::Test(s) => fmt::Debug::fmt(s, f), Expr::GetAttr(s) => fmt::Debug::fmt(s, f), Expr::GetItem(s) => fmt::Debug::fmt(s, f), Expr::Call(s) => fmt::Debug::fmt(s, f), Expr::List(s) => fmt::Debug::fmt(s, f), Expr::Map(s) => fmt::Debug::fmt(s, f), Expr::Kwargs(s) => fmt::Debug::fmt(s, f), } } } impl<'a> Expr<'a> { pub fn description(&self) -> &'static str { match self { Expr::Var(_) => "variable", Expr::Const(_) => "constant", Expr::Slice(_) | Expr::UnaryOp(_) | Expr::BinOp(_) | Expr::IfExpr(_) | Expr::GetAttr(_) | Expr::GetItem(_) => "expression", Expr::Call(_) => "call", Expr::List(_) => "list literal", Expr::Map(_) => "map literal", Expr::Test(_) => "test expression", Expr::Filter(_) => "filter expression", Expr::Kwargs(_) => "keyword arguments", } } } /// Root template node. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Template<'a> { pub children: Vec>, } /// A for loop. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct ForLoop<'a> { pub target: Expr<'a>, pub iter: Expr<'a>, pub filter_expr: Option>, pub recursive: bool, pub body: Vec>, pub else_body: Vec>, } /// An if/else condition. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct IfCond<'a> { pub expr: Expr<'a>, pub true_body: Vec>, pub false_body: Vec>, } /// A with block. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct WithBlock<'a> { pub assignments: Vec<(Expr<'a>, Expr<'a>)>, pub body: Vec>, } /// A set statement. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Set<'a> { pub target: Expr<'a>, pub expr: Expr<'a>, } /// A set capture statement. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct SetBlock<'a> { pub target: Expr<'a>, pub filter: Option>, pub body: Vec>, } /// A block for inheritance elements. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg(feature = "multi_template")] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Block<'a> { pub name: &'a str, pub body: Vec>, } /// An extends block. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg(feature = "multi_template")] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Extends<'a> { pub name: Expr<'a>, } /// An include block. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg(feature = "multi_template")] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Include<'a> { pub name: Expr<'a>, pub ignore_missing: bool, } /// An auto escape control block. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct AutoEscape<'a> { pub enabled: Expr<'a>, pub body: Vec>, } /// Applies filters to a block. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct FilterBlock<'a> { pub filter: Expr<'a>, pub body: Vec>, } /// Declares a macro. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg(feature = "macros")] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Macro<'a> { pub name: &'a str, pub args: Vec>, pub defaults: Vec>, pub body: Vec>, } /// A call block #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg(feature = "macros")] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct CallBlock<'a> { pub call: Spanned>, pub macro_decl: Spanned>, } /// A call block #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Do<'a> { pub call: Spanned>, } /// A "from" import #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg(feature = "multi_template")] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct FromImport<'a> { pub expr: Expr<'a>, pub names: Vec<(Expr<'a>, Option>)>, } /// A full module import #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg(feature = "multi_template")] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Import<'a> { pub expr: Expr<'a>, pub name: Expr<'a>, } /// Outputs the expression. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct EmitExpr<'a> { pub expr: Expr<'a>, } /// Outputs raw template code. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct EmitRaw<'a> { pub raw: &'a str, } /// Looks up a variable. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Var<'a> { pub id: &'a str, } /// Loads a constant #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Const { pub value: Value, } /// Represents a slice. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Slice<'a> { pub expr: Expr<'a>, pub start: Option>, pub stop: Option>, pub step: Option>, } /// A kind of unary operator. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub enum UnaryOpKind { Not, Neg, } /// An unary operator expression. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct UnaryOp<'a> { pub op: UnaryOpKind, pub expr: Expr<'a>, } /// A kind of binary operator. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub enum BinOpKind { Eq, Ne, Lt, Lte, Gt, Gte, ScAnd, ScOr, Add, Sub, Mul, Div, FloorDiv, Rem, Pow, Concat, In, } /// A binary operator expression. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct BinOp<'a> { pub op: BinOpKind, pub left: Expr<'a>, pub right: Expr<'a>, } /// An if expression. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct IfExpr<'a> { pub test_expr: Expr<'a>, pub true_expr: Expr<'a>, pub false_expr: Option>, } /// A filter expression. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Filter<'a> { pub name: &'a str, pub expr: Option>, pub args: Vec>, } /// A test expression. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Test<'a> { pub name: &'a str, pub expr: Expr<'a>, pub args: Vec>, } /// An attribute lookup expression. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct GetAttr<'a> { pub expr: Expr<'a>, pub name: &'a str, } /// An item lookup expression. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct GetItem<'a> { pub expr: Expr<'a>, pub subscript_expr: Expr<'a>, } /// Calls something. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Call<'a> { pub expr: Expr<'a>, pub args: Vec>, } /// Creates a list of values. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct List<'a> { pub items: Vec>, } impl<'a> List<'a> { pub fn as_const(&self) -> Option { if !self.items.iter().all(|x| matches!(x, Expr::Const(_))) { return None; } let items = self.items.iter(); let sequence = items.filter_map(|expr| match expr { Expr::Const(v) => Some(v.value.clone()), _ => None, }); Some(sequence.collect()) } } /// Creates a map of kwargs #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Kwargs<'a> { pub pairs: Vec<(&'a str, Expr<'a>)>, } impl<'a> Kwargs<'a> { pub fn as_const(&self) -> Option { if !self.pairs.iter().all(|x| matches!(x.1, Expr::Const(_))) { return None; } let mut rv = value_map_with_capacity(self.pairs.len()); for (key, value) in &self.pairs { if let Expr::Const(value) = value { rv.insert(KeyRef::Value(Value::from(*key)), value.value.clone()); } } Some(Value(ValueRepr::Map(rv.into(), MapType::Kwargs))) } } /// Creates a map of values. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Map<'a> { pub keys: Vec>, pub values: Vec>, } impl<'a> Map<'a> { pub fn as_const(&self) -> Option { if !self.keys.iter().all(|x| matches!(x, Expr::Const(_))) || !self.values.iter().all(|x| matches!(x, Expr::Const(_))) { return None; } let mut rv = value_map_with_capacity(self.keys.len()); for (key, value) in self.keys.iter().zip(self.values.iter()) { if let (Expr::Const(maybe_key), Expr::Const(value)) = (key, value) { rv.insert(KeyRef::Value(maybe_key.value.clone()), value.value.clone()); } } Some(Value(ValueRepr::Map(rv.into(), MapType::Normal))) } } /// Defines the specific type of call. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub enum CallType<'ast, 'source> { Function(&'source str), Method(&'ast Expr<'source>, &'source str), #[cfg(feature = "multi_template")] Block(&'source str), Object(&'ast Expr<'source>), } impl<'a> Call<'a> { /// Try to isolate a method call. /// /// name + call and attribute lookup + call are really method /// calls which are easier to handle for the compiler as a separate /// thing. pub fn identify_call(&self) -> CallType<'_, 'a> { match self.expr { Expr::Var(ref var) => CallType::Function(var.id), Expr::GetAttr(ref attr) => { #[cfg(feature = "multi_template")] { if let Expr::Var(ref var) = attr.expr { if var.id == "self" { return CallType::Block(attr.name); } } } CallType::Method(&attr.expr, attr.name) } _ => CallType::Object(&self.expr), } } } minijinja-1.0.3/src/compiler/codegen.rs000064400000000000000000000735501046102023000161620ustar 00000000000000use std::collections::BTreeMap; use crate::compiler::ast; use crate::compiler::instructions::{ Instruction, Instructions, LocalId, LOOP_FLAG_RECURSIVE, LOOP_FLAG_WITH_LOOP_VAR, MAX_LOCALS, }; use crate::compiler::tokens::Span; use crate::output::CaptureMode; use crate::value::Value; #[cfg(test)] use similar_asserts::assert_eq; #[cfg(feature = "macros")] type Caller<'source> = ast::Spanned>; #[cfg(not(feature = "macros"))] type Caller<'source> = std::marker::PhantomData<&'source ()>; /// For the first `MAX_LOCALS` filters/tests, an ID is returned for faster lookups from the stack. fn get_local_id<'source>(ids: &mut BTreeMap<&'source str, LocalId>, name: &'source str) -> LocalId { if let Some(id) = ids.get(name) { *id } else if ids.len() >= MAX_LOCALS { !0 } else { let next_id = ids.len() as LocalId; ids.insert(name, next_id); next_id } } /// Represents an open block of code that does not yet have updated /// jump targets. enum PendingBlock { Branch(usize), Loop(usize), ScBool(Vec), } /// Provides a convenient interface to creating instructions for the VM. pub struct CodeGenerator<'source> { instructions: Instructions<'source>, blocks: BTreeMap<&'source str, Instructions<'source>>, pending_block: Vec, current_line: u32, span_stack: Vec, filter_local_ids: BTreeMap<&'source str, LocalId>, test_local_ids: BTreeMap<&'source str, LocalId>, raw_template_bytes: usize, } impl<'source> CodeGenerator<'source> { /// Creates a new code generator. pub fn new(file: &'source str, source: &'source str) -> CodeGenerator<'source> { CodeGenerator { instructions: Instructions::new(file, source), blocks: BTreeMap::new(), pending_block: Vec::with_capacity(32), current_line: 0, span_stack: Vec::with_capacity(32), filter_local_ids: BTreeMap::new(), test_local_ids: BTreeMap::new(), raw_template_bytes: 0, } } /// Sets the current location's line. pub fn set_line(&mut self, lineno: u32) { self.current_line = lineno; } /// Sets line from span. pub fn set_line_from_span(&mut self, span: Span) { self.set_line(span.start_line); } /// Pushes a span to the stack pub fn push_span(&mut self, span: Span) { self.span_stack.push(span); self.set_line_from_span(span); } /// Pops a span from the stack. pub fn pop_span(&mut self) { self.span_stack.pop(); } /// Add a simple instruction with the current location. pub fn add(&mut self, instr: Instruction<'source>) -> usize { if let Some(span) = self.span_stack.last() { if span.start_line == self.current_line { return self.instructions.add_with_span(instr, *span); } } self.instructions.add_with_line(instr, self.current_line) } /// Add a simple instruction with other location. pub fn add_with_span(&mut self, instr: Instruction<'source>, span: Span) -> usize { self.instructions.add_with_span(instr, span) } /// Returns the next instruction index. pub fn next_instruction(&self) -> usize { self.instructions.len() } /// Creates a sub generator. #[cfg(feature = "multi_template")] fn new_subgenerator(&self) -> CodeGenerator<'source> { let mut sub = CodeGenerator::new(self.instructions.name(), self.instructions.source()); sub.current_line = self.current_line; sub.span_stack = self.span_stack.last().copied().into_iter().collect(); sub } /// Finishes a sub generator and syncs it back. #[cfg(feature = "multi_template")] fn finish_subgenerator(&mut self, sub: CodeGenerator<'source>) -> Instructions<'source> { self.current_line = sub.current_line; let (instructions, blocks) = sub.finish(); self.blocks.extend(blocks.into_iter()); instructions } /// Starts a for loop pub fn start_for_loop(&mut self, with_loop_var: bool, recursive: bool) { let mut flags = 0; if with_loop_var { flags |= LOOP_FLAG_WITH_LOOP_VAR; } if recursive { flags |= LOOP_FLAG_RECURSIVE; } self.add(Instruction::PushLoop(flags)); let iter_instr = self.add(Instruction::Iterate(!0)); self.pending_block.push(PendingBlock::Loop(iter_instr)); } /// Ends the open for loop pub fn end_for_loop(&mut self, push_did_not_iterate: bool) { match self.pending_block.pop() { Some(PendingBlock::Loop(iter_instr)) => { self.add(Instruction::Jump(iter_instr)); let loop_end = self.next_instruction(); if push_did_not_iterate { self.add(Instruction::PushDidNotIterate); }; self.add(Instruction::PopFrame); if let Some(Instruction::Iterate(ref mut jump_target)) = self.instructions.get_mut(iter_instr) { *jump_target = loop_end; } else { unreachable!(); } } _ => unreachable!(), } } /// Begins an if conditional pub fn start_if(&mut self) { let jump_instr = self.add(Instruction::JumpIfFalse(!0)); self.pending_block.push(PendingBlock::Branch(jump_instr)); } /// Begins an else conditional pub fn start_else(&mut self) { let jump_instr = self.add(Instruction::Jump(!0)); self.end_condition(jump_instr + 1); self.pending_block.push(PendingBlock::Branch(jump_instr)); } /// Closes the current if block. pub fn end_if(&mut self) { self.end_condition(self.next_instruction()); } /// Starts a short cirquited bool block. pub fn start_sc_bool(&mut self) { self.pending_block.push(PendingBlock::ScBool(vec![])); } /// Emits a short circuited bool operator. pub fn sc_bool(&mut self, and: bool) { if let Some(PendingBlock::ScBool(ref mut instructions)) = self.pending_block.last_mut() { instructions.push(self.instructions.add(if and { Instruction::JumpIfFalseOrPop(!0) } else { Instruction::JumpIfTrueOrPop(!0) })); } else { unreachable!(); } } /// Ends a short circuited bool block. pub fn end_sc_bool(&mut self) { let end = self.next_instruction(); if let Some(PendingBlock::ScBool(instructions)) = self.pending_block.pop() { for instr in instructions { match self.instructions.get_mut(instr) { Some(Instruction::JumpIfFalseOrPop(ref mut target)) | Some(Instruction::JumpIfTrueOrPop(ref mut target)) => { *target = end; } _ => unreachable!(), } } } } fn end_condition(&mut self, jump_instr: usize) { match self.pending_block.pop() { Some(PendingBlock::Branch(instr)) => match self.instructions.get_mut(instr) { Some(Instruction::JumpIfFalse(ref mut target)) | Some(Instruction::Jump(ref mut target)) => { *target = jump_instr; } _ => {} }, _ => unreachable!(), } } /// Compiles a statement. pub fn compile_stmt(&mut self, stmt: &ast::Stmt<'source>) { match stmt { ast::Stmt::Template(t) => { self.set_line_from_span(t.span()); for node in &t.children { self.compile_stmt(node); } } ast::Stmt::EmitExpr(expr) => { self.compile_emit_expr(expr); } ast::Stmt::EmitRaw(raw) => { self.set_line_from_span(raw.span()); self.add(Instruction::EmitRaw(raw.raw)); self.raw_template_bytes += raw.raw.len(); } ast::Stmt::ForLoop(for_loop) => { self.compile_for_loop(for_loop); } ast::Stmt::IfCond(if_cond) => { self.compile_if_stmt(if_cond); } ast::Stmt::WithBlock(with_block) => { self.set_line_from_span(with_block.span()); self.add(Instruction::PushWith); for (target, expr) in &with_block.assignments { self.compile_expr(expr); self.compile_assignment(target); } for node in &with_block.body { self.compile_stmt(node); } self.add(Instruction::PopFrame); } ast::Stmt::Set(set) => { self.set_line_from_span(set.span()); self.compile_expr(&set.expr); self.compile_assignment(&set.target); } ast::Stmt::SetBlock(set_block) => { self.set_line_from_span(set_block.span()); self.add(Instruction::BeginCapture(CaptureMode::Capture)); for node in &set_block.body { self.compile_stmt(node); } self.add(Instruction::EndCapture); if let Some(ref filter) = set_block.filter { self.compile_expr(filter); } self.compile_assignment(&set_block.target); } ast::Stmt::AutoEscape(auto_escape) => { self.set_line_from_span(auto_escape.span()); self.compile_expr(&auto_escape.enabled); self.add(Instruction::PushAutoEscape); for node in &auto_escape.body { self.compile_stmt(node); } self.add(Instruction::PopAutoEscape); } ast::Stmt::FilterBlock(filter_block) => { self.set_line_from_span(filter_block.span()); self.add(Instruction::BeginCapture(CaptureMode::Capture)); for node in &filter_block.body { self.compile_stmt(node); } self.add(Instruction::EndCapture); self.compile_expr(&filter_block.filter); self.add(Instruction::Emit); } #[cfg(feature = "multi_template")] ast::Stmt::Block(block) => { self.compile_block(block); } #[cfg(feature = "multi_template")] ast::Stmt::Import(import) => { self.add(Instruction::BeginCapture(CaptureMode::Discard)); self.add(Instruction::PushWith); self.compile_expr(&import.expr); self.add_with_span(Instruction::Include(false), import.span()); self.add(Instruction::ExportLocals); self.add(Instruction::PopFrame); self.compile_assignment(&import.name); self.add(Instruction::EndCapture); } #[cfg(feature = "multi_template")] ast::Stmt::FromImport(from_import) => { self.add(Instruction::BeginCapture(CaptureMode::Discard)); self.add(Instruction::PushWith); self.compile_expr(&from_import.expr); self.add_with_span(Instruction::Include(false), from_import.span()); for (name, _) in &from_import.names { self.compile_expr(name); } self.add(Instruction::PopFrame); for (name, alias) in from_import.names.iter().rev() { self.compile_assignment(alias.as_ref().unwrap_or(name)); } self.add(Instruction::EndCapture); } #[cfg(feature = "multi_template")] ast::Stmt::Extends(extends) => { self.set_line_from_span(extends.span()); self.compile_expr(&extends.name); self.add_with_span(Instruction::LoadBlocks, extends.span()); } #[cfg(feature = "multi_template")] ast::Stmt::Include(include) => { self.set_line_from_span(include.span()); self.compile_expr(&include.name); self.add_with_span(Instruction::Include(include.ignore_missing), include.span()); } #[cfg(feature = "macros")] ast::Stmt::Macro(macro_decl) => { self.compile_macro(macro_decl); } #[cfg(feature = "macros")] ast::Stmt::CallBlock(call_block) => { self.compile_call_block(call_block); } ast::Stmt::Do(do_tag) => { self.compile_do(do_tag); } } } #[cfg(feature = "multi_template")] fn compile_block(&mut self, block: &ast::Spanned>) { self.set_line_from_span(block.span()); let mut sub = self.new_subgenerator(); for node in &block.body { sub.compile_stmt(node); } let instructions = self.finish_subgenerator(sub); self.blocks.insert(block.name, instructions); self.add(Instruction::CallBlock(block.name)); } #[cfg(feature = "macros")] fn compile_macro_expression(&mut self, macro_decl: &ast::Spanned>) { use crate::compiler::instructions::MACRO_CALLER; use crate::value::ValueRepr; self.set_line_from_span(macro_decl.span()); let instr = self.add(Instruction::Jump(!0)); let mut defaults_iter = macro_decl.defaults.iter().rev(); for arg in macro_decl.args.iter().rev() { if let Some(default) = defaults_iter.next() { self.add(Instruction::DupTop); self.add(Instruction::IsUndefined); self.start_if(); self.add(Instruction::DiscardTop); self.compile_expr(default); self.end_if(); } self.compile_assignment(arg); } for node in ¯o_decl.body { self.compile_stmt(node); } self.add(Instruction::Return); let mut undeclared = crate::compiler::meta::find_macro_closure(macro_decl); let caller_reference = undeclared.remove("caller"); let macro_instr = self.next_instruction(); for name in &undeclared { self.add(Instruction::Enclose(name)); } self.add(Instruction::GetClosure); self.add(Instruction::LoadConst(Value::from(ValueRepr::Seq( macro_decl .args .iter() .map(|x| match x { ast::Expr::Var(var) => Value::from(var.id), _ => unreachable!(), }) .collect::>() .into(), )))); let mut flags = 0; if caller_reference { flags |= MACRO_CALLER; } self.add(Instruction::BuildMacro(macro_decl.name, instr + 1, flags)); if let Some(Instruction::Jump(ref mut target)) = self.instructions.get_mut(instr) { *target = macro_instr; } else { unreachable!(); } } #[cfg(feature = "macros")] fn compile_macro(&mut self, macro_decl: &ast::Spanned>) { self.compile_macro_expression(macro_decl); self.add(Instruction::StoreLocal(macro_decl.name)); } #[cfg(feature = "macros")] fn compile_call_block(&mut self, call_block: &ast::Spanned>) { self.compile_call(&call_block.call, Some(&call_block.macro_decl)); self.add(Instruction::Emit); } fn compile_do(&mut self, do_tag: &ast::Spanned>) { self.compile_call(&do_tag.call, None); } fn compile_if_stmt(&mut self, if_cond: &ast::Spanned>) { self.set_line_from_span(if_cond.span()); self.compile_expr(&if_cond.expr); self.start_if(); for node in &if_cond.true_body { self.compile_stmt(node); } if !if_cond.false_body.is_empty() { self.start_else(); for node in &if_cond.false_body { self.compile_stmt(node); } } self.end_if(); } fn compile_emit_expr(&mut self, expr: &ast::Spanned>) { self.set_line_from_span(expr.span()); if let ast::Expr::Call(call) = &expr.expr { match call.identify_call() { ast::CallType::Function(name) => { if name == "super" && call.args.is_empty() { self.add_with_span(Instruction::FastSuper, call.span()); return; } else if name == "loop" && call.args.len() == 1 { self.compile_expr(&call.args[0]); self.add(Instruction::FastRecurse); return; } } #[cfg(feature = "multi_template")] ast::CallType::Block(name) => { self.add(Instruction::CallBlock(name)); return; } _ => {} } } self.compile_expr(&expr.expr); self.add(Instruction::Emit); } fn compile_for_loop(&mut self, for_loop: &ast::Spanned>) { self.set_line_from_span(for_loop.span()); if let Some(ref filter_expr) = for_loop.filter_expr { // filter expressions work like a nested for loop without // the special loop variable that append into a new list // just outside of the loop. self.add(Instruction::BuildList(0)); self.compile_expr(&for_loop.iter); self.start_for_loop(false, false); self.add(Instruction::DupTop); self.compile_assignment(&for_loop.target); self.compile_expr(filter_expr); self.start_if(); self.add(Instruction::ListAppend); self.start_else(); self.add(Instruction::DiscardTop); self.end_if(); self.end_for_loop(false); } else { self.compile_expr(&for_loop.iter); } self.start_for_loop(true, for_loop.recursive); self.compile_assignment(&for_loop.target); for node in &for_loop.body { self.compile_stmt(node); } self.end_for_loop(!for_loop.else_body.is_empty()); if !for_loop.else_body.is_empty() { self.start_if(); for node in &for_loop.else_body { self.compile_stmt(node); } self.end_if(); }; } /// Compiles an assignment expression. pub fn compile_assignment(&mut self, expr: &ast::Expr<'source>) { match expr { ast::Expr::Var(var) => { self.add(Instruction::StoreLocal(var.id)); } ast::Expr::List(list) => { self.push_span(list.span()); self.add(Instruction::UnpackList(list.items.len())); for expr in &list.items { self.compile_assignment(expr); } self.pop_span(); } _ => unreachable!(), } } /// Compiles an expression. pub fn compile_expr(&mut self, expr: &ast::Expr<'source>) { match expr { ast::Expr::Var(v) => { self.set_line_from_span(v.span()); self.add(Instruction::Lookup(v.id)); } ast::Expr::Const(v) => { self.set_line_from_span(v.span()); self.add(Instruction::LoadConst(v.value.clone())); } ast::Expr::Slice(s) => { self.push_span(s.span()); self.compile_expr(&s.expr); if let Some(ref start) = s.start { self.compile_expr(start); } else { self.add(Instruction::LoadConst(Value::from(0))); } if let Some(ref stop) = s.stop { self.compile_expr(stop); } else { self.add(Instruction::LoadConst(Value::from(()))); } if let Some(ref step) = s.step { self.compile_expr(step); } else { self.add(Instruction::LoadConst(Value::from(1))); } self.add(Instruction::Slice); self.pop_span(); } ast::Expr::UnaryOp(c) => { self.set_line_from_span(c.span()); self.compile_expr(&c.expr); match c.op { ast::UnaryOpKind::Not => self.add(Instruction::Not), ast::UnaryOpKind::Neg => self.add_with_span(Instruction::Neg, c.span()), }; } ast::Expr::BinOp(c) => { self.compile_bin_op(c); } ast::Expr::IfExpr(i) => { self.set_line_from_span(i.span()); self.compile_expr(&i.test_expr); self.start_if(); self.compile_expr(&i.true_expr); self.start_else(); if let Some(ref false_expr) = i.false_expr { self.compile_expr(false_expr); } else { self.add(Instruction::LoadConst(Value::UNDEFINED)); } self.end_if(); } ast::Expr::Filter(f) => { self.push_span(f.span()); if let Some(ref expr) = f.expr { self.compile_expr(expr); } for arg in &f.args { self.compile_expr(arg); } let local_id = get_local_id(&mut self.filter_local_ids, f.name); self.add(Instruction::ApplyFilter(f.name, f.args.len() + 1, local_id)); self.pop_span(); } ast::Expr::Test(f) => { self.push_span(f.span()); self.compile_expr(&f.expr); for arg in &f.args { self.compile_expr(arg); } let local_id = get_local_id(&mut self.test_local_ids, f.name); self.add(Instruction::PerformTest(f.name, f.args.len() + 1, local_id)); self.pop_span(); } ast::Expr::GetAttr(g) => { self.push_span(g.span()); self.compile_expr(&g.expr); self.add(Instruction::GetAttr(g.name)); self.pop_span(); } ast::Expr::GetItem(g) => { self.push_span(g.span()); self.compile_expr(&g.expr); self.compile_expr(&g.subscript_expr); self.add(Instruction::GetItem); self.pop_span(); } ast::Expr::Call(c) => { self.compile_call(c, None); } ast::Expr::List(l) => { if let Some(val) = l.as_const() { self.add(Instruction::LoadConst(val)); } else { self.set_line_from_span(l.span()); for item in &l.items { self.compile_expr(item); } self.add(Instruction::BuildList(l.items.len())); } } ast::Expr::Map(m) => { if let Some(val) = m.as_const() { self.add(Instruction::LoadConst(val)); } else { self.set_line_from_span(m.span()); assert_eq!(m.keys.len(), m.values.len()); for (key, value) in m.keys.iter().zip(m.values.iter()) { self.compile_expr(key); self.compile_expr(value); } self.add(Instruction::BuildMap(m.keys.len())); } } ast::Expr::Kwargs(m) => { if let Some(val) = m.as_const() { self.add(Instruction::LoadConst(val)); } else { self.set_line_from_span(m.span()); for (key, value) in &m.pairs { self.add(Instruction::LoadConst(Value::from(*key))); self.compile_expr(value); } self.add(Instruction::BuildKwargs(m.pairs.len())); } } } } fn compile_call( &mut self, c: &ast::Spanned>, caller: Option<&Caller<'source>>, ) { self.push_span(c.span()); match c.identify_call() { ast::CallType::Function(name) => { let arg_count = self.compile_call_args(&c.args, caller); self.add(Instruction::CallFunction(name, arg_count)); } #[cfg(feature = "multi_template")] ast::CallType::Block(name) => { self.add(Instruction::BeginCapture(CaptureMode::Capture)); self.add(Instruction::CallBlock(name)); self.add(Instruction::EndCapture); } ast::CallType::Method(expr, name) => { self.compile_expr(expr); let arg_count = self.compile_call_args(&c.args, caller); self.add(Instruction::CallMethod(name, arg_count + 1)); } ast::CallType::Object(expr) => { self.compile_expr(expr); let arg_count = self.compile_call_args(&c.args, caller); self.add(Instruction::CallObject(arg_count + 1)); } }; self.pop_span(); } fn compile_call_args( &mut self, args: &[ast::Expr<'source>], caller: Option<&Caller<'source>>, ) -> usize { match caller { // we can conditionally compile the caller part here since this will // nicely call through for non macro builds #[cfg(feature = "macros")] Some(caller) => self.compile_call_args_with_caller(args, caller), _ => { for arg in args { self.compile_expr(arg); } args.len() } } } #[cfg(feature = "macros")] fn compile_call_args_with_caller( &mut self, args: &[ast::Expr<'source>], caller: &Caller<'source>, ) -> usize { let mut injected_caller = false; // try to add the caller to already existing keyword arguments. for arg in args { if let ast::Expr::Kwargs(ref m) = arg { self.set_line_from_span(m.span()); for (key, value) in &m.pairs { self.add(Instruction::LoadConst(Value::from(*key))); self.compile_expr(value); } self.add(Instruction::LoadConst(Value::from("caller"))); self.compile_macro_expression(caller); self.add(Instruction::BuildKwargs(m.pairs.len() + 1)); injected_caller = true; } else { self.compile_expr(arg); } } // if there are no keyword args so far, create a new kwargs object // and add caller to that. if !injected_caller { self.add(Instruction::LoadConst(Value::from("caller"))); self.compile_macro_expression(caller); self.add(Instruction::BuildKwargs(1)); args.len() + 1 } else { args.len() } } fn compile_bin_op(&mut self, c: &ast::Spanned>) { self.push_span(c.span()); let instr = match c.op { ast::BinOpKind::Eq => Instruction::Eq, ast::BinOpKind::Ne => Instruction::Ne, ast::BinOpKind::Lt => Instruction::Lt, ast::BinOpKind::Lte => Instruction::Lte, ast::BinOpKind::Gt => Instruction::Gt, ast::BinOpKind::Gte => Instruction::Gte, ast::BinOpKind::ScAnd | ast::BinOpKind::ScOr => { self.start_sc_bool(); self.compile_expr(&c.left); self.sc_bool(matches!(c.op, ast::BinOpKind::ScAnd)); self.compile_expr(&c.right); self.end_sc_bool(); self.pop_span(); return; } ast::BinOpKind::Add => Instruction::Add, ast::BinOpKind::Sub => Instruction::Sub, ast::BinOpKind::Mul => Instruction::Mul, ast::BinOpKind::Div => Instruction::Div, ast::BinOpKind::FloorDiv => Instruction::IntDiv, ast::BinOpKind::Rem => Instruction::Rem, ast::BinOpKind::Pow => Instruction::Pow, ast::BinOpKind::Concat => Instruction::StringConcat, ast::BinOpKind::In => Instruction::In, }; self.compile_expr(&c.left); self.compile_expr(&c.right); self.add(instr); self.pop_span(); } /// Returns the size hint for buffers. /// /// This is a proposal for the initial buffer size when rendering directly to a string. pub fn buffer_size_hint(&self) -> usize { // for now the assumption is made that twice the bytes of template code without // control structures, rounded up to the next power of two is a good default. The // round to the next power of two is chosen because the underlying vector backing // strings prefers powers of two. (self.raw_template_bytes * 2).next_power_of_two() } /// Converts the compiler into the instructions. pub fn finish( self, ) -> ( Instructions<'source>, BTreeMap<&'source str, Instructions<'source>>, ) { assert!(self.pending_block.is_empty()); (self.instructions, self.blocks) } } minijinja-1.0.3/src/compiler/instructions.rs000064400000000000000000000273511046102023000173200ustar 00000000000000#[cfg(feature = "internal_debug")] use std::fmt; use crate::compiler::tokens::Span; use crate::output::CaptureMode; use crate::value::Value; /// This loop has the loop var. pub const LOOP_FLAG_WITH_LOOP_VAR: u8 = 1; /// This loop is recursive. pub const LOOP_FLAG_RECURSIVE: u8 = 2; /// This macro uses the caller var. #[cfg(feature = "macros")] pub const MACRO_CALLER: u8 = 2; /// Rust type to represent locals. pub type LocalId = u8; /// The maximum number of filters/tests that can be cached. pub const MAX_LOCALS: usize = 50; /// Represents an instruction for the VM. #[cfg_attr(feature = "internal_debug", derive(Debug))] #[cfg_attr( feature = "unstable_machinery_serde", derive(serde::Serialize), serde(tag = "op", content = "arg") )] #[derive(Clone)] pub enum Instruction<'source> { /// Emits raw source EmitRaw(&'source str), /// Stores a variable (only possible in for loops) StoreLocal(&'source str), /// Load a variable, Lookup(&'source str), /// Looks up an attribute. GetAttr(&'source str), /// Looks up an item. GetItem, /// Performs a slice operation. Slice, /// Loads a constant value. LoadConst(Value), /// Builds a map of the last n pairs on the stack. BuildMap(usize), /// Builds a kwargs map of the last n pairs on the stack. BuildKwargs(usize), /// Builds a list of the last n pairs on the stack. BuildList(usize), /// Unpacks a list into N stack items. UnpackList(usize), /// Appends to the list. ListAppend, /// Add the top two values Add, /// Subtract the top two values Sub, /// Multiply the top two values Mul, /// Divide the top two values Div, /// Integer divide the top two values as "integer". /// /// Note that in MiniJinja this currently uses an euclidean /// division to match the rem implementation. In Python this /// instead uses a flooring division and a flooring remainder. IntDiv, /// Calculate the remainder the top two values Rem, /// x to the power of y. Pow, /// Negates the value. Neg, /// `=` operator Eq, /// `!=` operator Ne, /// `>` operator Gt, /// `>=` operator Gte, /// `<` operator Lt, /// `<=` operator Lte, /// Unary not Not, /// String concatenation operator StringConcat, /// Performs a containment check In, /// Apply a filter. ApplyFilter(&'source str, usize, LocalId), /// Perform a filter. PerformTest(&'source str, usize, LocalId), /// Emit the stack top as output Emit, /// Starts a loop /// /// The argument are loop flags. PushLoop(u8), /// Starts a with block. PushWith, /// Does a single loop iteration /// /// The argument is the jump target for when the loop /// ends and must point to a `PopFrame` instruction. Iterate(usize), /// Push a bool that indicates that the loop iterated. PushDidNotIterate, /// Pops the topmost frame PopFrame, /// Jump to a specific instruction Jump(usize), /// Jump if the stack top evaluates to false JumpIfFalse(usize), /// Jump if the stack top evaluates to false or pops the value JumpIfFalseOrPop(usize), /// Jump if the stack top evaluates to true or pops the value JumpIfTrueOrPop(usize), /// Sets the auto escape flag to the current value. PushAutoEscape, /// Resets the auto escape flag to the previous value. PopAutoEscape, /// Begins capturing of output (false) or discard (true). BeginCapture(CaptureMode), /// Ends capturing of output. EndCapture, /// Calls a global function CallFunction(&'source str, usize), /// Calls a method CallMethod(&'source str, usize), /// Calls an object CallObject(usize), /// Duplicates the top item DupTop, /// Discards the top item DiscardTop, /// A fast super instruction without intermediate capturing. FastSuper, /// A fast loop recurse instruction without intermediate capturing. FastRecurse, /// Call into a block. #[cfg(feature = "multi_template")] CallBlock(&'source str), /// Loads block from a template with name on stack ("extends") #[cfg(feature = "multi_template")] LoadBlocks, /// Includes another template. #[cfg(feature = "multi_template")] Include(bool), /// Builds a module #[cfg(feature = "multi_template")] ExportLocals, /// Builds a macro on the stack. #[cfg(feature = "macros")] BuildMacro(&'source str, usize, u8), /// Breaks from the interpreter loop (exists a function) #[cfg(feature = "macros")] Return, /// True if the value is undefined #[cfg(feature = "macros")] IsUndefined, /// Encloses a variable. #[cfg(feature = "macros")] Enclose(&'source str), /// Returns the closure of this context level. #[cfg(feature = "macros")] GetClosure, } #[derive(Copy, Clone)] struct LineInfo { first_instruction: u32, line: u32, } #[cfg(feature = "debug")] #[derive(Copy, Clone)] struct SpanInfo { first_instruction: u32, span: Option, } /// Wrapper around instructions to help with location management. pub struct Instructions<'source> { pub(crate) instructions: Vec>, line_infos: Vec, #[cfg(feature = "debug")] span_infos: Vec, name: &'source str, source: &'source str, } pub(crate) static EMPTY_INSTRUCTIONS: Instructions<'static> = Instructions { instructions: Vec::new(), line_infos: Vec::new(), #[cfg(feature = "debug")] span_infos: Vec::new(), name: "", source: "", }; impl<'source> Instructions<'source> { /// Creates a new instructions object. pub fn new(name: &'source str, source: &'source str) -> Instructions<'source> { Instructions { instructions: Vec::with_capacity(128), line_infos: Vec::with_capacity(128), #[cfg(feature = "debug")] span_infos: Vec::with_capacity(128), name, source, } } /// Returns the name of the template. pub fn name(&self) -> &'source str { self.name } /// Returns the source reference. pub fn source(&self) -> &'source str { self.source } /// Returns an instruction by index #[inline(always)] pub fn get(&self, idx: usize) -> Option<&Instruction<'source>> { self.instructions.get(idx) } /// Returns an instruction by index mutably pub fn get_mut(&mut self, idx: usize) -> Option<&mut Instruction<'source>> { self.instructions.get_mut(idx) } /// Adds a new instruction pub fn add(&mut self, instr: Instruction<'source>) -> usize { let rv = self.instructions.len(); self.instructions.push(instr); rv } fn add_line_record(&mut self, instr: usize, line: u32) { let same_loc = self .line_infos .last() .map_or(false, |last_loc| last_loc.line == line); if !same_loc { self.line_infos.push(LineInfo { first_instruction: instr as u32, line, }); } } /// Adds a new instruction with line number. pub fn add_with_line(&mut self, instr: Instruction<'source>, line: u32) -> usize { let rv = self.add(instr); self.add_line_record(rv, line); // if we follow up to a valid span with no more span, clear it out #[cfg(feature = "debug")] { if self.span_infos.last().map_or(false, |x| x.span.is_some()) { self.span_infos.push(SpanInfo { first_instruction: rv as u32, span: None, }); } } rv } /// Adds a new instruction with span. pub fn add_with_span(&mut self, instr: Instruction<'source>, span: Span) -> usize { let rv = self.add(instr); #[cfg(feature = "debug")] { let same_loc = self .span_infos .last() .map_or(false, |last_loc| last_loc.span == Some(span)); if !same_loc { self.span_infos.push(SpanInfo { first_instruction: rv as u32, span: Some(span), }); } } self.add_line_record(rv, span.start_line); rv } /// Looks up the line for an instruction pub fn get_line(&self, idx: usize) -> Option { let loc = match self .line_infos .binary_search_by_key(&idx, |x| x.first_instruction as usize) { Ok(idx) => &self.line_infos[idx], Err(0) => return None, Err(idx) => &self.line_infos[idx - 1], }; Some(loc.line as usize) } /// Looks up a span for an instruction. pub fn get_span(&self, idx: usize) -> Option { #[cfg(feature = "debug")] { let loc = match self .span_infos .binary_search_by_key(&idx, |x| x.first_instruction as usize) { Ok(idx) => &self.span_infos[idx], Err(0) => return None, Err(idx) => &self.span_infos[idx - 1], }; loc.span } #[cfg(not(feature = "debug"))] { let _ = idx; None } } /// Returns a list of all names referenced in the current block backwards /// from the given pc. #[cfg(feature = "debug")] pub fn get_referenced_names(&self, idx: usize) -> Vec<&'source str> { let mut rv = Vec::new(); // make sure we don't crash on empty instructions if self.instructions.is_empty() { return rv; } let idx = idx.min(self.instructions.len() - 1); for instr in self.instructions[..=idx].iter().rev() { let name = match instr { Instruction::Lookup(name) | Instruction::StoreLocal(name) | Instruction::CallFunction(name, _) => *name, Instruction::PushLoop(flags) if flags & LOOP_FLAG_WITH_LOOP_VAR != 0 => "loop", Instruction::PushLoop(_) | Instruction::PushWith => break, _ => continue, }; if !rv.contains(&name) { rv.push(name); } } rv } /// Returns the number of instructions pub fn len(&self) -> usize { self.instructions.len() } /// Do we have any instructions? #[allow(unused)] pub fn is_empty(&self) -> bool { self.instructions.is_empty() } } #[cfg(feature = "internal_debug")] impl<'source> fmt::Debug for Instructions<'source> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { struct InstructionWrapper<'a>(usize, &'a Instruction<'a>, Option); impl<'a> fmt::Debug for InstructionWrapper<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ok!(write!(f, "{:>05} | {:?}", self.0, self.1,)); if let Some(line) = self.2 { ok!(write!(f, " [line {line}]")); } Ok(()) } } let mut list = f.debug_list(); let mut last_line = None; for (idx, instr) in self.instructions.iter().enumerate() { let line = self.get_line(idx); list.entry(&InstructionWrapper( idx, instr, if line != last_line { line } else { None }, )); last_line = line; } list.finish() } } #[test] #[cfg(target_pointer_width = "64")] fn test_sizes() { assert_eq!(std::mem::size_of::(), 32); } minijinja-1.0.3/src/compiler/lexer.rs000064400000000000000000000602451046102023000156720ustar 00000000000000use crate::compiler::tokens::{Span, Token}; use crate::error::{Error, ErrorKind}; use crate::utils::{memchr, memstr, unescape}; #[cfg(feature = "custom_syntax")] pub use crate::custom_syntax::SyntaxConfig; /// Non configurable syntax config #[cfg(not(feature = "custom_syntax"))] #[derive(Debug, Clone, Default)] pub struct SyntaxConfig; enum LexerState { Template, InVariable, InBlock, } /// Utility enum that defines a marker. #[derive(Debug, Copy, Clone)] pub enum StartMarker { Variable, Block, Comment, } struct TokenizerState<'s> { stack: Vec, rest: &'s str, failed: bool, current_line: u32, current_col: u32, current_offset: u32, } fn find_start_marker_memchr(a: &str) -> Option<(usize, bool)> { let bytes = a.as_bytes(); let mut offset = 0; loop { let idx = match memchr(&bytes[offset..], b'{') { Some(idx) => idx, None => return None, }; if let Some(b'{' | b'%' | b'#') = bytes.get(offset + idx + 1).copied() { return Some(( offset + idx, bytes.get(offset + idx + 2).copied() == Some(b'-'), )); } offset += idx + 1; } } #[cfg(feature = "custom_syntax")] fn find_start_marker(a: &str, syntax_config: &SyntaxConfig) -> Option<(usize, bool)> { // If we have a custom delimiter we need to use the aho-corasick // otherwise we can use internal memchr. match syntax_config.aho_corasick { Some(ref ac) => { let bytes = a.as_bytes(); ac.find(bytes).map(|m| { ( m.start(), bytes.get(m.start() + m.len()).copied() == Some(b'-'), ) }) } None => find_start_marker_memchr(a), } } #[cfg(not(feature = "custom_syntax"))] fn find_start_marker(a: &str, _syntax_config: &SyntaxConfig) -> Option<(usize, bool)> { find_start_marker_memchr(a) } fn match_start_marker(rest: &str, syntax_config: &SyntaxConfig) -> Option<(StartMarker, usize)> { #[cfg(not(feature = "custom_syntax"))] { let _ = syntax_config; match_start_marker_default(rest) } #[cfg(feature = "custom_syntax")] { if syntax_config.aho_corasick.is_none() { return match_start_marker_default(rest); } for delimiter in syntax_config.start_delimiters_order { let marker = match delimiter { StartMarker::Variable => &syntax_config.syntax.variable_start as &str, StartMarker::Block => &syntax_config.syntax.block_start as &str, StartMarker::Comment => &syntax_config.syntax.comment_start as &str, }; if rest.get(..marker.len()) == Some(marker) { return Some((delimiter, marker.len())); } } None } } fn match_start_marker_default(rest: &str) -> Option<(StartMarker, usize)> { match rest.get(..2) { Some("{{") => Some((StartMarker::Variable, 2)), Some("{%") => Some((StartMarker::Block, 2)), Some("{#") => Some((StartMarker::Comment, 2)), _ => None, } } #[cfg(feature = "unicode")] fn lex_identifier(s: &str) -> usize { s.chars() .enumerate() .map_while(|(idx, c)| { let cont = if c == '_' { true } else if idx == 0 { unicode_ident::is_xid_start(c) } else { unicode_ident::is_xid_continue(c) }; cont.then(|| c.len_utf8()) }) .sum::() } #[cfg(not(feature = "unicode"))] fn lex_identifier(s: &str) -> usize { s.as_bytes() .iter() .enumerate() .take_while(|&(idx, &c)| { if c == b'_' { true } else if idx == 0 { c.is_ascii_alphabetic() } else { c.is_ascii_alphanumeric() } }) .count() } fn skip_basic_tag(block_str: &str, name: &str, block_end: &str) -> Option<(usize, bool)> { let mut ptr = block_str; let mut trim = false; if let Some(rest) = ptr.strip_prefix('-') { ptr = rest; } while let Some(rest) = ptr.strip_prefix(|x: char| x.is_ascii_whitespace()) { ptr = rest; } ptr = match ptr.strip_prefix(name) { Some(ptr) => ptr, None => return None, }; while let Some(rest) = ptr.strip_prefix(|x: char| x.is_ascii_whitespace()) { ptr = rest; } if let Some(rest) = ptr.strip_prefix('-') { ptr = rest; trim = true; } ptr = match ptr.strip_prefix(block_end) { Some(ptr) => ptr, None => return None, }; Some((block_str.len() - ptr.len(), trim)) } impl<'s> TokenizerState<'s> { fn advance(&mut self, bytes: usize) -> &'s str { let (skipped, new_rest) = self.rest.split_at(bytes); for c in skipped.chars() { match c { '\n' => { self.current_line += 1; self.current_col = 0; } _ => self.current_col += 1, } } self.current_offset += bytes as u32; self.rest = new_rest; skipped } #[inline(always)] fn loc(&self) -> (u32, u32, u32) { (self.current_line, self.current_col, self.current_offset) } fn span(&self, start: (u32, u32, u32)) -> Span { let (start_line, start_col, start_offset) = start; Span { start_line, start_col, start_offset, end_line: self.current_line, end_col: self.current_col, end_offset: self.current_offset, } } fn syntax_error(&mut self, msg: &'static str) -> Error { self.failed = true; Error::new(ErrorKind::SyntaxError, msg) } fn eat_number(&mut self) -> Result<(Token<'s>, Span), Error> { #[derive(Copy, Clone)] enum State { Integer, // 123 Fraction, // .123 Exponent, // E | e ExponentSign, // +|- } let old_loc = self.loc(); let mut state = State::Integer; let mut num_len = self .rest .chars() .take_while(|&c| c.is_ascii_digit()) .count(); for c in self.rest.chars().skip(num_len) { state = match (c, state) { ('.', State::Integer) => State::Fraction, ('E' | 'e', State::Integer | State::Fraction) => State::Exponent, ('+' | '-', State::Exponent) => State::ExponentSign, ('0'..='9', State::Exponent) => State::ExponentSign, ('0'..='9', state) => state, _ => break, }; num_len += 1; } let is_float = !matches!(state, State::Integer); let num = self.advance(num_len); Ok(( ok!(if is_float { num.parse() .map(Token::Float) .map_err(|_| self.syntax_error("invalid float")) } else { num.parse() .map(Token::Int) .map_err(|_| self.syntax_error("invalid integer")) }), self.span(old_loc), )) } fn eat_identifier(&mut self) -> Result<(Token<'s>, Span), Error> { let ident_len = lex_identifier(self.rest); if ident_len > 0 { let old_loc = self.loc(); let ident = self.advance(ident_len); Ok((Token::Ident(ident), self.span(old_loc))) } else { Err(self.syntax_error("unexpected character")) } } fn eat_string(&mut self, delim: u8) -> Result<(Token<'s>, Span), Error> { let old_loc = self.loc(); let mut escaped = false; let mut has_escapes = false; let str_len = self .rest .as_bytes() .iter() .skip(1) .take_while(|&&c| match (escaped, c) { (true, _) => { escaped = false; true } (_, b'\\') => { escaped = true; has_escapes = true; true } (_, c) if c == delim => false, _ => true, }) .count(); if escaped || self.rest.as_bytes().get(str_len + 1) != Some(&delim) { return Err(self.syntax_error("unexpected end of string")); } let s = self.advance(str_len + 2); Ok(if has_escapes { ( Token::String(match unescape(&s[1..s.len() - 1]) { Ok(unescaped) => unescaped, Err(err) => return Err(err), }), self.span(old_loc), ) } else { (Token::Str(&s[1..s.len() - 1]), self.span(old_loc)) }) } fn skip_whitespace(&mut self) { let skip = self .rest .chars() .map_while(|c| c.is_whitespace().then(|| c.len_utf8())) .sum::(); if skip > 0 { self.advance(skip); } } } /// Tokenizes the source. pub fn tokenize( input: &str, in_expr: bool, syntax_config: SyntaxConfig, ) -> impl Iterator, Span), Error>> { let mut state = TokenizerState { rest: input, stack: vec![if in_expr { LexerState::InVariable } else { LexerState::Template }], failed: false, current_line: 1, current_col: 0, current_offset: 0, }; let mut trim_leading_whitespace = false; std::iter::from_fn(move || { let (variable_end, block_start, block_end, comment_end) = { #[cfg(feature = "custom_syntax")] { ( &syntax_config.syntax.variable_end as &str, &syntax_config.syntax.block_start as &str, &syntax_config.syntax.block_end as &str, &syntax_config.syntax.comment_end as &str, ) } #[cfg(not(feature = "custom_syntax"))] { // hardcode them here again, so the compiler can optimize ("}}", "{%", "%}", "#}") } }; loop { if state.rest.is_empty() || state.failed { return None; } let mut old_loc = state.loc(); match state.stack.last() { Some(LexerState::Template) => { match match_start_marker(state.rest, &syntax_config) { Some((StartMarker::Comment, skip)) => { if let Some(end) = memstr(&state.rest.as_bytes()[skip..], comment_end.as_bytes()) { if state .rest .as_bytes() .get(end.saturating_sub(1) + skip) .copied() == Some(b'-') { trim_leading_whitespace = true; } state.advance(end + skip + comment_end.len()); continue; } else { return Some(Err(state.syntax_error("unexpected end of comment"))); } } Some((StartMarker::Variable, skip)) => { if state.rest.as_bytes().get(skip) == Some(&b'-') { state.advance(skip + 1); } else { state.advance(skip); } state.stack.push(LexerState::InVariable); return Some(Ok((Token::VariableStart, state.span(old_loc)))); } Some((StartMarker::Block, skip)) => { // raw blocks require some special handling. If we are at the beginning of a raw // block we want to skip everything until {% endraw %} completely ignoring iterior // syntax and emit the entire raw block as TemplateData. if let Some((raw, trim_start)) = skip_basic_tag(&state.rest[skip..], "raw", block_end) { state.advance(raw + skip); let mut ptr = 0; while let Some(block) = memstr(&state.rest.as_bytes()[ptr..], block_start.as_bytes()) { ptr += block + block_start.len(); let trim_end = state.rest.as_bytes().get(ptr) == Some(&b'-'); if let Some((endraw, trim_next)) = skip_basic_tag(&state.rest[ptr..], "endraw", block_end) { let mut result = &state.rest[..ptr - block_start.len()]; if trim_start { result = result.trim_start(); } if trim_end { result = result.trim_end(); } state.advance(ptr + endraw); trim_leading_whitespace = trim_next; return Some(Ok(( Token::TemplateData(result), state.span(old_loc), ))); } } return Some( Err(state.syntax_error("unexpected end of raw block")), ); } if state.rest.as_bytes().get(skip) == Some(&b'-') { state.advance(skip + 1); } else { state.advance(skip); } state.stack.push(LexerState::InBlock); return Some(Ok((Token::BlockStart, state.span(old_loc)))); } None => {} } if trim_leading_whitespace { trim_leading_whitespace = false; state.skip_whitespace(); } old_loc = state.loc(); let (lead, span) = match find_start_marker(state.rest, &syntax_config) { Some((start, false)) => (state.advance(start), state.span(old_loc)), Some((start, _)) => { let peeked = &state.rest[..start]; let trimmed = peeked.trim_end(); let lead = state.advance(trimmed.len()); let span = state.span(old_loc); state.advance(peeked.len() - trimmed.len()); (lead, span) } None => (state.advance(state.rest.len()), state.span(old_loc)), }; if lead.is_empty() { continue; } return Some(Ok((Token::TemplateData(lead), span))); } Some(LexerState::InBlock | LexerState::InVariable) => { // in blocks whitespace is generally ignored, skip it. match state .rest .as_bytes() .iter() .position(|&x| !x.is_ascii_whitespace()) { Some(0) => {} None => { state.advance(state.rest.len()); continue; } Some(offset) => { state.advance(offset); continue; } } // look out for the end of blocks if let Some(&LexerState::InBlock) = state.stack.last() { if state.rest.get(..1) == Some("-") && state.rest.get(1..block_end.len() + 1) == Some(block_end) { state.stack.pop(); trim_leading_whitespace = true; state.advance(block_end.len() + 1); return Some(Ok((Token::BlockEnd, state.span(old_loc)))); } if state.rest.get(..block_end.len()) == Some(block_end) { state.stack.pop(); state.advance(block_end.len()); return Some(Ok((Token::BlockEnd, state.span(old_loc)))); } } else { if state.rest.get(..1) == Some("-") && state.rest.get(1..variable_end.len() + 1) == Some(variable_end) { state.stack.pop(); state.advance(variable_end.len() + 1); trim_leading_whitespace = true; return Some(Ok((Token::VariableEnd, state.span(old_loc)))); } if state.rest.get(..variable_end.len()) == Some(variable_end) { state.stack.pop(); state.advance(variable_end.len()); return Some(Ok((Token::VariableEnd, state.span(old_loc)))); } } // two character operators let op = match state.rest.as_bytes().get(..2) { Some(b"//") => Some(Token::FloorDiv), Some(b"**") => Some(Token::Pow), Some(b"==") => Some(Token::Eq), Some(b"!=") => Some(Token::Ne), Some(b">=") => Some(Token::Gte), Some(b"<=") => Some(Token::Lte), _ => None, }; if let Some(op) = op { state.advance(2); return Some(Ok((op, state.span(old_loc)))); } // single character operators (and strings) let op = match state.rest.as_bytes().get(0) { Some(b'+') => Some(Token::Plus), Some(b'-') => Some(Token::Minus), Some(b'*') => Some(Token::Mul), Some(b'/') => Some(Token::Div), Some(b'%') => Some(Token::Mod), Some(b'!') => Some(Token::Bang), Some(b'.') => Some(Token::Dot), Some(b',') => Some(Token::Comma), Some(b':') => Some(Token::Colon), Some(b'~') => Some(Token::Tilde), Some(b'|') => Some(Token::Pipe), Some(b'=') => Some(Token::Assign), Some(b'>') => Some(Token::Gt), Some(b'<') => Some(Token::Lt), Some(b'(') => Some(Token::ParenOpen), Some(b')') => Some(Token::ParenClose), Some(b'[') => Some(Token::BracketOpen), Some(b']') => Some(Token::BracketClose), Some(b'{') => Some(Token::BraceOpen), Some(b'}') => Some(Token::BraceClose), Some(b'\'') => { return Some(state.eat_string(b'\'')); } Some(b'"') => { return Some(state.eat_string(b'"')); } Some(c) if c.is_ascii_digit() => return Some(state.eat_number()), _ => None, }; if let Some(op) = op { state.advance(1); return Some(Ok((op, state.span(old_loc)))); } return Some(state.eat_identifier()); } None => panic!("empty lexer state"), } } }) } #[cfg(test)] mod tests { use super::*; use similar_asserts::assert_eq; #[test] fn test_find_marker() { let syntax = SyntaxConfig::default(); assert!(find_start_marker("{", &syntax).is_none()); assert!(find_start_marker("foo", &syntax).is_none()); assert!(find_start_marker("foo {", &syntax).is_none()); assert_eq!(find_start_marker("foo {{", &syntax), Some((4, false))); assert_eq!(find_start_marker("foo {{-", &syntax), Some((4, true))); } #[test] #[cfg(feature = "custom_syntax")] fn test_find_marker_custom_syntax() { use crate::Syntax; let syntax = Syntax { block_start: "%{".into(), block_end: "}%".into(), variable_start: "[[".into(), variable_end: "]]".into(), comment_start: "/*".into(), comment_end: "*/".into(), }; let syntax_config = syntax.compile().expect("failed to create syntax config"); assert_eq!(find_start_marker("%{", &syntax_config), Some((0, false))); assert!(find_start_marker("/", &syntax_config).is_none()); assert!(find_start_marker("foo [", &syntax_config).is_none()); assert_eq!( find_start_marker("foo /*", &syntax_config), Some((4, false)) ); assert_eq!( find_start_marker("foo [[-", &syntax_config), Some((4, true)) ); } #[test] fn test_is_basic_tag() { assert_eq!(skip_basic_tag(" raw %}", "raw", "%}"), Some((7, false))); assert_eq!(skip_basic_tag(" raw %}", "endraw", "%}"), None); assert_eq!(skip_basic_tag(" raw %}", "raw", "%}"), Some((9, false))); assert_eq!(skip_basic_tag("- raw -%}", "raw", "%}"), Some((11, true))); } #[test] fn test_basic_identifiers() { fn assert_ident(s: &str) { match tokenize(s, true, Default::default()).next() { Some(Ok((Token::Ident(ident), _))) if ident == s => {} _ => panic!("did not get a matching token result: {s:?}"), } } fn assert_not_ident(s: &str) { let res = tokenize(s, true, Default::default()).collect::, _>>(); if let Ok(tokens) = res { if let &[(Token::Ident(_), _)] = &tokens[..] { panic!("got a single ident for {s:?}") } } } assert_ident("foo_bar_baz"); assert_ident("_foo_bar_baz"); assert_ident("_42world"); assert_ident("_world42"); assert_ident("world42"); assert_not_ident("42world"); #[cfg(feature = "unicode")] { assert_ident("foo"); assert_ident("föö"); assert_ident("き"); assert_ident("_"); assert_not_ident("1a"); assert_not_ident("a-"); assert_not_ident("🐍a"); assert_not_ident("a🐍🐍"); assert_ident("ᢅ"); assert_ident("ᢆ"); assert_ident("℘"); assert_ident("℮"); assert_not_ident("·"); assert_ident("a·"); } } } minijinja-1.0.3/src/compiler/meta.rs000064400000000000000000000221051046102023000154720ustar 00000000000000use std::collections::HashSet; use std::fmt::Write; use crate::compiler::ast; struct AssignmentTracker<'a> { out: HashSet<&'a str>, nested_out: Option>, assigned: Vec>, } impl<'a> AssignmentTracker<'a> { fn is_assigned(&self, name: &str) -> bool { self.assigned.iter().any(|x| x.contains(name)) } fn assign(&mut self, name: &'a str) { self.assigned.last_mut().unwrap().insert(name); } fn assign_nested(&mut self, name: String) { if let Some(ref mut nested_out) = self.nested_out { if !nested_out.contains(&name) { nested_out.insert(name); } } } fn push(&mut self) { self.assigned.push(Default::default()); } fn pop(&mut self) { self.assigned.pop(); } } /// Finds all variables that need to be captured as closure for a macro. #[cfg(feature = "macros")] pub fn find_macro_closure<'a>(m: &ast::Macro<'a>) -> HashSet<&'a str> { let mut state = AssignmentTracker { out: HashSet::new(), nested_out: None, assigned: vec![Default::default()], }; m.args.iter().for_each(|arg| track_assign(arg, &mut state)); m.body.iter().for_each(|node| track_walk(node, &mut state)); state.out } /// Finds all variables that are undeclared in a template. pub fn find_undeclared(t: &ast::Stmt<'_>, track_nested: bool) -> HashSet { let mut state = AssignmentTracker { out: HashSet::new(), nested_out: if track_nested { Some(HashSet::new()) } else { None }, assigned: vec![Default::default()], }; track_walk(t, &mut state); if let Some(nested) = state.nested_out { nested } else { state.out.into_iter().map(|x| x.to_string()).collect() } } fn tracker_visit_expr_opt<'a>(expr: &Option>, state: &mut AssignmentTracker<'a>) { if let Some(expr) = expr { tracker_visit_expr(expr, state); } } fn tracker_visit_expr<'a>(expr: &ast::Expr<'a>, state: &mut AssignmentTracker<'a>) { match expr { ast::Expr::Var(var) => { if !state.is_assigned(var.id) { state.out.insert(var.id); // if we are not tracking nested assignments, we can consider a variable // to be assigned the first time we perform a lookup. if state.nested_out.is_none() { state.assign(var.id); } else { state.assign_nested(var.id.to_string()); } } } ast::Expr::Const(_) => {} ast::Expr::UnaryOp(expr) => tracker_visit_expr(&expr.expr, state), ast::Expr::BinOp(expr) => { tracker_visit_expr(&expr.left, state); tracker_visit_expr(&expr.right, state); } ast::Expr::IfExpr(expr) => { tracker_visit_expr(&expr.test_expr, state); tracker_visit_expr(&expr.true_expr, state); tracker_visit_expr_opt(&expr.false_expr, state); } ast::Expr::Filter(expr) => { tracker_visit_expr_opt(&expr.expr, state); expr.args.iter().for_each(|x| tracker_visit_expr(x, state)); } ast::Expr::Test(expr) => { tracker_visit_expr(&expr.expr, state); expr.args.iter().for_each(|x| tracker_visit_expr(x, state)); } ast::Expr::GetAttr(expr) => { // if we are tracking nested, we check if we have a chain of attribute // lookups that terminate in a variable lookup. In that case we can // assign the nested lookup. if state.nested_out.is_some() { let mut attrs = vec![expr.name]; let mut ptr = &expr.expr; loop { match ptr { ast::Expr::Var(var) => { if !state.is_assigned(var.id) { let mut rv = var.id.to_string(); for attr in attrs.iter().rev() { write!(rv, ".{}", attr).ok(); } state.assign_nested(rv); return; } } ast::Expr::GetAttr(expr) => { attrs.push(expr.name); ptr = &expr.expr; continue; } _ => break, } } } tracker_visit_expr(&expr.expr, state) } ast::Expr::GetItem(expr) => { tracker_visit_expr(&expr.expr, state); tracker_visit_expr(&expr.subscript_expr, state); } ast::Expr::Slice(slice) => { tracker_visit_expr_opt(&slice.start, state); tracker_visit_expr_opt(&slice.stop, state); tracker_visit_expr_opt(&slice.step, state); } ast::Expr::Call(expr) => { tracker_visit_expr(&expr.expr, state); expr.args.iter().for_each(|x| tracker_visit_expr(x, state)); } ast::Expr::List(expr) => expr.items.iter().for_each(|x| tracker_visit_expr(x, state)), ast::Expr::Map(expr) => expr.keys.iter().zip(expr.values.iter()).for_each(|(k, v)| { tracker_visit_expr(k, state); tracker_visit_expr(v, state); }), ast::Expr::Kwargs(expr) => expr .pairs .iter() .for_each(|(_, v)| tracker_visit_expr(v, state)), } } fn track_assign<'a>(expr: &ast::Expr<'a>, state: &mut AssignmentTracker<'a>) { match expr { ast::Expr::Var(var) => state.assign(var.id), ast::Expr::List(list) => list.items.iter().for_each(|x| track_assign(x, state)), _ => {} } } fn track_walk<'a>(node: &ast::Stmt<'a>, state: &mut AssignmentTracker<'a>) { match node { ast::Stmt::Template(stmt) => { state.assign("self"); stmt.children.iter().for_each(|x| track_walk(x, state)); } ast::Stmt::EmitExpr(expr) => tracker_visit_expr(&expr.expr, state), ast::Stmt::EmitRaw(_) => {} ast::Stmt::ForLoop(stmt) => { state.push(); state.assign("loop"); tracker_visit_expr(&stmt.iter, state); track_assign(&stmt.target, state); tracker_visit_expr_opt(&stmt.filter_expr, state); stmt.body.iter().for_each(|x| track_walk(x, state)); state.pop(); state.push(); stmt.else_body.iter().for_each(|x| track_walk(x, state)); state.pop(); } ast::Stmt::IfCond(stmt) => { tracker_visit_expr(&stmt.expr, state); state.push(); stmt.true_body.iter().for_each(|x| track_walk(x, state)); state.pop(); state.push(); stmt.false_body.iter().for_each(|x| track_walk(x, state)); state.pop(); } ast::Stmt::WithBlock(stmt) => { state.push(); for (target, expr) in &stmt.assignments { track_assign(target, state); tracker_visit_expr(expr, state); } stmt.body.iter().for_each(|x| track_walk(x, state)); state.pop(); } ast::Stmt::Set(stmt) => { track_assign(&stmt.target, state); tracker_visit_expr(&stmt.expr, state); } ast::Stmt::AutoEscape(stmt) => { state.push(); stmt.body.iter().for_each(|x| track_walk(x, state)); state.pop(); } ast::Stmt::FilterBlock(stmt) => { state.push(); stmt.body.iter().for_each(|x| track_walk(x, state)); state.pop(); } ast::Stmt::SetBlock(stmt) => { track_assign(&stmt.target, state); state.push(); stmt.body.iter().for_each(|x| track_walk(x, state)); state.pop(); } #[cfg(feature = "multi_template")] ast::Stmt::Block(stmt) => { state.push(); state.assign("super"); stmt.body.iter().for_each(|x| track_walk(x, state)); state.pop(); } #[cfg(feature = "multi_template")] ast::Stmt::Extends(_) | ast::Stmt::Include(_) => {} #[cfg(feature = "multi_template")] ast::Stmt::Import(stmt) => { track_assign(&stmt.name, state); } #[cfg(feature = "multi_template")] ast::Stmt::FromImport(stmt) => stmt.names.iter().for_each(|(arg, alias)| { track_assign(alias.as_ref().unwrap_or(arg), state); }), #[cfg(feature = "macros")] ast::Stmt::Macro(stmt) => { state.assign(stmt.name); } #[cfg(feature = "macros")] ast::Stmt::CallBlock(_) => {} ast::Stmt::Do(stmt) => { tracker_visit_expr(&stmt.call.expr, state); stmt.call .args .iter() .for_each(|x| tracker_visit_expr(x, state)); } } } minijinja-1.0.3/src/compiler/mod.rs000064400000000000000000000003011046102023000153150ustar 00000000000000#![allow(missing_docs)] /// This module contains the internals of the compiler. pub mod ast; pub mod codegen; pub mod instructions; pub mod lexer; pub mod meta; pub mod parser; pub mod tokens; minijinja-1.0.3/src/compiler/parser.rs000064400000000000000000001201341046102023000160410ustar 00000000000000use std::borrow::Cow; use std::collections::BTreeSet; use std::fmt; use crate::compiler::ast::{self, Spanned}; use crate::compiler::lexer::{tokenize, SyntaxConfig}; use crate::compiler::tokens::{Span, Token}; use crate::error::{Error, ErrorKind}; use crate::value::Value; const MAX_RECURSION: usize = 150; const RESERVED_NAMES: [&str; 8] = [ "true", "True", "false", "False", "none", "None", "loop", "self", ]; fn unexpected(unexpected: D, expected: &str) -> Error { Error::new( ErrorKind::SyntaxError, format!("unexpected {unexpected}, expected {expected}"), ) } fn unexpected_eof(expected: &str) -> Error { unexpected("end of input", expected) } fn make_const(value: Value, span: Span) -> ast::Expr<'static> { ast::Expr::Const(Spanned::new(ast::Const { value }, span)) } fn syntax_error(msg: Cow<'static, str>) -> Error { Error::new(ErrorKind::SyntaxError, msg) } macro_rules! syntax_error { ($msg:expr) => {{ return Err(syntax_error(Cow::Borrowed($msg))); }}; ($msg:expr, $($tt:tt)*) => {{ return Err(syntax_error(Cow::Owned(format!($msg, $($tt)*)))); }}; } macro_rules! expect_token { ($parser:expr, $expectation:expr) => {{ match ok!($parser.stream.next()) { Some(rv) => rv, None => return Err(unexpected_eof($expectation)), } }}; ($parser:expr, $match:pat, $expectation:expr) => {{ match ok!($parser.stream.next()) { Some((token @ $match, span)) => (token, span), Some((token, _)) => return Err(unexpected(token, $expectation)), None => return Err(unexpected_eof($expectation)), } }}; ($parser:expr, $match:pat => $target:expr, $expectation:expr) => {{ match ok!($parser.stream.next()) { Some(($match, span)) => ($target, span), Some((token, _)) => return Err(unexpected(token, $expectation)), None => return Err(unexpected_eof($expectation)), } }}; } macro_rules! matches_token { ($p:expr, $match:pat) => { match $p.stream.current() { Err(err) => return Err(err), Ok(Some(($match, _))) => true, _ => false, } }; } macro_rules! skip_token { ($p:expr, $match:pat) => { match $p.stream.current() { Err(err) => return Err(err), Ok(Some(($match, _))) => { let _ = $p.stream.next(); true } _ => false, } }; } enum SetParseResult<'a> { Set(ast::Set<'a>), SetBlock(ast::SetBlock<'a>), } struct TokenStream<'a> { iter: Box, Span), Error>> + 'a>, current: Option, Span), Error>>, last_span: Span, } impl<'a> TokenStream<'a> { /// Tokenize a template pub fn new(source: &'a str, in_expr: bool, syntax: SyntaxConfig) -> TokenStream<'a> { let mut iter = Box::new(tokenize(source, in_expr, syntax)) as Box>; let current = iter.next(); TokenStream { iter, current, last_span: Span::default(), } } /// Advance the stream. pub fn next(&mut self) -> Result, Span)>, Error> { let rv = self.current.take(); self.current = self.iter.next(); if let Some(Ok((_, span))) = rv { self.last_span = span; } rv.transpose() } /// Look at the current token pub fn current(&mut self) -> Result, Span)>, Error> { match self.current { Some(Ok(ref tok)) => Ok(Some((&tok.0, tok.1))), Some(Err(_)) => Err(self.current.take().unwrap().unwrap_err()), None => Ok(None), } } /// Expands the span #[inline(always)] pub fn expand_span(&self, mut span: Span) -> Span { span.end_line = self.last_span.end_line; span.end_col = self.last_span.end_col; span.end_offset = self.last_span.end_offset; span } /// Returns the current span. #[inline(always)] pub fn current_span(&self) -> Span { if let Some(Ok((_, span))) = self.current { span } else { self.last_span } } /// Returns the last seen span. #[inline(always)] pub fn last_span(&self) -> Span { self.last_span } } struct Parser<'a> { stream: TokenStream<'a>, #[allow(unused)] in_macro: bool, #[allow(unused)] blocks: BTreeSet<&'a str>, depth: usize, } macro_rules! binop { ($func:ident, $next:ident, { $($tok:tt)* }) => { fn $func(&mut self) -> Result, Error> { let span = self.stream.current_span(); let mut left = ok!(self.$next()); loop { let op = match ok!(self.stream.current()) { $($tok)* _ => break, }; ok!(self.stream.next()); let right = ok!(self.$next()); left = ast::Expr::BinOp(Spanned::new( ast::BinOp { op, left, right, }, self.stream.expand_span(span), )); } Ok(left) } }; } macro_rules! unaryop { ($func:ident, $next:ident, { $($tok:tt)* }) => { fn $func(&mut self) -> Result, Error> { let span = self.stream.current_span(); let op = match ok!(self.stream.current()) { $($tok)* _ => return self.$next() }; ok!(self.stream.next()); Ok(ast::Expr::UnaryOp(Spanned::new( ast::UnaryOp { op, expr: ok!(self.$func()), }, self.stream.expand_span(span), ))) } }; } macro_rules! with_recursion_guard { ($parser:expr, $expr:expr) => {{ $parser.depth += 1; if $parser.depth > MAX_RECURSION { return Err(syntax_error(Cow::Borrowed( "template exceeds maximum recursion limits", ))); } let rv = $expr; $parser.depth -= 1; rv }}; } impl<'a> Parser<'a> { pub fn new(source: &'a str, in_expr: bool, syntax: SyntaxConfig) -> Parser<'a> { Parser { stream: TokenStream::new(source, in_expr, syntax), in_macro: false, blocks: BTreeSet::new(), depth: 0, } } fn parse_ifexpr(&mut self) -> Result, Error> { let mut span = self.stream.last_span(); let mut expr = ok!(self.parse_or()); loop { if skip_token!(self, Token::Ident("if")) { let expr2 = ok!(self.parse_or()); let expr3 = if skip_token!(self, Token::Ident("else")) { Some(ok!(self.parse_ifexpr())) } else { None }; expr = ast::Expr::IfExpr(Spanned::new( ast::IfExpr { test_expr: expr2, true_expr: expr, false_expr: expr3, }, self.stream.expand_span(span), )); span = self.stream.last_span(); } else { break; } } Ok(expr) } binop!(parse_or, parse_and, { Some((Token::Ident("or"), _)) => ast::BinOpKind::ScOr, }); binop!(parse_and, parse_not, { Some((Token::Ident("and"), _)) => ast::BinOpKind::ScAnd, }); unaryop!(parse_not, parse_compare, { Some((Token::Ident("not"), _)) => ast::UnaryOpKind::Not, }); fn parse_compare(&mut self) -> Result, Error> { let mut span = self.stream.last_span(); let mut expr = ok!(self.parse_math1()); loop { let mut negated = false; let op = match ok!(self.stream.current()) { Some((Token::Eq, _)) => ast::BinOpKind::Eq, Some((Token::Ne, _)) => ast::BinOpKind::Ne, Some((Token::Lt, _)) => ast::BinOpKind::Lt, Some((Token::Lte, _)) => ast::BinOpKind::Lte, Some((Token::Gt, _)) => ast::BinOpKind::Gt, Some((Token::Gte, _)) => ast::BinOpKind::Gte, Some((Token::Ident("in"), _)) => ast::BinOpKind::In, Some((Token::Ident("not"), _)) => { ok!(self.stream.next()); expect_token!(self, Token::Ident("in"), "in"); negated = true; ast::BinOpKind::In } _ => break, }; if !negated { ok!(self.stream.next()); } expr = ast::Expr::BinOp(Spanned::new( ast::BinOp { op, left: expr, right: ok!(self.parse_math1()), }, self.stream.expand_span(span), )); if negated { expr = ast::Expr::UnaryOp(Spanned::new( ast::UnaryOp { op: ast::UnaryOpKind::Not, expr, }, self.stream.expand_span(span), )); } span = self.stream.last_span(); } Ok(expr) } binop!(parse_math1, parse_concat, { Some((Token::Plus, _)) => ast::BinOpKind::Add, Some((Token::Minus, _)) => ast::BinOpKind::Sub, }); binop!(parse_concat, parse_math2, { Some((Token::Tilde, _)) => ast::BinOpKind::Concat, }); binop!(parse_math2, parse_pow, { Some((Token::Mul, _)) => ast::BinOpKind::Mul, Some((Token::Div, _)) => ast::BinOpKind::Div, Some((Token::FloorDiv, _)) => ast::BinOpKind::FloorDiv, Some((Token::Mod, _)) => ast::BinOpKind::Rem, }); binop!(parse_pow, parse_unary, { Some((Token::Pow, _)) => ast::BinOpKind::Pow, }); unaryop!(parse_unary_only, parse_primary, { Some((Token::Minus, _)) => ast::UnaryOpKind::Neg, }); fn parse_unary(&mut self) -> Result, Error> { let span = self.stream.current_span(); let mut expr = ok!(self.parse_unary_only()); expr = ok!(self.parse_postfix(expr, span)); self.parse_filter_expr(expr) } fn parse_postfix( &mut self, expr: ast::Expr<'a>, mut span: Span, ) -> Result, Error> { let mut expr = expr; loop { let next_span = self.stream.current_span(); match ok!(self.stream.current()) { Some((Token::Dot, _)) => { ok!(self.stream.next()); let (name, _) = expect_token!(self, Token::Ident(name) => name, "identifier"); expr = ast::Expr::GetAttr(Spanned::new( ast::GetAttr { name, expr }, self.stream.expand_span(span), )); } Some((Token::BracketOpen, _)) => { ok!(self.stream.next()); let mut start = None; let mut stop = None; let mut step = None; let mut is_slice = false; if !matches_token!(self, Token::Colon) { start = Some(ok!(self.parse_expr())); } if skip_token!(self, Token::Colon) { is_slice = true; if !matches_token!(self, Token::BracketClose | Token::Colon) { stop = Some(ok!(self.parse_expr())); } if skip_token!(self, Token::Colon) && !matches_token!(self, Token::BracketClose) { step = Some(ok!(self.parse_expr())); } } expect_token!(self, Token::BracketClose, "`]`"); if !is_slice { expr = ast::Expr::GetItem(Spanned::new( ast::GetItem { expr, subscript_expr: ok!(start.ok_or_else(|| { syntax_error(Cow::Borrowed("empty subscript")) })), }, self.stream.expand_span(span), )); } else { expr = ast::Expr::Slice(Spanned::new( ast::Slice { expr, start, stop, step, }, self.stream.expand_span(span), )); } } Some((Token::ParenOpen, _)) => { let args = ok!(self.parse_args()); expr = ast::Expr::Call(Spanned::new( ast::Call { expr, args }, self.stream.expand_span(span), )); } _ => break, } span = next_span; } Ok(expr) } fn parse_filter_expr(&mut self, expr: ast::Expr<'a>) -> Result, Error> { let mut expr = expr; loop { match ok!(self.stream.current()) { Some((Token::Pipe, _)) => { ok!(self.stream.next()); let (name, span) = expect_token!(self, Token::Ident(name) => name, "identifier"); let args = if matches_token!(self, Token::ParenOpen) { ok!(self.parse_args()) } else { Vec::new() }; expr = ast::Expr::Filter(Spanned::new( ast::Filter { name, expr: Some(expr), args, }, self.stream.expand_span(span), )); } Some((Token::Ident("is"), _)) => { ok!(self.stream.next()); let negated = skip_token!(self, Token::Ident("not")); let (name, span) = expect_token!(self, Token::Ident(name) => name, "identifier"); let args = if matches_token!(self, Token::ParenOpen) { ok!(self.parse_args()) } else { Vec::new() }; expr = ast::Expr::Test(Spanned::new( ast::Test { name, expr, args }, self.stream.expand_span(span), )); if negated { expr = ast::Expr::UnaryOp(Spanned::new( ast::UnaryOp { op: ast::UnaryOpKind::Not, expr, }, self.stream.expand_span(span), )); } } _ => break, } } Ok(expr) } fn parse_args(&mut self) -> Result>, Error> { let mut args = Vec::new(); let mut first_span = None; let mut kwargs = Vec::new(); expect_token!(self, Token::ParenOpen, "`(`"); loop { if skip_token!(self, Token::ParenClose) { break; } if !args.is_empty() || !kwargs.is_empty() { expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::ParenClose) { break; } } let expr = ok!(self.parse_expr()); // keyword argument match expr { ast::Expr::Var(ref var) if skip_token!(self, Token::Assign) => { if first_span.is_none() { first_span = Some(var.span()); } kwargs.push((var.id, ok!(self.parse_expr_noif()))); } _ if !kwargs.is_empty() => { return Err(syntax_error(Cow::Borrowed( "non-keyword arg after keyword arg", ))); } _ => { args.push(expr); } } } if !kwargs.is_empty() { args.push(ast::Expr::Kwargs(ast::Spanned::new( ast::Kwargs { pairs: kwargs }, self.stream.expand_span(first_span.unwrap()), ))); }; Ok(args) } fn parse_primary(&mut self) -> Result, Error> { with_recursion_guard!(self, self.parse_primary_impl()) } fn parse_primary_impl(&mut self) -> Result, Error> { let (token, span) = expect_token!(self, "expression"); macro_rules! const_val { ($expr:expr) => { make_const(Value::from($expr), span) }; } match token { Token::Ident("true" | "True") => Ok(const_val!(true)), Token::Ident("false" | "False") => Ok(const_val!(false)), Token::Ident("none" | "None") => Ok(const_val!(())), Token::Ident(name) => Ok(ast::Expr::Var(Spanned::new(ast::Var { id: name }, span))), Token::Str(val) => Ok(const_val!(val)), Token::String(val) => Ok(const_val!(val)), Token::Int(val) => Ok(const_val!(val)), Token::Float(val) => Ok(const_val!(val)), Token::ParenOpen => self.parse_tuple_or_expression(span), Token::BracketOpen => self.parse_list_expr(span), Token::BraceOpen => self.parse_map_expr(span), token => syntax_error!("unexpected {}", token), } } fn parse_list_expr(&mut self, span: Span) -> Result, Error> { let mut items = Vec::new(); loop { if skip_token!(self, Token::BracketClose) { break; } if !items.is_empty() { expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::BracketClose) { break; } } items.push(ok!(self.parse_expr())); } Ok(ast::Expr::List(Spanned::new( ast::List { items }, self.stream.expand_span(span), ))) } fn parse_map_expr(&mut self, span: Span) -> Result, Error> { let mut keys = Vec::new(); let mut values = Vec::new(); loop { if skip_token!(self, Token::BraceClose) { break; } if !keys.is_empty() { expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::BraceClose) { break; } } keys.push(ok!(self.parse_expr())); expect_token!(self, Token::Colon, "`:`"); values.push(ok!(self.parse_expr())); } Ok(ast::Expr::Map(Spanned::new( ast::Map { keys, values }, self.stream.expand_span(span), ))) } fn parse_tuple_or_expression(&mut self, span: Span) -> Result, Error> { // MiniJinja does not really have tuples, but it treats the tuple // syntax the same as lists. if skip_token!(self, Token::ParenClose) { return Ok(ast::Expr::List(Spanned::new( ast::List { items: vec![] }, self.stream.expand_span(span), ))); } let mut expr = ok!(self.parse_expr()); if matches_token!(self, Token::Comma) { let mut items = vec![expr]; loop { if skip_token!(self, Token::ParenClose) { break; } expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::ParenClose) { break; } items.push(ok!(self.parse_expr())); } expr = ast::Expr::List(Spanned::new( ast::List { items }, self.stream.expand_span(span), )); } else { expect_token!(self, Token::ParenClose, "`)`"); } Ok(expr) } pub fn parse_expr(&mut self) -> Result, Error> { with_recursion_guard!(self, self.parse_ifexpr()) } pub fn parse_expr_noif(&mut self) -> Result, Error> { self.parse_or() } fn parse_stmt(&mut self) -> Result, Error> { with_recursion_guard!(self, self.parse_stmt_unprotected()) } fn parse_stmt_unprotected(&mut self) -> Result, Error> { let (token, span) = expect_token!(self, "block keyword"); macro_rules! respan { ($expr:expr) => { Spanned::new($expr, self.stream.expand_span(span)) }; } let ident = match token { Token::Ident(ident) => ident, token => syntax_error!("unknown {}, expected statement", token), }; Ok(match ident { "for" => ast::Stmt::ForLoop(respan!(ok!(self.parse_for_stmt()))), "if" => ast::Stmt::IfCond(respan!(ok!(self.parse_if_cond()))), "with" => ast::Stmt::WithBlock(respan!(ok!(self.parse_with_block()))), "set" => match ok!(self.parse_set()) { SetParseResult::Set(rv) => ast::Stmt::Set(respan!(rv)), SetParseResult::SetBlock(rv) => ast::Stmt::SetBlock(respan!(rv)), }, "autoescape" => ast::Stmt::AutoEscape(respan!(ok!(self.parse_auto_escape()))), "filter" => ast::Stmt::FilterBlock(respan!(ok!(self.parse_filter_block()))), #[cfg(feature = "multi_template")] "block" => ast::Stmt::Block(respan!(ok!(self.parse_block()))), #[cfg(feature = "multi_template")] "extends" => ast::Stmt::Extends(respan!(ok!(self.parse_extends()))), #[cfg(feature = "multi_template")] "include" => ast::Stmt::Include(respan!(ok!(self.parse_include()))), #[cfg(feature = "multi_template")] "import" => ast::Stmt::Import(respan!(ok!(self.parse_import()))), #[cfg(feature = "multi_template")] "from" => ast::Stmt::FromImport(respan!(ok!(self.parse_from_import()))), #[cfg(feature = "macros")] "macro" => ast::Stmt::Macro(respan!(ok!(self.parse_macro()))), #[cfg(feature = "macros")] "call" => ast::Stmt::CallBlock(respan!(ok!(self.parse_call_block()))), "do" => ast::Stmt::Do(respan!(ok!(self.parse_do()))), name => syntax_error!("unknown statement {}", name), }) } fn parse_assign_name(&mut self) -> Result, Error> { let (id, span) = expect_token!(self, Token::Ident(name) => name, "identifier"); if RESERVED_NAMES.contains(&id) { syntax_error!("cannot assign to reserved variable name {}", id); } Ok(ast::Expr::Var(ast::Spanned::new(ast::Var { id }, span))) } fn parse_assignment(&mut self) -> Result, Error> { let span = self.stream.current_span(); let mut items = Vec::new(); let mut is_tuple = false; loop { if !items.is_empty() { expect_token!(self, Token::Comma, "`,`"); } if matches_token!( self, Token::ParenClose | Token::VariableEnd | Token::BlockEnd | Token::Ident("in") ) { break; } items.push(if skip_token!(self, Token::ParenOpen) { let rv = ok!(self.parse_assignment()); expect_token!(self, Token::ParenClose, "`)`"); rv } else { ok!(self.parse_assign_name()) }); if matches_token!(self, Token::Comma) { is_tuple = true; } else { break; } } if !is_tuple && items.len() == 1 { Ok(items.into_iter().next().unwrap()) } else { Ok(ast::Expr::List(Spanned::new( ast::List { items }, self.stream.expand_span(span), ))) } } fn parse_for_stmt(&mut self) -> Result, Error> { let target = ok!(self.parse_assignment()); expect_token!(self, Token::Ident("in"), "in"); let iter = ok!(self.parse_expr_noif()); let filter_expr = if skip_token!(self, Token::Ident("if")) { Some(ok!(self.parse_expr())) } else { None }; let recursive = skip_token!(self, Token::Ident("recursive")); expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endfor" | "else")))); let else_body = if skip_token!(self, Token::Ident("else")) { expect_token!(self, Token::BlockEnd, "end of block"); ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endfor")))) } else { Vec::new() }; ok!(self.stream.next()); Ok(ast::ForLoop { target, iter, filter_expr, recursive, body, else_body, }) } fn parse_if_cond(&mut self) -> Result, Error> { let expr = ok!(self.parse_expr_noif()); expect_token!(self, Token::BlockEnd, "end of block"); let true_body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endif" | "else" | "elif")))); let false_body = match ok!(self.stream.next()) { Some((Token::Ident("else"), _)) => { expect_token!(self, Token::BlockEnd, "end of block"); let rv = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endif")))); ok!(self.stream.next()); rv } Some((Token::Ident("elif"), span)) => vec![ast::Stmt::IfCond(Spanned::new( ok!(self.parse_if_cond()), self.stream.expand_span(span), ))], _ => Vec::new(), }; Ok(ast::IfCond { expr, true_body, false_body, }) } fn parse_with_block(&mut self) -> Result, Error> { let mut assignments = Vec::new(); while !matches_token!(self, Token::BlockEnd) { if !assignments.is_empty() { expect_token!(self, Token::Comma, "comma"); } let target = if skip_token!(self, Token::ParenOpen) { let assign = ok!(self.parse_assignment()); expect_token!(self, Token::ParenClose, "`)`"); assign } else { ok!(self.parse_assign_name()) }; expect_token!(self, Token::Assign, "assignment operator"); let expr = ok!(self.parse_expr()); assignments.push((target, expr)); } expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endwith")))); ok!(self.stream.next()); Ok(ast::WithBlock { assignments, body }) } fn parse_set(&mut self) -> Result, Error> { let (target, in_paren) = if skip_token!(self, Token::ParenOpen) { let assign = ok!(self.parse_assignment()); expect_token!(self, Token::ParenClose, "`)`"); (assign, true) } else { (ok!(self.parse_assign_name()), false) }; if !in_paren && matches_token!(self, Token::BlockEnd | Token::Pipe) { let filter = if skip_token!(self, Token::Pipe) { Some(ok!(self.parse_filter_chain())) } else { None }; expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endset")))); ok!(self.stream.next()); Ok(SetParseResult::SetBlock(ast::SetBlock { target, filter, body, })) } else { expect_token!(self, Token::Assign, "assignment operator"); let expr = ok!(self.parse_expr()); Ok(SetParseResult::Set(ast::Set { target, expr })) } } #[cfg(feature = "multi_template")] fn parse_block(&mut self) -> Result, Error> { if self.in_macro { syntax_error!("block tags in macros are not allowed"); } let (name, _) = expect_token!(self, Token::Ident(name) => name, "identifier"); if !self.blocks.insert(name) { syntax_error!("block '{}' defined twice", name); } expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endblock")))); ok!(self.stream.next()); if let Some((Token::Ident(trailing_name), _)) = ok!(self.stream.current()) { if *trailing_name != name { syntax_error!( "mismatching name on block. Got `{}`, expected `{}`", *trailing_name, name ); } ok!(self.stream.next()); } Ok(ast::Block { name, body }) } fn parse_auto_escape(&mut self) -> Result, Error> { let enabled = ok!(self.parse_expr()); expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endautoescape")))); ok!(self.stream.next()); Ok(ast::AutoEscape { enabled, body }) } fn parse_filter_chain(&mut self) -> Result, Error> { let mut filter = None; while !matches_token!(self, Token::BlockEnd) { if filter.is_some() { expect_token!(self, Token::Pipe, "`|`"); } let (name, span) = expect_token!(self, Token::Ident(name) => name, "identifier"); let args = if matches_token!(self, Token::ParenOpen) { ok!(self.parse_args()) } else { Vec::new() }; filter = Some(ast::Expr::Filter(Spanned::new( ast::Filter { name, expr: filter, args, }, self.stream.expand_span(span), ))); } filter.ok_or_else(|| syntax_error(Cow::Borrowed("expected a filter"))) } fn parse_filter_block(&mut self) -> Result, Error> { let filter = ok!(self.parse_filter_chain()); expect_token!(self, Token::BlockEnd, "end of block"); let body = ok!(self.subparse(&|tok| matches!(tok, Token::Ident("endfilter")))); ok!(self.stream.next()); Ok(ast::FilterBlock { filter, body }) } #[cfg(feature = "multi_template")] fn parse_extends(&mut self) -> Result, Error> { let name = ok!(self.parse_expr()); Ok(ast::Extends { name }) } #[cfg(feature = "multi_template")] fn parse_include(&mut self) -> Result, Error> { let name = ok!(self.parse_expr()); // with/without context is without meaning in MiniJinja, but for syntax // compatibility it's supported. if skip_token!(self, Token::Ident("without" | "with")) { expect_token!(self, Token::Ident("context"), "missing keyword"); } let ignore_missing = if skip_token!(self, Token::Ident("ignore")) { expect_token!(self, Token::Ident("missing"), "missing keyword"); if skip_token!(self, Token::Ident("without" | "with")) { expect_token!(self, Token::Ident("context"), "missing keyword"); } true } else { false }; Ok(ast::Include { name, ignore_missing, }) } #[cfg(feature = "multi_template")] fn parse_import(&mut self) -> Result, Error> { let expr = ok!(self.parse_expr()); expect_token!(self, Token::Ident("as"), "as"); let name = ok!(self.parse_expr()); Ok(ast::Import { expr, name }) } #[cfg(feature = "multi_template")] fn parse_from_import(&mut self) -> Result, Error> { let expr = ok!(self.parse_expr()); let mut names = Vec::new(); expect_token!(self, Token::Ident("import"), "import"); loop { if matches_token!(self, Token::BlockEnd) { break; } if !names.is_empty() { expect_token!(self, Token::Comma, "`,`"); } if matches_token!(self, Token::BlockEnd) { break; } let name = ok!(self.parse_assign_name()); let alias = if skip_token!(self, Token::Ident("as")) { Some(ok!(self.parse_assign_name())) } else { None }; names.push((name, alias)); } Ok(ast::FromImport { expr, names }) } #[cfg(feature = "macros")] fn parse_macro_args_and_defaults( &mut self, args: &mut Vec>, defaults: &mut Vec>, ) -> Result<(), Error> { loop { if skip_token!(self, Token::ParenClose) { break; } if !args.is_empty() { expect_token!(self, Token::Comma, "`,`"); if skip_token!(self, Token::ParenClose) { break; } } args.push(ok!(self.parse_assign_name())); if skip_token!(self, Token::Assign) { defaults.push(ok!(self.parse_expr())); } else if !defaults.is_empty() { expect_token!(self, Token::Assign, "`=`"); } } Ok(()) } #[cfg(feature = "macros")] fn parse_macro_or_call_block_body( &mut self, args: Vec>, defaults: Vec>, name: Option<&'a str>, ) -> Result, Error> { expect_token!(self, Token::BlockEnd, "end of block"); let old_in_macro = std::mem::replace(&mut self.in_macro, true); let body = ok!(self.subparse(&|tok| match tok { Token::Ident("endmacro") if name.is_some() => true, Token::Ident("endcall") if name.is_none() => true, _ => false, })); self.in_macro = old_in_macro; ok!(self.stream.next()); Ok(ast::Macro { name: name.unwrap_or("caller"), args, defaults, body, }) } #[cfg(feature = "macros")] fn parse_macro(&mut self) -> Result, Error> { let (name, _) = expect_token!(self, Token::Ident(name) => name, "identifier"); expect_token!(self, Token::ParenOpen, "`(`"); let mut args = Vec::new(); let mut defaults = Vec::new(); ok!(self.parse_macro_args_and_defaults(&mut args, &mut defaults)); self.parse_macro_or_call_block_body(args, defaults, Some(name)) } #[cfg(feature = "macros")] fn parse_call_block(&mut self) -> Result, Error> { let span = self.stream.last_span(); let mut args = Vec::new(); let mut defaults = Vec::new(); if skip_token!(self, Token::ParenOpen) { ok!(self.parse_macro_args_and_defaults(&mut args, &mut defaults)); } let call = match ok!(self.parse_expr()) { ast::Expr::Call(call) => call, expr => syntax_error!( "expected call expression in call block, got {}", expr.description() ), }; let macro_decl = ok!(self.parse_macro_or_call_block_body(args, defaults, None)); Ok(ast::CallBlock { call, macro_decl: Spanned::new(macro_decl, self.stream.expand_span(span)), }) } fn parse_do(&mut self) -> Result, Error> { let call = match ok!(self.parse_expr()) { ast::Expr::Call(call) => call, expr => syntax_error!( "expected call expression in call block, got {}", expr.description() ), }; Ok(ast::Do { call }) } fn subparse( &mut self, end_check: &dyn Fn(&Token) -> bool, ) -> Result>, Error> { let mut rv = Vec::new(); while let Some((token, span)) = ok!(self.stream.next()) { match token { Token::TemplateData(raw) => { rv.push(ast::Stmt::EmitRaw(Spanned::new(ast::EmitRaw { raw }, span))) } Token::VariableStart => { let expr = ok!(self.parse_expr()); rv.push(ast::Stmt::EmitExpr(Spanned::new( ast::EmitExpr { expr }, self.stream.expand_span(span), ))); expect_token!(self, Token::VariableEnd, "end of variable block"); } Token::BlockStart => { let (tok, _span) = match ok!(self.stream.current()) { Some(rv) => rv, None => syntax_error!("unexpected end of input, expected keyword"), }; if end_check(tok) { return Ok(rv); } rv.push(ok!(self.parse_stmt())); expect_token!(self, Token::BlockEnd, "end of block"); } _ => unreachable!("lexer produced garbage"), } } Ok(rv) } pub fn parse(&mut self) -> Result, Error> { let span = self.stream.last_span(); Ok(ast::Stmt::Template(Spanned::new( ast::Template { children: ok!(self.subparse(&|_| false)), }, self.stream.expand_span(span), ))) } } /// Parses a template #[cfg(feature = "unstable_machinery")] pub fn parse<'source>(source: &'source str, filename: &str) -> Result, Error> { parse_with_syntax(source, filename, Default::default()) } /// Parses a template with a specific syntax pub fn parse_with_syntax<'source>( source: &'source str, filename: &str, syntax: SyntaxConfig, ) -> Result, Error> { // we want to chop off a single newline at the end. This means that a template // by default does not end in a newline which is a useful property to allow // inline templates to work. If someone wants a trailing newline the expectation // is that the user adds it themselves for achieve consistency. let mut source = source; if source.ends_with('\n') { source = &source[..source.len() - 1]; } if source.ends_with('\r') { source = &source[..source.len() - 1]; } let mut parser = Parser::new(source, false, syntax); parser.parse().map_err(|mut err| { if err.line().is_none() { err.set_filename_and_span(filename, parser.stream.last_span()) } err }) } /// Parses an expression pub fn parse_expr(source: &str, syntax: SyntaxConfig) -> Result, Error> { let mut parser = Parser::new(source, true, syntax); parser .parse_expr() .and_then(|result| { if ok!(parser.stream.next()).is_some() { syntax_error!("unexpected input after expression") } else { Ok(result) } }) .map_err(|mut err| { if err.line().is_none() { err.set_filename_and_span("", parser.stream.last_span()) } err }) } minijinja-1.0.3/src/compiler/tokens.rs000064400000000000000000000101751046102023000160530ustar 00000000000000use std::fmt; /// Represents a token in the stream. #[derive(Debug)] #[cfg_attr( feature = "unstable_machinery_serde", derive(serde::Serialize), serde(tag = "name", content = "payload") )] pub enum Token<'a> { /// Raw template data. TemplateData(&'a str), /// Variable block start. VariableStart, /// Variable block end VariableEnd, /// Statement block start BlockStart, /// Statement block end BlockEnd, /// An identifier. Ident(&'a str), /// A borrowed string. Str(&'a str), /// An allocated string. String(String), /// An integer (limited to i64) Int(i64), /// A float Float(f64), /// A plus (`+`) operator. Plus, /// A plus (`-`) operator. Minus, /// A mul (`*`) operator. Mul, /// A div (`/`) operator. Div, /// A floor division (`//`) operator. FloorDiv, /// Power operator (`**`). Pow, /// A mod (`%`) operator. Mod, /// The bang (`!`) operator. Bang, /// A dot operator (`.`) Dot, /// The comma operator (`,`) Comma, /// The colon operator (`:`) Colon, /// The tilde operator (`~`) Tilde, /// The assignment operator (`=`) Assign, /// The pipe symbol. Pipe, /// `==` operator Eq, /// `!=` operator Ne, /// `>` operator Gt, /// `>=` operator Gte, /// `<` operator Lt, /// `<=` operator Lte, /// Open Bracket BracketOpen, /// Close Bracket BracketClose, /// Open Parenthesis ParenOpen, /// Close Parenthesis ParenClose, /// Open Brace BraceOpen, /// Close Brace BraceClose, } impl<'a> fmt::Display for Token<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Token::TemplateData(_) => f.write_str("template-data"), Token::VariableStart => f.write_str("start of variable block"), Token::VariableEnd => f.write_str("end of variable block"), Token::BlockStart => f.write_str("start of block"), Token::BlockEnd => f.write_str("end of block"), Token::Ident(_) => f.write_str("identifier"), Token::Str(_) | Token::String(_) => f.write_str("string"), Token::Int(_) => f.write_str("integer"), Token::Float(_) => f.write_str("float"), Token::Plus => f.write_str("`+`"), Token::Minus => f.write_str("`-`"), Token::Mul => f.write_str("`*`"), Token::Div => f.write_str("`/`"), Token::FloorDiv => f.write_str("`//`"), Token::Pow => f.write_str("`**`"), Token::Mod => f.write_str("`%`"), Token::Bang => f.write_str("`!`"), Token::Dot => f.write_str("`.`"), Token::Comma => f.write_str("`,`"), Token::Colon => f.write_str("`:`"), Token::Tilde => f.write_str("`~`"), Token::Assign => f.write_str("`=`"), Token::Pipe => f.write_str("`|`"), Token::Eq => f.write_str("`==`"), Token::Ne => f.write_str("`!=`"), Token::Gt => f.write_str("`>`"), Token::Gte => f.write_str("`>=`"), Token::Lt => f.write_str("`<`"), Token::Lte => f.write_str("`<=`"), Token::BracketOpen => f.write_str("`[`"), Token::BracketClose => f.write_str("`]`"), Token::ParenOpen => f.write_str("`(`"), Token::ParenClose => f.write_str("`)`"), Token::BraceOpen => f.write_str("`{{`"), Token::BraceClose => f.write_str("`}}`"), } } } /// Token span information #[derive(Clone, Copy, Default, PartialEq, Eq)] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub struct Span { pub start_line: u32, pub start_col: u32, pub start_offset: u32, pub end_line: u32, pub end_col: u32, pub end_offset: u32, } impl fmt::Debug for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, " @ {}:{}-{}:{}", self.start_line, self.start_col, self.end_line, self.end_col ) } } minijinja-1.0.3/src/custom_syntax.rs000064400000000000000000000103601046102023000156520ustar 00000000000000use aho_corasick::AhoCorasick; use std::borrow::Cow; use std::sync::Arc; use crate::compiler::lexer::StartMarker; use crate::error::{Error, ErrorKind}; /// The delimiter configuration for the environment and the parser. /// /// MiniJinja allows you to override the syntax configuration for /// templates by setting different delimiters. The end markers can /// be shared, but the start markers need to be distinct. It would /// thus not be valid to configure `{{` to be the marker for both /// variables and blocks. /// /// ``` /// # use minijinja::{Environment, Syntax}; /// let mut environment = Environment::new(); /// environment.set_syntax(Syntax { /// block_start: "\\BLOCK{".into(), /// block_end: "}".into(), /// variable_start: "\\VAR{".into(), /// variable_end: "}".into(), /// comment_start: "\\#{".into(), /// comment_end: "}".into(), /// }).unwrap(); /// ``` #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(docsrs, doc(cfg(feature = "custom_syntax")))] pub struct Syntax { /// The start of a block. By default it is `{%`. pub block_start: Cow<'static, str>, /// The end of a block. By default it is `%}`. pub block_end: Cow<'static, str>, /// The start of a variable. By default it is `{{`. pub variable_start: Cow<'static, str>, /// The end of a variable. By default it is `}}`. pub variable_end: Cow<'static, str>, /// The start of a comment. By default it is `{#`. pub comment_start: Cow<'static, str>, /// The end of a comment. By default it is `#}`. pub comment_end: Cow<'static, str>, } const DEFAULT_SYNTAX: Syntax = Syntax { block_start: Cow::Borrowed("{%"), block_end: Cow::Borrowed("%}"), variable_start: Cow::Borrowed("{{"), variable_end: Cow::Borrowed("}}"), comment_start: Cow::Borrowed("{#"), comment_end: Cow::Borrowed("#}"), }; impl Default for Syntax { fn default() -> Self { DEFAULT_SYNTAX } } impl Syntax { /// Creates a new syntax configuration with custom delimiters. pub(crate) fn compile(self) -> Result { if self == DEFAULT_SYNTAX { return Ok(SyntaxConfig::default()); } ok!(self.check_delimiters()); let mut delimiter_order = [ StartMarker::Variable, StartMarker::Block, StartMarker::Comment, ]; delimiter_order.sort_by_key(|marker| { std::cmp::Reverse(match marker { StartMarker::Variable => self.variable_start.len(), StartMarker::Block => self.block_start.len(), StartMarker::Comment => self.comment_start.len(), }) }); let aho_corasick = ok!(AhoCorasick::builder() .match_kind(aho_corasick::MatchKind::LeftmostLongest) .build([ &self.variable_start as &str, &self.block_start as &str, &self.comment_start as &str, ]) .map_err(|_| ErrorKind::InvalidDelimiter.into())); Ok(Arc::new(SyntaxConfigInternal { syntax: self, aho_corasick: Some(aho_corasick), start_delimiters_order: delimiter_order, })) } /// block, variable and comment start strings must be different fn check_delimiters(&self) -> Result<(), Error> { if self.block_start != self.variable_start && self.block_start != self.comment_start && self.variable_start != self.comment_start { Ok(()) } else { Err(ErrorKind::InvalidDelimiter.into()) } } } /// Internal configuration for the environment and the parser. #[derive(Debug)] pub struct SyntaxConfigInternal { pub(crate) syntax: Syntax, pub(crate) start_delimiters_order: [StartMarker; 3], pub(crate) aho_corasick: Option, } /// Configurable syntax config pub type SyntaxConfig = Arc; impl Default for SyntaxConfigInternal { fn default() -> Self { SyntaxConfigInternal { syntax: Syntax::default(), start_delimiters_order: [ StartMarker::Variable, StartMarker::Block, StartMarker::Comment, ], aho_corasick: None, } } } minijinja-1.0.3/src/debug.rs000064400000000000000000000052441046102023000140250ustar 00000000000000use std::collections::BTreeMap; use std::fmt; use crate::compiler::tokens::Span; use crate::error::ErrorKind; use crate::value::Value; /// This is a snapshot of the debug information. #[cfg_attr(docsrs, doc(cfg(feature = "debug")))] #[derive(Default)] pub(crate) struct DebugInfo { pub(crate) template_source: Option, pub(crate) referenced_locals: BTreeMap, } struct VarPrinter<'x>(&'x BTreeMap); impl<'x> fmt::Debug for VarPrinter<'x> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.0.is_empty() { return f.write_str("No referenced variables"); } let mut m = f.debug_struct("Referenced variables:"); let mut vars = self.0.iter().collect::>(); vars.sort_by_key(|x| x.0); for (key, value) in vars { m.field(key, value); } m.finish() } } impl DebugInfo { pub fn source(&self) -> Option<&str> { self.template_source.as_deref() } } pub(super) fn render_debug_info( f: &mut fmt::Formatter, name: Option<&str>, kind: ErrorKind, line: Option, span: Option, info: &DebugInfo, ) -> fmt::Result { if let Some(source) = info.source() { let title = format!( " {} ", name.unwrap_or_default() .rsplit(&['/', '\\']) .next() .unwrap_or("Template Source") ); ok!(writeln!(f)); writeln!(f, "{:-^1$}", title, 79).unwrap(); let lines: Vec<_> = source.lines().enumerate().collect(); let idx = line.unwrap_or(1).saturating_sub(1); let skip = idx.saturating_sub(3); let pre = lines.iter().skip(skip).take(3.min(idx)).collect::>(); let post = lines.iter().skip(idx + 1).take(3).collect::>(); for (idx, line) in pre { writeln!(f, "{:>4} | {}", idx + 1, line).unwrap(); } writeln!(f, "{:>4} > {}", idx + 1, lines[idx].1).unwrap(); if let Some(span) = span { if span.start_line == span.end_line { ok!(writeln!( f, " i {}{} {}", " ".repeat(span.start_col as usize), "^".repeat(span.end_col as usize - span.start_col as usize), kind, )); } } for (idx, line) in post { writeln!(f, "{:>4} | {}", idx + 1, line).unwrap(); } write!(f, "{:~^1$}", "", 79).unwrap(); } ok!(writeln!(f)); ok!(writeln!(f, "{:#?}", VarPrinter(&info.referenced_locals))); write!(f, "{:-^1$}", "", 79).unwrap(); Ok(()) } minijinja-1.0.3/src/defaults.rs000064400000000000000000000170651046102023000145520ustar 00000000000000use std::borrow::Cow; use std::collections::BTreeMap; use crate::error::Error; use crate::filters::{self, BoxedFilter}; use crate::output::Output; use crate::tests::{self, BoxedTest}; use crate::utils::{write_escaped, AutoEscape}; use crate::value::Value; use crate::vm::State; pub(crate) fn no_auto_escape(_: &str) -> AutoEscape { AutoEscape::None } /// The default logic for auto escaping based on file extension. /// /// * [`Html`](AutoEscape::Html): `.html`, `.htm`, `.xml` #[cfg_attr( feature = "json", doc = r" * [`Json`](AutoEscape::Json): `.json`, `.json5`, `.js`, `.yaml`, `.yml`" )] /// * [`None`](AutoEscape::None): _all others_ pub fn default_auto_escape_callback(name: &str) -> AutoEscape { match name.rsplit('.').next() { Some("html" | "htm" | "xml") => AutoEscape::Html, #[cfg(feature = "json")] Some("json" | "json5" | "js" | "yaml" | "yml") => AutoEscape::Json, _ => AutoEscape::None, } } /// The default formatter. /// /// This formatter takes a value and directly writes it into the output format /// while honoring the requested auto escape format of the state. If the /// value is already marked as safe, it's handled as if no auto escaping /// was requested. /// /// * [`Html`](AutoEscape::Html): performs HTML escaping #[cfg_attr( feature = "json", doc = r" * [`Json`](AutoEscape::Json): serializes values to JSON" )] /// * [`None`](AutoEscape::None): no escaping /// * [`Custom(..)`](AutoEscape::Custom): results in an error pub fn escape_formatter(out: &mut Output, state: &State, value: &Value) -> Result<(), Error> { write_escaped(out, state.auto_escape(), value) } pub(crate) fn get_builtin_filters() -> BTreeMap, filters::BoxedFilter> { let mut rv = BTreeMap::new(); rv.insert("safe".into(), BoxedFilter::new(filters::safe)); let escape = BoxedFilter::new(filters::escape); rv.insert("escape".into(), escape.clone()); rv.insert("e".into(), escape); #[cfg(feature = "builtins")] { rv.insert("lower".into(), BoxedFilter::new(filters::lower)); rv.insert("upper".into(), BoxedFilter::new(filters::upper)); rv.insert("title".into(), BoxedFilter::new(filters::title)); rv.insert("capitalize".into(), BoxedFilter::new(filters::capitalize)); rv.insert("replace".into(), BoxedFilter::new(filters::replace)); let length = BoxedFilter::new(filters::length); rv.insert("length".into(), length.clone()); rv.insert("count".into(), length); rv.insert("dictsort".into(), BoxedFilter::new(filters::dictsort)); rv.insert("items".into(), BoxedFilter::new(filters::items)); rv.insert("reverse".into(), BoxedFilter::new(filters::reverse)); rv.insert("trim".into(), BoxedFilter::new(filters::trim)); rv.insert("join".into(), BoxedFilter::new(filters::join)); rv.insert("default".into(), BoxedFilter::new(filters::default)); rv.insert("round".into(), BoxedFilter::new(filters::round)); rv.insert("abs".into(), BoxedFilter::new(filters::abs)); rv.insert("attr".into(), BoxedFilter::new(filters::attr)); rv.insert("first".into(), BoxedFilter::new(filters::first)); rv.insert("last".into(), BoxedFilter::new(filters::last)); rv.insert("min".into(), BoxedFilter::new(filters::min)); rv.insert("max".into(), BoxedFilter::new(filters::max)); rv.insert("sort".into(), BoxedFilter::new(filters::sort)); rv.insert("d".into(), BoxedFilter::new(filters::default)); rv.insert("list".into(), BoxedFilter::new(filters::list)); rv.insert("bool".into(), BoxedFilter::new(filters::bool)); rv.insert("batch".into(), BoxedFilter::new(filters::batch)); rv.insert("slice".into(), BoxedFilter::new(filters::slice)); rv.insert("indent".into(), BoxedFilter::new(filters::indent)); rv.insert("select".into(), BoxedFilter::new(filters::select)); rv.insert("reject".into(), BoxedFilter::new(filters::reject)); rv.insert("selectattr".into(), BoxedFilter::new(filters::selectattr)); rv.insert("rejectattr".into(), BoxedFilter::new(filters::rejectattr)); rv.insert("map".into(), BoxedFilter::new(filters::map)); rv.insert("unique".into(), BoxedFilter::new(filters::unique)); rv.insert("pprint".into(), BoxedFilter::new(filters::pprint)); #[cfg(feature = "json")] { rv.insert("tojson".into(), BoxedFilter::new(filters::tojson)); } #[cfg(feature = "urlencode")] { rv.insert("urlencode".into(), BoxedFilter::new(filters::urlencode)); } } rv } pub(crate) fn get_builtin_tests() -> BTreeMap, BoxedTest> { let mut rv = BTreeMap::new(); rv.insert("undefined".into(), BoxedTest::new(tests::is_undefined)); rv.insert("defined".into(), BoxedTest::new(tests::is_defined)); rv.insert("none".into(), BoxedTest::new(tests::is_none)); let is_safe = BoxedTest::new(tests::is_safe); rv.insert("safe".into(), is_safe.clone()); rv.insert("escaped".into(), is_safe); #[cfg(feature = "builtins")] { rv.insert("odd".into(), BoxedTest::new(tests::is_odd)); rv.insert("even".into(), BoxedTest::new(tests::is_even)); rv.insert("number".into(), BoxedTest::new(tests::is_number)); rv.insert("string".into(), BoxedTest::new(tests::is_string)); rv.insert("sequence".into(), BoxedTest::new(tests::is_sequence)); rv.insert("mapping".into(), BoxedTest::new(tests::is_mapping)); rv.insert( "startingwith".into(), BoxedTest::new(tests::is_startingwith), ); rv.insert("endingwith".into(), BoxedTest::new(tests::is_endingwith)); // operators let is_eq = BoxedTest::new(tests::is_eq); rv.insert("eq".into(), is_eq.clone()); rv.insert("equalto".into(), is_eq.clone()); rv.insert("==".into(), is_eq); let is_ne = BoxedTest::new(tests::is_ne); rv.insert("ne".into(), is_ne.clone()); rv.insert("!=".into(), is_ne); let is_lt = BoxedTest::new(tests::is_lt); rv.insert("lt".into(), is_lt.clone()); rv.insert("lessthan".into(), is_lt.clone()); rv.insert("<".into(), is_lt); let is_le = BoxedTest::new(tests::is_le); rv.insert("le".into(), is_le.clone()); rv.insert("<=".into(), is_le); let is_gt = BoxedTest::new(tests::is_gt); rv.insert("gt".into(), is_gt.clone()); rv.insert("greaterthan".into(), is_gt.clone()); rv.insert(">".into(), is_gt); let is_ge = BoxedTest::new(tests::is_ge); rv.insert("ge".into(), is_ge.clone()); rv.insert(">=".into(), is_ge); rv.insert("in".into(), BoxedTest::new(tests::is_in)); rv.insert("true".into(), BoxedTest::new(tests::is_true)); rv.insert("false".into(), BoxedTest::new(tests::is_false)); rv.insert("filter".into(), BoxedTest::new(tests::is_filter)); rv.insert("test".into(), BoxedTest::new(tests::is_test)); } rv } pub(crate) fn get_globals() -> BTreeMap, Value> { #[allow(unused_mut)] let mut rv = BTreeMap::new(); #[cfg(feature = "builtins")] { use crate::functions::{self, BoxedFunction}; rv.insert( "range".into(), BoxedFunction::new(functions::range).to_value(), ); rv.insert( "dict".into(), BoxedFunction::new(functions::dict).to_value(), ); rv.insert( "debug".into(), BoxedFunction::new(functions::debug).to_value(), ); } rv } minijinja-1.0.3/src/environment.rs000064400000000000000000000574351046102023000153140ustar 00000000000000use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt; use std::sync::Arc; use serde::Serialize; use crate::compiler::codegen::CodeGenerator; use crate::compiler::lexer::SyntaxConfig; use crate::compiler::parser::parse_expr; use crate::error::{attach_basic_debug_info, Error, ErrorKind}; use crate::expression::Expression; use crate::output::Output; use crate::template::{CompiledTemplate, CompiledTemplateRef, Template}; use crate::utils::{AutoEscape, BTreeMapKeysDebug, UndefinedBehavior}; use crate::value::{FunctionArgs, FunctionResult, Value}; use crate::vm::State; use crate::{defaults, filters, functions, tests}; type AutoEscapeFunc = dyn Fn(&str) -> AutoEscape + Sync + Send; type FormatterFunc = dyn Fn(&mut Output, &State, &Value) -> Result<(), Error> + Sync + Send; /// An abstraction that holds the engine configuration. /// /// This object holds the central configuration state for templates. It is also /// the container for all loaded templates. /// /// The environment holds references to the source the templates were created from. /// This makes it very inconvenient to pass around unless the templates are static /// strings. /// /// There are generally two ways to construct an environment: /// /// * [`Environment::new`] creates an environment preconfigured with sensible /// defaults. It will contain all built-in filters, tests and globals as well /// as a callback for auto escaping based on file extension. /// * [`Environment::empty`] creates a completely blank environment. #[derive(Clone)] pub struct Environment<'source> { templates: TemplateStore<'source>, filters: BTreeMap, filters::BoxedFilter>, tests: BTreeMap, tests::BoxedTest>, globals: BTreeMap, Value>, default_auto_escape: Arc, undefined_behavior: UndefinedBehavior, formatter: Arc, #[cfg(feature = "debug")] debug: bool, #[cfg(feature = "fuel")] fuel: Option, } impl<'source> Default for Environment<'source> { fn default() -> Self { Environment::empty() } } impl<'source> fmt::Debug for Environment<'source> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Environment") .field("globals", &self.globals) .field("tests", &BTreeMapKeysDebug(&self.tests)) .field("filters", &BTreeMapKeysDebug(&self.filters)) .field("templates", &self.templates) .finish() } } impl<'source> Environment<'source> { /// Creates a new environment with sensible defaults. /// /// This environment does not yet contain any templates but it will have all /// the default filters, tests and globals loaded. If you do not want any /// default configuration you can use the alternative /// [`empty`](Environment::empty) method. pub fn new() -> Environment<'source> { Environment { templates: TemplateStore::default(), filters: defaults::get_builtin_filters(), tests: defaults::get_builtin_tests(), globals: defaults::get_globals(), default_auto_escape: Arc::new(defaults::default_auto_escape_callback), undefined_behavior: UndefinedBehavior::default(), formatter: Arc::new(defaults::escape_formatter), #[cfg(feature = "debug")] debug: cfg!(debug_assertions), #[cfg(feature = "fuel")] fuel: None, } } /// Creates a completely empty environment. /// /// This environment has no filters, no templates, no globals and no default /// logic for auto escaping configured. pub fn empty() -> Environment<'source> { Environment { templates: TemplateStore::default(), filters: Default::default(), tests: Default::default(), globals: Default::default(), default_auto_escape: Arc::new(defaults::no_auto_escape), undefined_behavior: UndefinedBehavior::default(), formatter: Arc::new(defaults::escape_formatter), #[cfg(feature = "debug")] debug: cfg!(debug_assertions), #[cfg(feature = "fuel")] fuel: None, } } /// Loads a template from a string into the environment. /// /// The `name` parameter defines the name of the template which identifies /// it. To look up a loaded template use the [`get_template`](Self::get_template) /// method. /// /// ``` /// # use minijinja::Environment; /// let mut env = Environment::new(); /// env.add_template("index.html", "Hello {{ name }}!").unwrap(); /// ``` /// /// Note that there are situations where the interface of this method is /// too restrictive as you need to hold on to the strings for the lifetime /// of the environment. #[cfg_attr( feature = "loader", doc = "To address this restriction use [`add_template_owned`](Self::add_template_owned)." )] pub fn add_template(&mut self, name: &'source str, source: &'source str) -> Result<(), Error> { self.templates.insert(name, source) } /// Adds a template without without borrowing. /// /// This lets you place an owned [`String`] in the environment rather than the /// borrowed `&str` without having to worry about lifetimes. /// /// ``` /// # use minijinja::Environment; /// let mut env = Environment::new(); /// env.add_template_owned("index.html".to_string(), "Hello {{ name }}!".to_string()).unwrap(); /// ``` /// /// **Note**: the name is a bit of a misnomer as this API also allows to borrow too as /// the parameters are actually [`Cow`]. #[cfg(feature = "loader")] #[cfg_attr(docsrs, doc(cfg(feature = "loader")))] pub fn add_template_owned(&mut self, name: N, source: S) -> Result<(), Error> where N: Into>, S: Into>, { self.templates.insert_cow(name.into(), source.into()) } /// Register a template loader as source of templates. /// /// When a template loader is registered, the environment gains the ability /// to dynamically load templates. The loader is invoked with the name of /// the template. If this template exists `Ok(Some(template_source))` has /// to be returned, otherwise `Ok(None)`. Once a template has been loaded /// it's stored on the environment. This means the loader is only invoked /// once per template name. /// /// For loading templates from the file system, you can use the /// [`path_loader`](crate::path_loader) function. /// /// # Example /// /// ```rust /// # use minijinja::Environment; /// fn create_env() -> Environment<'static> { /// let mut env = Environment::new(); /// env.set_loader(|name| { /// if name == "layout.html" { /// Ok(Some("...".into())) /// } else { /// Ok(None) /// } /// }); /// env /// } /// ``` #[cfg(feature = "loader")] #[cfg_attr(docsrs, doc(cfg(feature = "loader")))] pub fn set_loader(&mut self, f: F) where F: Fn(&str) -> Result, Error> + Send + Sync + 'static, { self.templates.set_loader(f); } /// Removes a template by name. pub fn remove_template(&mut self, name: &str) { self.templates.remove(name); } /// Removes all stored templates. /// /// This method is mainly useful when combined with a loader as it causes /// the loader to "reload" templates. By calling this method one can trigger /// a reload. pub fn clear_templates(&mut self) { self.templates.clear(); } /// Fetches a template by name. /// /// This requires that the template has been loaded with /// [`add_template`](Environment::add_template) beforehand. If the template was /// not loaded an error of kind `TemplateNotFound` is returned. If a loaded was /// added to the engine this can also dynamically load templates. /// /// ``` /// # use minijinja::{Environment, context}; /// let mut env = Environment::new(); /// env.add_template("hello.txt", "Hello {{ name }}!").unwrap(); /// let tmpl = env.get_template("hello.txt").unwrap(); /// println!("{}", tmpl.render(context!{ name => "World" }).unwrap()); /// ``` pub fn get_template(&self, name: &str) -> Result, Error> { let compiled = ok!(self.templates.get(name)); Ok(Template::new( self, CompiledTemplateRef::Borrowed(compiled), self.initial_auto_escape(name), )) } /// Loads a template from a string. /// /// In some cases you really only need to work with (eg: render) a template to be /// rendered once only. /// /// ``` /// # use minijinja::{Environment, context}; /// let env = Environment::new(); /// let tmpl = env.template_from_named_str("template_name", "Hello {{ name }}").unwrap(); /// let rv = tmpl.render(context! { name => "World" }); /// println!("{}", rv.unwrap()); /// ``` pub fn template_from_named_str( &self, name: &'source str, source: &'source str, ) -> Result, Error> { Ok(Template::new( self, CompiledTemplateRef::Owned(Arc::new(ok!(CompiledTemplate::new( name, source, self._syntax_config().clone(), )))), self.initial_auto_escape(name), )) } /// Loads a template from a string, with name ``. /// /// This is a shortcut to [`template_from_named_str`](Self::template_from_named_str) /// with name set to ``. pub fn template_from_str(&self, source: &'source str) -> Result, Error> { self.template_from_named_str("", source) } /// Parses and renders a template from a string in one go with name. /// /// Like [`render_str`](Self::render_str), but provide a name for the /// template to be used instead of the default ``. This is an /// alias for [`template_from_named_str`](Self::template_from_named_str) paired with /// [`render`](Template::render). /// /// ``` /// # use minijinja::{Environment, context}; /// let env = Environment::new(); /// let rv = env.render_named_str( /// "template_name", /// "Hello {{ name }}", /// context!{ name => "World" } /// ); /// println!("{}", rv.unwrap()); /// ``` /// /// **Note on values:** The [`Value`] type implements `Serialize` and can be /// efficiently passed to render. It does not undergo actual serialization. pub fn render_named_str( &self, name: &str, source: &str, ctx: S, ) -> Result { ok!(self.template_from_named_str(name, source)).render(ctx) } /// Parses and renders a template from a string in one go. /// /// In some cases you really only need a template to be rendered once from /// a string and returned. The internal name of the template is ``. /// /// This is an alias for [`template_from_str`](Self::template_from_str) paired with /// [`render`](Template::render). /// /// **Note on values:** The [`Value`] type implements `Serialize` and can be /// efficiently passed to render. It does not undergo actual serialization. pub fn render_str(&self, source: &str, ctx: S) -> Result { // reduce total amount of code faling under mono morphization into // this function, and share the rest in _eval. ok!(self.template_from_str(source)).render(ctx) } /// Sets a new function to select the default auto escaping. /// /// This function is invoked when templates are loaded from the environment /// to determine the default auto escaping behavior. The function is /// invoked with the name of the template and can make an initial auto /// escaping decision based on that. The default implementation /// ([`default_auto_escape_callback`](defaults::default_auto_escape_callback)) /// turn on escaping depending on the file extension. /// /// ``` /// # use minijinja::{Environment, AutoEscape}; /// # let mut env = Environment::new(); /// env.set_auto_escape_callback(|name| { /// if matches!(name.rsplit('.').next().unwrap_or(""), "html" | "htm" | "aspx") { /// AutoEscape::Html /// } else { /// AutoEscape::None /// } /// }); /// ``` pub fn set_auto_escape_callback(&mut self, f: F) where F: Fn(&str) -> AutoEscape + 'static + Sync + Send, { self.default_auto_escape = Arc::new(f); } /// Changes the undefined behavior. /// /// This changes the runtime behavior of [`undefined`](Value::UNDEFINED) values in /// the template engine. For more information see [`UndefinedBehavior`]. The /// default is [`UndefinedBehavior::Lenient`]. pub fn set_undefined_behavior(&mut self, behavior: UndefinedBehavior) { self.undefined_behavior = behavior; } /// Returns the current undefined behavior. /// /// This is particularly useful if a filter function or similar wants to change its /// behavior with regards to undefined values. #[inline(always)] pub fn undefined_behavior(&self) -> UndefinedBehavior { self.undefined_behavior } /// Sets a different formatter function. /// /// The formatter is invoked to format the given value into the provided /// [`Output`]. The default implementation is /// [`escape_formatter`](defaults::escape_formatter). /// /// When implementing a custom formatter it depends on if auto escaping /// should be supported or not. If auto escaping should be supported then /// it's easiest to just wrap the default formatter. The /// following example swaps out `None` values before rendering for /// `Undefined` which renders as an empty string instead. /// /// The current value of the auto escape flag can be retrieved directly /// from the [`State`]. /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// use minijinja::escape_formatter; /// use minijinja::value::Value; /// /// env.set_formatter(|out, state, value| { /// escape_formatter( /// out, /// state, /// if value.is_none() { /// &Value::UNDEFINED /// } else { /// value /// }, /// ) ///}); /// # assert_eq!(env.render_str("{{ none }}", ()).unwrap(), ""); /// ``` pub fn set_formatter(&mut self, f: F) where F: Fn(&mut Output, &State, &Value) -> Result<(), Error> + 'static + Sync + Send, { self.formatter = Arc::new(f); } /// Enable or disable the debug mode. /// /// When the debug mode is enabled the engine will dump out some of the /// execution state together with the source information of the executing /// template when an error is created. The cost of this is relatively /// high as the data including the template source is cloned. /// /// When this is enabled templates will print debug information with source /// context when the error is printed. /// /// This requires the `debug` feature. This is enabled by default if /// debug assertions are enabled and false otherwise. #[cfg(feature = "debug")] #[cfg_attr(docsrs, doc(cfg(feature = "debug")))] pub fn set_debug(&mut self, enabled: bool) { self.debug = enabled; } /// Returns the current value of the debug flag. #[cfg(feature = "debug")] pub fn debug(&self) -> bool { self.debug } /// Sets the optional fuel of the engine. /// /// When MiniJinja is compiled with the `fuel` feature then every /// instruction consumes a certain amount of fuel. Usually `1`, some will /// consume no fuel. By default the engine has the fuel feature disabled /// (`None`). To turn on fuel set something like `Some(50000)` which will /// allow 50.000 instructions to execute before running out of fuel. /// /// To find out how much fuel is consumed, you can access the fuel levels /// from the [`State`](crate::State). /// /// Fuel consumed per-render. #[cfg(feature = "fuel")] #[cfg_attr(docsrs, doc(cfg(feature = "fuel")))] pub fn set_fuel(&mut self, fuel: Option) { self.fuel = fuel; } /// Returns the configured fuel. #[cfg(feature = "fuel")] #[cfg_attr(docsrs, doc(cfg(feature = "fuel")))] pub fn fuel(&self) -> Option { self.fuel } /// Sets the syntax for the environment. /// /// Note that when `source` is used, the syntax is held on the underlying source /// which means that the actual source needs to have it's syntax changed. /// /// See [`Syntax`](crate::Syntax) for more information. #[cfg(feature = "custom_syntax")] #[cfg_attr(docsrs, doc(cfg(feature = "custom_syntax")))] pub fn set_syntax(&mut self, syntax: crate::custom_syntax::Syntax) -> Result<(), Error> { self.templates.syntax_config = ok!(syntax.compile()); Ok(()) } /// Returns the current syntax. #[cfg(feature = "custom_syntax")] #[cfg_attr(docsrs, doc(cfg(feature = "custom_syntax")))] pub fn syntax(&self) -> &crate::custom_syntax::Syntax { &self._syntax_config().syntax } fn _syntax_config(&self) -> &SyntaxConfig { &self.templates.syntax_config } /// Compiles an expression. /// /// This lets one compile an expression in the template language and /// receive the output. This lets one use the expressions of the language /// be used as a minimal scripting language. For more information and an /// example see [`Expression`]. pub fn compile_expression(&self, expr: &'source str) -> Result, Error> { attach_basic_debug_info(self._compile_expression(expr), expr) } fn _compile_expression(&self, expr: &'source str) -> Result, Error> { let ast = ok!(parse_expr(expr, self._syntax_config().clone())); let mut gen = CodeGenerator::new("", expr); gen.compile_expr(&ast); let (instructions, _) = gen.finish(); Ok(Expression::new(self, instructions)) } /// Adds a new filter function. /// /// Filter functions are functions that can be applied to values in /// templates. For details about filters have a look at /// [`Filter`](crate::filters::Filter). pub fn add_filter(&mut self, name: N, f: F) where N: Into>, // the crazy bounds here exist to enable borrowing in closures F: filters::Filter + for<'a> filters::Filter>::Output>, Rv: FunctionResult, Args: for<'a> FunctionArgs<'a>, { self.filters .insert(name.into(), filters::BoxedFilter::new(f)); } /// Removes a filter by name. pub fn remove_filter(&mut self, name: &str) { self.filters.remove(name); } /// Adds a new test function. /// /// Test functions are similar to filters but perform a check on a value /// where the return value is always true or false. For details about tests /// have a look at [`Test`](crate::tests::Test). pub fn add_test(&mut self, name: N, f: F) where N: Into>, // the crazy bounds here exist to enable borrowing in closures F: tests::Test + for<'a> tests::Test>::Output>, Rv: tests::TestResult, Args: for<'a> FunctionArgs<'a>, { self.tests.insert(name.into(), tests::BoxedTest::new(f)); } /// Removes a test by name. pub fn remove_test(&mut self, name: &str) { self.tests.remove(name); } /// Adds a new global function. /// /// For details about functions have a look at [`functions`]. Note that /// functions and other global variables share the same namespace. /// For more details about functions have a look at /// [`Function`](crate::functions::Function). pub fn add_function(&mut self, name: N, f: F) where N: Into>, // the crazy bounds here exist to enable borrowing in closures F: functions::Function + for<'a> functions::Function>::Output>, Rv: FunctionResult, Args: for<'a> FunctionArgs<'a>, { self.add_global(name.into(), Value::from_function(f)) } /// Adds a global variable. pub fn add_global(&mut self, name: N, value: V) where N: Into>, V: Into, { self.globals.insert(name.into(), value.into()); } /// Removes a global function or variable by name. pub fn remove_global(&mut self, name: &str) { self.globals.remove(name); } /// Returns an empty [`State`] for testing purposes and similar. pub fn empty_state(&self) -> State<'_, '_> { State::new_for_env(self) } /// Looks up a function. pub(crate) fn get_global(&self, name: &str) -> Option { self.globals.get(name).cloned() } /// Looks up a filter. pub(crate) fn get_filter(&self, name: &str) -> Option<&filters::BoxedFilter> { self.filters.get(name) } /// Looks up a test function. pub(crate) fn get_test(&self, name: &str) -> Option<&tests::BoxedTest> { self.tests.get(name) } pub(crate) fn initial_auto_escape(&self, name: &str) -> AutoEscape { (self.default_auto_escape)(name) } /// Formats a value into the final format. /// /// This step is called finalization in Jinja2 but since we are writing into /// the output stream rather than converting values, it's renamed to format /// here. pub(crate) fn format( &self, value: &Value, state: &State, out: &mut Output, ) -> Result<(), Error> { if value.is_undefined() && matches!(self.undefined_behavior, UndefinedBehavior::Strict) { Err(Error::from(ErrorKind::UndefinedError)) } else { (self.formatter)(out, state, value) } } } #[cfg(not(feature = "loader"))] mod basic_store { use super::*; #[derive(Clone, Default)] pub struct BasicStore<'source> { pub syntax_config: SyntaxConfig, map: BTreeMap<&'source str, Arc>>, } impl<'source> fmt::Debug for BasicStore<'source> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { BTreeMapKeysDebug(&self.map).fmt(f) } } impl<'source> BasicStore<'source> { pub fn insert(&mut self, name: &'source str, source: &'source str) -> Result<(), Error> { self.map.insert( name, Arc::new(ok!(CompiledTemplate::new( name, source, self.syntax_config.clone() ))), ); Ok(()) } pub fn remove(&mut self, name: &str) { self.map.remove(name); } pub fn clear(&mut self) { self.map.clear(); } pub fn get(&self, name: &str) -> Result<&CompiledTemplate<'source>, Error> { self.map .get(name) .map(|x| &**x) .ok_or_else(|| Error::new_not_found(name)) } } } #[cfg(not(feature = "loader"))] use self::basic_store::BasicStore as TemplateStore; #[cfg(feature = "loader")] use crate::loader::LoaderStore as TemplateStore; minijinja-1.0.3/src/error.rs000064400000000000000000000307541046102023000140740ustar 00000000000000use std::borrow::Cow; use std::fmt; use crate::compiler::tokens::Span; /// Represents template errors. /// /// If debug mode is enabled a template error contains additional debug /// information that can be displayed by formatting an error with the /// alternative formatting (``format!("{:#}", err)``). That information /// is also shown for the [`Debug`] display where the extended information /// is hidden when the alternative formatting is used. /// /// Since MiniJinja takes advantage of chained errors it's recommended /// to render the entire chain to better understand the causes. /// /// # Example /// /// Here is an example of you might want to render errors: /// /// ```rust /// # let mut env = minijinja::Environment::new(); /// # env.add_template("", ""); /// # let template = env.get_template("").unwrap(); let ctx = (); /// match template.render(ctx) { /// Ok(result) => println!("{}", result), /// Err(err) => { /// eprintln!("Could not render template: {:#}", err); /// // render causes as well /// let mut err = &err as &dyn std::error::Error; /// while let Some(next_err) = err.source() { /// eprintln!(); /// eprintln!("caused by: {:#}", next_err); /// err = next_err; /// } /// } /// } /// ``` pub struct Error { repr: Box, } /// The internal error data struct ErrorRepr { kind: ErrorKind, detail: Option>, name: Option, lineno: usize, span: Option, source: Option>, #[cfg(feature = "debug")] debug_info: Option, } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut err = f.debug_struct("Error"); err.field("kind", &self.kind()); if let Some(ref detail) = self.repr.detail { err.field("detail", detail); } if let Some(ref name) = self.name() { err.field("name", name); } if let Some(line) = self.line() { err.field("line", &line); } if let Some(ref source) = std::error::Error::source(self) { err.field("source", source); } ok!(err.finish()); // so this is a bit questionablem, but because of how commonly errors are just // unwrapped i think it's sensible to spit out the debug info following the // error struct dump. #[cfg(feature = "debug")] { if !f.alternate() { if let Some(info) = self.debug_info() { ok!(writeln!(f)); ok!(crate::debug::render_debug_info( f, self.name(), self.kind(), self.line(), self.span(), info, )); ok!(writeln!(f)); } } } Ok(()) } } /// An enum describing the error kind. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum ErrorKind { /// A non primitive value was encountered where one was expected. NonPrimitive, /// A value is not valid for a key in a map. NonKey, /// An invalid operation was attempted. InvalidOperation, /// The template has a syntax error SyntaxError, /// A template was not found. TemplateNotFound, /// Too many arguments were passed to a function. TooManyArguments, /// A expected argument was missing MissingArgument, /// A filter is unknown UnknownFilter, /// A test is unknown UnknownTest, /// A function is unknown UnknownFunction, /// Un unknown method was called UnknownMethod, /// A bad escape sequence in a string was encountered. BadEscape, /// An operation on an undefined value was attempted. UndefinedError, /// Not able to serialize this value. BadSerialization, /// An error happened in an include. BadInclude, /// An error happened in a super block. EvalBlock, /// Unable to unpack a value. CannotUnpack, /// Failed writing output. WriteFailure, /// Engine ran out of fuel #[cfg(feature = "fuel")] OutOfFuel, #[cfg(feature = "custom_syntax")] /// Error creating aho-corasick delimiters InvalidDelimiter, /// An unknown block was called #[cfg(feature = "multi_template")] UnknownBlock, } impl ErrorKind { fn description(self) -> &'static str { match self { ErrorKind::NonPrimitive => "not a primitive", ErrorKind::NonKey => "not a key type", ErrorKind::InvalidOperation => "invalid operation", ErrorKind::SyntaxError => "syntax error", ErrorKind::TemplateNotFound => "template not found", ErrorKind::TooManyArguments => "too many arguments", ErrorKind::MissingArgument => "missing argument", ErrorKind::UnknownFilter => "unknown filter", ErrorKind::UnknownFunction => "unknown function", ErrorKind::UnknownTest => "unknown test", ErrorKind::UnknownMethod => "unknown method", ErrorKind::BadEscape => "bad string escape", ErrorKind::UndefinedError => "undefined value", ErrorKind::BadSerialization => "could not serialize to value", ErrorKind::BadInclude => "could not render include", ErrorKind::EvalBlock => "could not render block", ErrorKind::CannotUnpack => "cannot unpack", ErrorKind::WriteFailure => "failed to write output", #[cfg(feature = "fuel")] ErrorKind::OutOfFuel => "engine ran out of fuel", #[cfg(feature = "custom_syntax")] ErrorKind::InvalidDelimiter => "invalid custom delimiters", #[cfg(feature = "multi_template")] ErrorKind::UnknownBlock => "unknown block", } } } impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.description()) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(ref detail) = self.repr.detail { ok!(write!(f, "{}: {}", self.kind(), detail)); } else { ok!(write!(f, "{}", self.kind())); } if let Some(ref filename) = self.name() { ok!(write!(f, " (in {}:{})", filename, self.line().unwrap_or(0))) } #[cfg(feature = "debug")] { if f.alternate() { if let Some(info) = self.debug_info() { ok!(crate::debug::render_debug_info( f, self.name(), self.kind(), self.line(), self.span(), info, )); } } } Ok(()) } } impl Error { /// Creates a new error with kind and detail. pub fn new>>(kind: ErrorKind, detail: D) -> Error { Error { repr: Box::new(ErrorRepr { kind, detail: Some(detail.into()), name: None, lineno: 0, span: None, source: None, #[cfg(feature = "debug")] debug_info: None, }), } } pub(crate) fn set_filename_and_line(&mut self, filename: &str, lineno: usize) { self.repr.name = Some(filename.into()); self.repr.lineno = lineno; } pub(crate) fn set_filename_and_span(&mut self, filename: &str, span: Span) { self.repr.name = Some(filename.into()); self.repr.span = Some(span); self.repr.lineno = span.start_line as usize; } pub(crate) fn new_not_found(name: &str) -> Error { Error::new( ErrorKind::TemplateNotFound, format!("template {name:?} does not exist"), ) } /// Attaches another error as source to this error. #[allow(unused)] pub fn with_source(mut self, source: E) -> Self { self.repr.source = Some(Box::new(source)); self } /// Returns the error kind pub fn kind(&self) -> ErrorKind { self.repr.kind } /// Returns the error detail /// /// The detail is an error message that provides further details about /// the error kind. pub fn detail(&self) -> Option<&str> { self.repr.detail.as_deref() } /// Returns the filename of the template that caused the error. pub fn name(&self) -> Option<&str> { self.repr.name.as_deref() } /// Returns the line number where the error occurred. pub fn line(&self) -> Option { if self.repr.lineno > 0 { Some(self.repr.lineno) } else { None } } /// Returns the byte range of where the error occurred if available. /// /// In combination with [`template_source`](Self::template_source) this can be /// used to better visualize where the error is coming from. By indexing into /// the template source one ends up with the source of the failing expression. /// /// Note that debug mode ([`Environment::set_debug`](crate::Environment::set_debug)) /// needs to be enabled, and the `debug` feature must be turned on. The engine /// usually keeps track of spans in all cases, but there is no absolute guarantee /// that it is able to provide a range in all error cases. /// /// ``` /// # use minijinja::{Error, Environment, context}; /// # let mut env = Environment::new(); /// # env.set_debug(true); /// let tmpl = env.template_from_str("Hello {{ foo + bar }}!").unwrap(); /// let err = tmpl.render(context!(foo => "a string", bar => 0)).unwrap_err(); /// let src = err.template_source().unwrap(); /// assert_eq!(&src[err.range().unwrap()], "foo + bar"); /// ``` #[cfg(feature = "debug")] #[cfg_attr(docsrs, doc(cfg(feature = "debug")))] pub fn range(&self) -> Option> { self.repr .span .map(|x| x.start_offset as usize..x.end_offset as usize) } /// Returns the template source if available. #[cfg(feature = "debug")] #[cfg_attr(docsrs, doc(cfg(feature = "debug")))] pub fn template_source(&self) -> Option<&str> { self.debug_info().and_then(|x| x.source()) } /// Returns the line number where the error occurred. #[cfg(feature = "debug")] pub(crate) fn span(&self) -> Option { self.repr.span } /// Returns the template debug information is available. /// /// The debug info snapshot is only embedded into the error if the debug /// mode is enabled on the environment /// ([`Environment::set_debug`](crate::Environment::set_debug)). #[cfg(feature = "debug")] pub(crate) fn debug_info(&self) -> Option<&crate::debug::DebugInfo> { self.repr.debug_info.as_ref() } #[cfg(feature = "debug")] #[cfg_attr(docsrs, doc(cfg(feature = "debug")))] pub(crate) fn attach_debug_info(&mut self, value: crate::debug::DebugInfo) { self.repr.debug_info = Some(value); } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.repr.source.as_ref().map(|err| err.as_ref() as _) } } impl From for Error { fn from(kind: ErrorKind) -> Self { Error { repr: Box::new(ErrorRepr { kind, detail: None, name: None, lineno: 0, span: None, source: None, #[cfg(feature = "debug")] debug_info: None, }), } } } impl From for Error { fn from(_: fmt::Error) -> Self { Error::new(ErrorKind::WriteFailure, "formatting failed") } } pub fn attach_basic_debug_info(rv: Result, source: &str) -> Result { #[cfg(feature = "debug")] { match rv { Ok(rv) => Ok(rv), Err(mut err) => { err.repr.debug_info = Some(crate::debug::DebugInfo { template_source: Some(source.to_string()), ..Default::default() }); Err(err) } } } #[cfg(not(feature = "debug"))] { let _source = source; rv } } minijinja-1.0.3/src/expression.rs000064400000000000000000000061201046102023000151300ustar 00000000000000use std::collections::{BTreeMap, HashSet}; use std::fmt; use serde::Serialize; use crate::compiler::ast; use crate::compiler::instructions::Instructions; use crate::compiler::lexer::SyntaxConfig; use crate::compiler::meta::find_undeclared; use crate::compiler::parser::parse_expr; use crate::environment::Environment; use crate::error::Error; use crate::output::Output; use crate::value::Value; use crate::vm::Vm; /// A handle to a compiled expression. /// /// An expression is created via the /// [`compile_expression`](Environment::compile_expression) method. It provides /// a method to evaluate the expression and return the result as value object. /// This for instance can be used to evaluate simple expressions from user /// provided input to implement features such as dynamic filtering. /// /// This is usually best paired with [`context`](crate::context!) to pass /// a single value to it. /// /// # Example /// /// ```rust /// # use minijinja::{Environment, context}; /// let env = Environment::new(); /// let expr = env.compile_expression("number > 10 and number < 20").unwrap(); /// let rv = expr.eval(context!(number => 15)).unwrap(); /// assert!(rv.is_true()); /// ``` pub struct Expression<'env, 'source> { env: &'env Environment<'source>, instructions: Instructions<'source>, } impl<'env, 'source> fmt::Debug for Expression<'env, 'source> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Expression") .field("env", &self.env) .finish() } } impl<'env, 'source> Expression<'env, 'source> { pub(crate) fn new( env: &'env Environment<'source>, instructions: Instructions<'source>, ) -> Expression<'env, 'source> { Expression { env, instructions } } /// Evaluates the expression with some context. /// /// The result of the expression is returned as [`Value`]. pub fn eval(&self, ctx: S) -> Result { // reduce total amount of code faling under mono morphization into // this function, and share the rest in _eval. self._eval(Value::from_serializable(&ctx)) } /// Returns a set of all undeclared variables in the expression. /// /// This works the same as /// [`Template::undeclared_variables`](crate::Template::undeclared_variables). pub fn undeclared_variables(&self, nested: bool) -> HashSet { match parse_expr(self.instructions.source(), SyntaxConfig::default()) { Ok(expr) => find_undeclared( &ast::Stmt::EmitExpr(ast::Spanned::new( ast::EmitExpr { expr }, Default::default(), )), nested, ), Err(_) => HashSet::new(), } } fn _eval(&self, root: Value) -> Result { Ok(ok!(Vm::new(self.env).eval( &self.instructions, root, &BTreeMap::new(), &mut Output::null(), crate::AutoEscape::None, )) .0 .expect("expression evaluation did not leave value on stack")) } } minijinja-1.0.3/src/filters.rs000064400000000000000000001237171046102023000144150ustar 00000000000000//! Filter functions and abstractions. //! //! MiniJinja inherits from Jinja2 the concept of filter functions. These are functions //! which are applied to values to modify them. For example the expression `{{ 42|filter(23) }}` //! invokes the filter `filter` with the arguments `42` and `23`. //! //! MiniJinja comes with some built-in filters that are listed below. To create a //! custom filter write a function that takes at least a value, then registers it //! with [`add_filter`](crate::Environment::add_filter). //! //! # Using Filters //! //! Using filters in templates is possible in all places an expression is permitted. //! This means they are not just used for printing but also are useful for iteration //! or similar situations. //! //! Motivating example: //! //! ```jinja //!
//! {% for key, value in config|items %} //!
{{ key }} //!
{{ value|tojson }}
//! {% endfor %} //!
//! ``` //! //! # Custom Filters //! //! A custom filter is just a simple function which accepts its inputs //! as parameters and then returns a new value. For instance the following //! shows a filter which takes an input value and replaces whitespace with //! dashes and converts it to lowercase: //! //! ``` //! # use minijinja::Environment; //! # let mut env = Environment::new(); //! fn slugify(value: String) -> String { //! value.to_lowercase().split_whitespace().collect::>().join("-") //! } //! //! env.add_filter("slugify", slugify); //! ``` //! //! MiniJinja will perform the necessary conversions automatically. For more //! information see the [`Filter`] trait. //! //! # Accessing State //! //! In some cases it can be necessary to access the execution [`State`]. Since a borrowed //! state implements [`ArgType`](crate::value::ArgType) it's possible to add a //! parameter that holds the state. For instance the following filter appends //! the current template name to the string: //! //! ``` //! # use minijinja::Environment; //! # let mut env = Environment::new(); //! use minijinja::value::Value; //! use minijinja::State; //! //! fn append_template(state: &State, value: &Value) -> String { //! format!("{}-{}", value, state.name()) //! } //! //! env.add_filter("appendTemplate", append_template); //! ``` //! //! # Built-in Filters //! //! When the `builtins` feature is enabled a range of built-in filters are //! automatically added to the environment. These are also all provided in //! this module. Note though that these functions are not to be //! called from Rust code as their exact interface (arguments and return types) //! might change from one MiniJinja version to another. //! //! Some additional filters are available in the //! [`minijinja-contrib`](https://crates.io/crates/minijinja-contrib) crate. use std::sync::Arc; use crate::error::Error; use crate::utils::{write_escaped, SealedMarker}; use crate::value::{ArgType, FunctionArgs, FunctionResult, Value}; use crate::vm::State; use crate::{AutoEscape, Output}; type FilterFunc = dyn Fn(&State, &[Value]) -> Result + Sync + Send + 'static; #[derive(Clone)] pub(crate) struct BoxedFilter(Arc); /// A utility trait that represents filters. /// /// This trait is used by the [`add_filter`](crate::Environment::add_filter) method to abstract over /// different types of functions that implement filters. Filters are functions /// which at the very least accept the [`State`] by reference as first parameter /// and the value that that the filter is applied to as second. Additionally up to /// 4 further parameters are supported. /// /// A filter can return any of the following types: /// /// * `Rv` where `Rv` implements `Into` /// * `Result` where `Rv` implements `Into` /// /// Filters accept one mandatory parameter which is the value the filter is /// applied to and up to 4 extra parameters. The extra parameters can be /// marked optional by using `Option`. The last argument can also use /// [`Rest`](crate::value::Rest) to capture the remaining arguments. All /// types are supported for which [`ArgType`](crate::value::ArgType) is implemented. /// /// For a list of built-in filters see [`filters`](crate::filters). /// /// # Basic Example /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// use minijinja::State; /// /// fn slugify(value: String) -> String { /// value.to_lowercase().split_whitespace().collect::>().join("-") /// } /// /// env.add_filter("slugify", slugify); /// ``` /// /// ```jinja /// {{ "Foo Bar Baz"|slugify }} -> foo-bar-baz /// ``` /// /// # Arguments and Optional Arguments /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// fn substr(value: String, start: u32, end: Option) -> String { /// let end = end.unwrap_or(value.len() as _); /// value.get(start as usize..end as usize).unwrap_or_default().into() /// } /// /// env.add_filter("substr", substr); /// ``` /// /// ```jinja /// {{ "Foo Bar Baz"|substr(4) }} -> Bar Baz /// {{ "Foo Bar Baz"|substr(4, 7) }} -> Bar /// ``` /// /// # Variadic /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// use minijinja::value::Rest; /// /// fn pyjoin(joiner: String, values: Rest) -> String { /// values.join(&joiner) /// } /// /// env.add_filter("pyjoin", pyjoin); /// ``` /// /// ```jinja /// {{ "|".join(1, 2, 3) }} -> 1|2|3 /// ``` pub trait Filter: Send + Sync + 'static { /// Applies a filter to value with the given arguments. /// /// The value is always the first argument. #[doc(hidden)] fn apply_to(&self, args: Args, _: SealedMarker) -> Rv; } macro_rules! tuple_impls { ( $( $name:ident )* ) => { impl Filter for Func where Func: Fn($($name),*) -> Rv + Send + Sync + 'static, Rv: FunctionResult, $($name: for<'a> ArgType<'a>,)* { fn apply_to(&self, args: ($($name,)*), _: SealedMarker) -> Rv { #[allow(non_snake_case)] let ($($name,)*) = args; (self)($($name,)*) } } }; } tuple_impls! {} tuple_impls! { A } tuple_impls! { A B } tuple_impls! { A B C } tuple_impls! { A B C D } tuple_impls! { A B C D E } impl BoxedFilter { /// Creates a new boxed filter. pub fn new(f: F) -> BoxedFilter where F: Filter + for<'a> Filter>::Output>, Rv: FunctionResult, Args: for<'a> FunctionArgs<'a>, { BoxedFilter(Arc::new(move |state, args| -> Result { f.apply_to(ok!(Args::from_values(Some(state), args)), SealedMarker) .into_result() })) } /// Applies the filter to a value and argument. pub fn apply_to(&self, state: &State, args: &[Value]) -> Result { (self.0)(state, args) } } /// Marks a value as safe. This converts it into a string. /// /// When a value is marked as safe, no further auto escaping will take place. pub fn safe(v: String) -> Value { Value::from_safe_string(v) } /// Escapes a string. By default to HTML. /// /// By default this filter is also registered under the alias `e`. Note that /// this filter escapes with the format that is native to the format or HTML /// otherwise. This means that if the auto escape setting is set to /// `Json` for instance then this filter will serialize to JSON instead. pub fn escape(state: &State, v: Value) -> Result { if v.is_safe() { return Ok(v); } // this tries to use the escaping flag of the current scope, then // of the initial state and if that is also not set it falls back // to HTML. let auto_escape = match state.auto_escape() { AutoEscape::None => match state.env().initial_auto_escape(state.name()) { AutoEscape::None => AutoEscape::Html, other => other, }, other => other, }; let mut rv = match v.as_str() { Some(s) => String::with_capacity(s.len()), None => String::new(), }; let mut out = Output::with_string(&mut rv); ok!(write_escaped(&mut out, auto_escape, &v)); Ok(Value::from_safe_string(rv)) } #[cfg(feature = "builtins")] mod builtins { use super::*; use crate::error::ErrorKind; use crate::value::{Kwargs, ValueKind, ValueRepr}; use std::borrow::Cow; use std::cmp::Ordering; use std::fmt::Write; use std::mem; /// Converts a value to uppercase. /// /// ```jinja ///

{{ chapter.title|upper }}

/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn upper(v: Cow<'_, str>) -> String { v.to_uppercase() } /// Converts a value to lowercase. /// /// ```jinja ///

{{ chapter.title|lower }}

/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn lower(v: Cow<'_, str>) -> String { v.to_lowercase() } /// Converts a value to title case. /// /// ```jinja ///

{{ chapter.title|title }}

/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn title(v: Cow<'_, str>) -> String { let mut rv = String::new(); let mut capitalize = true; for c in v.chars() { if c.is_ascii_punctuation() || c.is_whitespace() { rv.push(c); capitalize = true; } else if capitalize { write!(rv, "{}", c.to_uppercase()).unwrap(); capitalize = false; } else { write!(rv, "{}", c.to_lowercase()).unwrap(); } } rv } /// Convert the string with all its characters lowercased /// apart from the first char which is uppercased. /// /// ```jinja ///

{{ chapter.title|capitalize }}

/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn capitalize(text: Cow<'_, str>) -> String { let mut chars = text.chars(); match chars.next() { None => String::new(), Some(f) => f.to_uppercase().collect::() + &chars.as_str().to_lowercase(), } } /// Does a string replace. /// /// It replaces all occurrences of the first parameter with the second. /// /// ```jinja /// {{ "Hello World"|replace("Hello", "Goodbye") }} /// -> Goodbye World /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn replace( _state: &State, v: Cow<'_, str>, from: Cow<'_, str>, to: Cow<'_, str>, ) -> String { v.replace(&from as &str, &to as &str) } /// Returns the "length" of the value /// /// By default this filter is also registered under the alias `count`. /// /// ```jinja ///

Search results: {{ results|length }} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn length(v: Value) -> Result { v.len().ok_or_else(|| { Error::new( ErrorKind::InvalidOperation, format!("cannot calculate length of value of type {}", v.kind()), ) }) } fn sort_helper(a: &Value, b: &Value, case_sensitive: bool) -> Ordering { if !case_sensitive { if let (Some(a), Some(b)) = (a.as_str(), b.as_str()) { #[cfg(feature = "unicode")] { return unicase::UniCase::new(a).cmp(&unicase::UniCase::new(b)); } #[cfg(not(feature = "unicode"))] { return a.to_ascii_lowercase().cmp(&b.to_ascii_lowercase()); } } } a.cmp(b) } /// Dict sorting functionality. /// /// This filter works like `|items` but sorts the pairs by key first. /// /// The filter accepts a few keyword arguments: /// /// * `case_sensitive`: set to `true` to make the sorting of strings case sensitive. /// * `by`: set to `"value"` to sort by value. Defaults to `"key"`. /// * `reverse`: set to `true` to sort in reverse. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn dictsort(v: Value, kwargs: Kwargs) -> Result { if v.kind() == ValueKind::Map { let mut rv = Vec::with_capacity(v.len().unwrap_or(0)); let iter = ok!(v.try_iter()); for key in iter { let value = v.get_item(&key).unwrap_or(Value::UNDEFINED); rv.push((key, value)); } let by_value = match ok!(kwargs.get("by")) { None | Some("key") => false, Some("value") => true, Some(invalid) => { return Err(Error::new( ErrorKind::InvalidOperation, format!("invalid value '{}' for 'by' parameter", invalid), )) } }; let case_sensitive = ok!(kwargs.get::>("case_sensitive")).unwrap_or(false); rv.sort_by(|a, b| { let (a, b) = if by_value { (&a.1, &b.1) } else { (&a.0, &b.0) }; sort_helper(a, b, case_sensitive) }); if let Some(true) = ok!(kwargs.get("reverse")) { rv.reverse(); } ok!(kwargs.assert_all_used()); Ok(Value::from( rv.into_iter() .map(|(k, v)| Value::from(vec![k, v])) .collect::>(), )) } else { Err(Error::new( ErrorKind::InvalidOperation, "cannot convert value into pair list", )) } } /// Returns a list of pairs (items) from a mapping. /// /// This can be used to iterate over keys and values of a mapping /// at once. Note that this will use the original order of the map /// which is typically arbitrary unless the `preserve_order` feature /// is used in which case the original order of the map is retained. /// It's generally better to use `|dictsort` which sorts the map by /// key before iterating. /// /// ```jinja ///

/// {% for key, value in my_dict|items %} ///
{{ key }} ///
{{ value }} /// {% endfor %} ///
/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn items(v: Value) -> Result { if v.kind() == ValueKind::Map { let mut rv = Vec::with_capacity(v.len().unwrap_or(0)); let iter = ok!(v.try_iter()); for key in iter { let value = v.get_item(&key).unwrap_or(Value::UNDEFINED); rv.push(Value::from(vec![key, value])); } Ok(Value::from(rv)) } else { Err(Error::new( ErrorKind::InvalidOperation, "cannot convert value into pair list", )) } } /// Reverses a list or string /// /// ```jinja /// {% for user in users|reverse %} ///
  • {{ user.name }} /// {% endfor %} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn reverse(v: Value) -> Result { if let Some(s) = v.as_str() { Ok(Value::from(s.chars().rev().collect::())) } else if let Some(seq) = v.as_seq() { Ok(Value::from(seq.iter().rev().collect::>())) } else { Err(Error::new( ErrorKind::InvalidOperation, format!("cannot reverse value of type {}", v.kind()), )) } } /// Trims a value #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn trim(s: Cow<'_, str>, chars: Option>) -> String { match chars { Some(chars) => { let chars = chars.chars().collect::>(); s.trim_matches(&chars[..]).to_string() } None => s.trim().to_string(), } } /// Joins a sequence by a character #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn join(val: Value, joiner: Option>) -> Result { if val.is_undefined() || val.is_none() { return Ok(String::new()); } let joiner = joiner.as_ref().unwrap_or(&Cow::Borrowed("")); if let Some(s) = val.as_str() { let mut rv = String::new(); for c in s.chars() { if !rv.is_empty() { rv.push_str(joiner); } rv.push(c); } Ok(rv) } else if let Some(seq) = val.as_seq() { let mut rv = String::new(); for item in seq.iter() { if !rv.is_empty() { rv.push_str(joiner); } if let Some(s) = item.as_str() { rv.push_str(s); } else { write!(rv, "{item}").ok(); } } Ok(rv) } else { Err(Error::new( ErrorKind::InvalidOperation, format!("cannot join value of type {}", val.kind()), )) } } /// If the value is undefined it will return the passed default value, /// otherwise the value of the variable: /// /// ```jinja ///

    {{ my_variable|default("my_variable was not defined") }}

    /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn default(value: Value, other: Option) -> Value { if value.is_undefined() { other.unwrap_or_else(|| Value::from("")) } else { value } } /// Returns the absolute value of a number. /// /// ```jinja /// |a - b| = {{ (a - b)|abs }} /// -> |2 - 4| = 2 /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn abs(value: Value) -> Result { match value.0 { ValueRepr::I64(x) => Ok(Value::from(x.abs())), ValueRepr::I128(x) => Ok(Value::from(x.0.abs())), ValueRepr::F64(x) => Ok(Value::from(x.abs())), _ => Err(Error::new( ErrorKind::InvalidOperation, "cannot round value", )), } } /// Looks up an attribute. /// /// In MiniJinja this is the same as the `[]` operator. In Jinja2 there is a /// small difference which is why this filter is sometimes used in Jinja2 /// templates. For compatibility it's provided here as well. /// /// ```jinja /// {{ value['key'] == value|attr('key') }} -> true /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn attr(value: Value, key: &Value) -> Result { value.get_item(key) } /// Round the number to a given precision. /// /// Round the number to a given precision. The first parameter specifies the /// precision (default is 0). /// /// ```jinja /// {{ 42.55|round }} /// -> 43.0 /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn round(value: Value, precision: Option) -> Result { match value.0 { ValueRepr::I64(_) | ValueRepr::I128(_) => Ok(value), ValueRepr::F64(val) => { let x = 10f64.powi(precision.unwrap_or(0)); Ok(Value::from((x * val).round() / x)) } _ => Err(Error::new( ErrorKind::InvalidOperation, "cannot round value", )), } } /// Returns the first item from a list. /// /// If the list is empty `undefined` is returned. /// /// ```jinja ///
    ///
    primary email ///
    {{ user.email_addresses|first|default('no user') }} ///
    /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn first(value: Value) -> Result { if let Some(s) = value.as_str() { Ok(s.chars().next().map_or(Value::UNDEFINED, Value::from)) } else if let Some(s) = value.as_seq() { Ok(s.get_item(0).unwrap_or(Value::UNDEFINED)) } else { Err(Error::new( ErrorKind::InvalidOperation, "cannot get first item from value", )) } } /// Returns the last item from a list. /// /// If the list is empty `undefined` is returned. /// /// ```jinja ///

    Most Recent Update

    /// {% with update = updates|last %} ///
    ///
    Location ///
    {{ update.location }} ///
    Status ///
    {{ update.status }} ///
    /// {% endwith %} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn last(value: Value) -> Result { if let Some(s) = value.as_str() { Ok(s.chars().rev().next().map_or(Value::UNDEFINED, Value::from)) } else if let Some(seq) = value.as_seq() { Ok(seq.iter().last().unwrap_or(Value::UNDEFINED)) } else { Err(Error::new( ErrorKind::InvalidOperation, "cannot get last item from value", )) } } /// Returns the smallest item from the list. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn min(state: &State, value: Value) -> Result { let iter = ok!(state.undefined_behavior().try_iter(value).map_err(|err| { Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err) })); Ok(iter.min().unwrap_or(Value::UNDEFINED)) } /// Returns the largest item from the list. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn max(state: &State, value: Value) -> Result { let iter = ok!(state.undefined_behavior().try_iter(value).map_err(|err| { Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err) })); Ok(iter.max().unwrap_or(Value::UNDEFINED)) } /// Returns the sorted version of the given list. /// /// The filter accepts a few keyword arguments: /// /// * `case_sensitive`: set to `true` to make the sorting of strings case sensitive. /// * `attribute`: can be set to an attribute or dotted path to sort by that attribute /// * `reverse`: set to `true` to sort in reverse. /// /// ```jinja /// {{ [1, 3, 2, 4]|sort }} -> [4, 3, 2, 1] /// {{ [1, 3, 2, 4]|sort(reverse=true) }} -> [1, 2, 3, 4] /// # Sort users by age attribute in descending order. /// {{ users|sort(attribute="age") }} /// # Sort users by age attribute in ascending order. /// {{ users|sort(attribute="age", reverse=true) }} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn sort(state: &State, value: Value, kwargs: Kwargs) -> Result { let mut items = ok!(state.undefined_behavior().try_iter(value).map_err(|err| { Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err) })) .collect::>(); let case_sensitive = ok!(kwargs.get::>("case_sensitive")).unwrap_or(false); if let Some(attr) = ok!(kwargs.get::>("attribute")) { items.sort_by(|a, b| match (a.get_path(attr), b.get_path(attr)) { (Ok(a), Ok(b)) => sort_helper(&a, &b, case_sensitive), _ => Ordering::Equal, }); } else { items.sort_by(|a, b| sort_helper(a, b, case_sensitive)) } if let Some(true) = ok!(kwargs.get("reverse")) { items.reverse(); } ok!(kwargs.assert_all_used()); Ok(Value::from(items)) } /// Converts the input value into a list. /// /// If the value is already a list, then it's returned unchanged. /// Applied to a map this returns the list of keys, applied to a /// string this returns the characters. If the value is undefined /// an empty list is returned. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn list(state: &State, value: Value) -> Result { let iter = ok!(state.undefined_behavior().try_iter(value).map_err(|err| { Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err) })); Ok(Value::from(iter.collect::>())) } /// Converts the value into a boolean value. /// /// This behaves the same as the if statement does with regards to /// handling of boolean values. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn bool(value: Value) -> bool { value.is_true() } /// Slice an iterable and return a list of lists containing /// those items. /// /// Useful if you want to create a div containing three ul tags that /// represent columns: /// /// ```jinja ///
    /// {% for column in items|slice(3) %} ///
      /// {% for item in column %} ///
    • {{ item }}
    • /// {% endfor %} ///
    /// {% endfor %} ///
    /// ``` /// /// If you pass it a second argument it’s used to fill missing values on the /// last iteration. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn slice( state: &State, value: Value, count: usize, fill_with: Option, ) -> Result { if count == 0 { return Err(Error::new(ErrorKind::InvalidOperation, "count cannot be 0")); } let items = ok!(state.undefined_behavior().try_iter(value)).collect::>(); let len = items.len(); let items_per_slice = len / count; let slices_with_extra = len % count; let mut offset = 0; let mut rv = Vec::with_capacity(count); for slice in 0..count { let start = offset + slice * items_per_slice; if slice < slices_with_extra { offset += 1; } let end = offset + (slice + 1) * items_per_slice; let tmp = &items[start..end]; if let Some(ref filler) = fill_with { if slice >= slices_with_extra { let mut tmp = tmp.to_vec(); tmp.push(filler.clone()); rv.push(Value::from(tmp)); continue; } } rv.push(Value::from(tmp.to_vec())); } Ok(Value::from(rv)) } /// Batch items. /// /// This filter works pretty much like `slice` just the other way round. It /// returns a list of lists with the given number of items. If you provide a /// second parameter this is used to fill up missing items. /// /// ```jinja /// /// {% for row in items|batch(3, ' ') %} /// /// {% for column in row %} /// /// {% endfor %} /// /// {% endfor %} ///
    {{ column }}
    /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn batch( state: &State, value: Value, count: usize, fill_with: Option, ) -> Result { if count == 0 { return Err(Error::new(ErrorKind::InvalidOperation, "count cannot be 0")); } let mut rv = Vec::with_capacity(value.len().unwrap_or(0) / count); let mut tmp = Vec::with_capacity(count); for item in ok!(state.undefined_behavior().try_iter(value)) { if tmp.len() == count { rv.push(Value::from(mem::replace( &mut tmp, Vec::with_capacity(count), ))); } tmp.push(item); } if !tmp.is_empty() { if let Some(filler) = fill_with { for _ in 0..count - tmp.len() { tmp.push(filler.clone()); } } rv.push(Value::from(tmp)); } Ok(Value::from(rv)) } /// Dumps a value to JSON. /// /// This filter is only available if the `json` feature is enabled. The resulting /// value is safe to use in HTML as well as it will not contain any special HTML /// characters. The optional parameter to the filter can be set to `true` to enable /// pretty printing. Not that the `"` character is left unchanged as it's the /// JSON string delimiter. If you want to pass JSON serialized this way into an /// HTTP attribute use single quoted HTML attributes: /// /// ```jinja /// /// ... /// ``` #[cfg_attr(docsrs, doc(cfg(all(feature = "builtins", feature = "json"))))] #[cfg(feature = "json")] pub fn tojson(value: Value, pretty: Option) -> Result { if pretty.unwrap_or(false) { serde_json::to_string_pretty(&value) } else { serde_json::to_string(&value) } .map_err(|err| { Error::new(ErrorKind::InvalidOperation, "cannot serialize to JSON").with_source(err) }) .map(|s| { // When this filter is used the return value is safe for both HTML and JSON let mut rv = String::with_capacity(s.len()); for c in s.chars() { match c { '<' => rv.push_str("\\u003c"), '>' => rv.push_str("\\u003e"), '&' => rv.push_str("\\u0026"), '\'' => rv.push_str("\\u0027"), _ => rv.push(c), } } Value::from_safe_string(rv) }) } /// indents Value with spaces /// /// The first optional parameter to the filter can be set to `true` to /// indent the first line. The parameter defaults to false. /// the second optional parameter to the filter can be set to `true` /// to indent blank lines. The parameter defaults to false. /// This filter is useful, if you want to template yaml-files /// /// ```jinja /// example: /// config: /// {{ global_conifg|indent(2) }} #does not indent first line /// {{ global_config|indent(2,true) }} #indent whole Value with two spaces /// {{ global_config|indent(2,true,true)}} #indent whole Value and all Blank Lines value /// ``` #[cfg_attr(docsrs, doc(cfg(all(feature = "builtins"))))] #[cfg(feature = "builtins")] pub fn indent( mut value: String, width: usize, indent_first_line: Option, indent_blank_lines: Option, ) -> String { fn strip_trailing_newline(input: &mut String) { if let Some(stripped) = input.strip_suffix(&['\r', '\n'][..]) { input.truncate(stripped.len()); } } strip_trailing_newline(&mut value); let mut output: String = String::new(); let mut iterator = value.split('\n'); if !indent_first_line.unwrap_or(false) { output.push_str(iterator.next().unwrap()); output.push('\n'); } for line in iterator { if line.is_empty() { if indent_blank_lines.unwrap_or(false) { output.push_str(&" ".repeat(width)); } } else { output.push_str(format!("{}{}", " ".repeat(width), line).as_str()); } output.push('\n'); } strip_trailing_newline(&mut output); output } /// URL encodes a value. /// /// If given a map it encodes the parameters into a query set, otherwise it /// encodes the stringified value. If the value is none or undefined, an /// empty string is returned. /// /// ```jinja /// Search /// ``` #[cfg_attr(docsrs, doc(cfg(all(feature = "builtins", feature = "urlencode"))))] #[cfg(feature = "urlencode")] pub fn urlencode(value: Value) -> Result { const SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC .remove(b'/') .remove(b'.') .remove(b'-') .remove(b'_') .add(b' '); if value.kind() == ValueKind::Map { let mut rv = String::new(); for (idx, k) in ok!(value.try_iter()).enumerate() { if idx > 0 { rv.push('&'); } let v = ok!(value.get_item(&k)); write!( rv, "{}={}", percent_encoding::utf8_percent_encode(&k.to_string(), SET), percent_encoding::utf8_percent_encode(&v.to_string(), SET) ) .unwrap(); } Ok(rv) } else { match &value.0 { ValueRepr::None | ValueRepr::Undefined => Ok("".into()), ValueRepr::Bytes(b) => Ok(percent_encoding::percent_encode(b, SET).to_string()), ValueRepr::String(s, _) => { Ok(percent_encoding::utf8_percent_encode(s, SET).to_string()) } _ => Ok(percent_encoding::utf8_percent_encode(&value.to_string(), SET).to_string()), } } } #[cfg(feature = "builtins")] fn select_or_reject( state: &State, invert: bool, value: Value, attr: Option>, test_name: Option>, args: crate::value::Rest, ) -> Result, Error> { let mut rv = vec![]; let test = if let Some(test_name) = test_name { Some(ok!(state .env .get_test(&test_name) .ok_or_else(|| Error::from(ErrorKind::UnknownTest)))) } else { None }; for value in ok!(state.undefined_behavior().try_iter(value)) { let test_value = if let Some(ref attr) = attr { ok!(value.get_path(attr)) } else { value.clone() }; let passed = if let Some(test) = test { let new_args = Some(test_value) .into_iter() .chain(args.0.iter().cloned()) .collect::>(); ok!(test.perform(state, &new_args)) } else { test_value.is_true() }; if passed != invert { rv.push(value); } } Ok(rv) } /// Creates a new sequence of values that pass a test. /// /// Filters a sequence of objects by applying a test to each object. /// Only values that pass the test are included. /// /// If no test is specified, each object will be evaluated as a boolean. /// /// ```jinja /// {{ [1, 2, 3, 4]|select("odd") }} -> [1, 3] /// {{ [false, null, 42]|select }} -> [42] /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn select( state: &State, value: Value, test_name: Option>, args: crate::value::Rest, ) -> Result, Error> { select_or_reject(state, false, value, None, test_name, args) } /// Creates a new sequence of values of which an attribute passes a test. /// /// This functions like [`select`] but it will test an attribute of the /// object itself: /// /// ```jinja /// {{ users|selectattr("is_active") }} -> all users where x.is_active is true /// {{ users|selectattr("id", "even") }} -> returns all users with an even id /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn selectattr( state: &State, value: Value, attr: Cow<'_, str>, test_name: Option>, args: crate::value::Rest, ) -> Result, Error> { select_or_reject(state, false, value, Some(attr), test_name, args) } /// Creates a new sequence of values that don't pass a test. /// /// This is the inverse of [`select`]. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn reject( state: &State, value: Value, test_name: Option>, args: crate::value::Rest, ) -> Result, Error> { select_or_reject(state, true, value, None, test_name, args) } /// Creates a new sequence of values of which an attribute does not pass a test. /// /// This functions like [`select`] but it will test an attribute of the /// object itself: /// /// ```jinja /// {{ users|rejectattr("is_active") }} -> all users where x.is_active is false /// {{ users|rejectattr("id", "even") }} -> returns all users with an odd id /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn rejectattr( state: &State, value: Value, attr: Cow<'_, str>, test_name: Option>, args: crate::value::Rest, ) -> Result, Error> { select_or_reject(state, true, value, Some(attr), test_name, args) } /// Applies a filter to a sequence of objects or looks up an attribute. /// /// This is useful when dealing with lists of objects but you are really /// only interested in a certain value of it. /// /// The basic usage is mapping on an attribute. Given a list of users /// you can for instance quickly select the username and join on it: /// /// ```jinja /// {{ users|map(attribute='username')|join(', ') }} /// ``` /// /// You can specify a `default` value to use if an object in the list does /// not have the given attribute. /// /// ```jinja /// {{ users|map(attribute="username", default="Anonymous")|join(", ") }} /// ``` /// /// Alternatively you can have `map` invoke a filter by passing the name of the /// filter and the arguments afterwards. A good example would be applying a /// text conversion filter on a sequence: /// /// ```jinja /// Users on this page: {{ titles|map('lower')|join(', ') }} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn map( state: &State, value: Value, args: crate::value::Rest, ) -> Result, Error> { let mut rv = Vec::with_capacity(value.len().unwrap_or(0)); // attribute mapping let (args, kwargs): (&[Value], Kwargs) = crate::value::from_args(&args)?; if let Some(attr) = ok!(kwargs.get::>("attribute")) { if !args.is_empty() { return Err(Error::from(ErrorKind::TooManyArguments)); } let default = ok!(kwargs.get::>("default")); for value in ok!(state.undefined_behavior().try_iter(value)) { let sub_val = match attr.as_str() { Some(path) => value.get_path(path), None => value.get_item(&attr), }; rv.push(match (sub_val, &default) { (Ok(attr), _) => { if attr.is_undefined() { if let Some(ref default) = default { default.clone() } else { Value::UNDEFINED } } else { attr } } (Err(_), Some(default)) => default.clone(), (Err(err), None) => return Err(err), }); } ok!(kwargs.assert_all_used()); return Ok(rv); } // filter mapping let filter_name = ok!(args .first() .ok_or_else(|| Error::new(ErrorKind::InvalidOperation, "filter name is required"))); let filter_name = ok!(filter_name.as_str().ok_or_else(|| { Error::new(ErrorKind::InvalidOperation, "filter name must be a string") })); let filter = ok!(state .env .get_filter(filter_name) .ok_or_else(|| Error::from(ErrorKind::UnknownFilter))); for value in ok!(state.undefined_behavior().try_iter(value)) { let new_args = Some(value.clone()) .into_iter() .chain(args.iter().skip(1).cloned()) .collect::>(); rv.push(ok!(filter.apply_to(state, &new_args))); } Ok(rv) } /// Returns a list of unique items from the given iterable. /// /// Returns a list of unique items from the given iterable. /// /// ```jinja /// {{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }} /// -> ['foo', 'bar', 'foobar'] /// ``` /// /// The unique items are yielded in the same order as their first occurrence /// in the iterable passed to the filter. The filter will not detect /// duplicate objects or arrays, only primitives such as strings or numbers. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn unique(values: Vec) -> Value { use std::collections::BTreeSet; let mut rv = Vec::new(); let mut seen = BTreeSet::new(); for item in values { if !seen.contains(&item) { rv.push(item.clone()); seen.insert(item); } } Value::from(rv) } /// Pretty print a variable. /// /// This is useful for debugging as it better shows what's inside an object. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn pprint(value: &Value) -> String { format!("{:#?}", value) } } #[cfg(feature = "builtins")] pub use self::builtins::*; minijinja-1.0.3/src/functions.rs000064400000000000000000000237341046102023000147530ustar 00000000000000//! Global functions and abstractions. //! //! This module provides the abstractions for functions that can registered as //! global functions to the environment via //! [`add_function`](crate::Environment::add_function). //! //! # Using Functions //! //! Functions can be called in any place where an expression is valid. They //! are useful to retrieve data. Some functions are special and provided //! by the engine (like `super`) within certain context, others are global. //! //! The following is a motivating example: //! //! ```jinja //!
    {{ debug() }}
    //! ``` //! //! # Custom Functions //! //! A custom global function is just a simple rust function which accepts optional //! arguments and then returns a result. Global functions are typically used to //! perform a data loading operation. For instance these functions can be used //! to expose data to the template that hasn't been provided by the individual //! render invocation. //! //! ```rust //! # use minijinja::Environment; //! # let mut env = Environment::new(); //! use minijinja::{Error, ErrorKind}; //! //! fn include_file(name: String) -> Result { //! std::fs::read_to_string(&name) //! .map_err(|e| Error::new( //! ErrorKind::InvalidOperation, //! "cannot load file" //! ).with_source(e)) //! } //! //! env.add_function("include_file", include_file); //! ``` //! //! # Note on Keyword Arguments //! //! MiniJinja inherits a lot of the runtime model from Jinja2. That includes support for //! keyword arguments. These however are a concept not native to Rust which makes them //! somewhat unconfortable to work with. In MiniJinja keyword arguments are implemented by //! converting them into an extra parameter represented by a map. That means if you call //! a function as `foo(1, 2, three=3, four=4)` the function gets three arguments: //! //! ```json //! [1, 2, {"three": 3, "four": 4}] //! ``` //! //! If a function wants to disambiugate between a value passed as keyword argument or not, //! the the [`Value::is_kwargs`] can be used which returns `true` if a value represents //! keyword arguments as opposed to just a map. A more convenient way to work with keyword //! arguments is the [`Kwargs`](crate::value::Kwargs) type. //! //! # Built-in Functions //! //! When the `builtins` feature is enabled a range of built-in functions are //! automatically added to the environment. These are also all provided in //! this module. Note though that these functions are not to be //! called from Rust code as their exact interface (arguments and return types) //! might change from one MiniJinja version to another. use std::fmt; use std::sync::Arc; use crate::error::Error; use crate::utils::SealedMarker; use crate::value::{ArgType, FunctionArgs, FunctionResult, Object, Value}; use crate::vm::State; type FuncFunc = dyn Fn(&State, &[Value]) -> Result + Sync + Send + 'static; /// A boxed function. #[derive(Clone)] pub(crate) struct BoxedFunction(Arc, #[cfg(feature = "debug")] &'static str); /// A utility trait that represents global functions. /// /// This trait is used by the [`add_function`](crate::Environment::add_function) /// method to abstract over different types of functions. /// /// Functions which at the very least accept the [`State`] by reference as first /// parameter and additionally up to 4 further parameters. They share much of /// their interface with [`filters`](crate::filters). /// /// A function can return any of the following types: /// /// * `Rv` where `Rv` implements `Into` /// * `Result` where `Rv` implements `Into` /// /// The parameters can be marked optional by using `Option`. The last /// argument can also use [`Rest`](crate::value::Rest) to capture the /// remaining arguments. All types are supported for which /// [`ArgType`](crate::value::ArgType) is implemented. /// /// For a list of built-in functions see [`functions`](crate::functions). /// /// # Basic Example /// /// ```rust /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// use minijinja::{Error, ErrorKind}; /// /// fn include_file(name: String) -> Result { /// std::fs::read_to_string(&name) /// .map_err(|e| Error::new( /// ErrorKind::InvalidOperation, /// "cannot load file" /// ).with_source(e)) /// } /// /// env.add_function("include_file", include_file); /// ``` /// /// ```jinja /// {{ include_file("filename.txt") }} /// ``` /// /// # Variadic /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// use minijinja::value::Rest; /// /// fn sum(values: Rest) -> i64 { /// values.iter().sum() /// } /// /// env.add_function("sum", sum); /// ``` /// /// ```jinja /// {{ sum(1, 2, 3) }} -> 6 /// ``` pub trait Function: Send + Sync + 'static { /// Calls a function with the given arguments. #[doc(hidden)] fn invoke(&self, args: Args, _: SealedMarker) -> Rv; } macro_rules! tuple_impls { ( $( $name:ident )* ) => { impl Function for Func where Func: Fn($($name),*) -> Rv + Send + Sync + 'static, Rv: FunctionResult, $($name: for<'a> ArgType<'a>,)* { fn invoke(&self, args: ($($name,)*), _: SealedMarker) -> Rv { #[allow(non_snake_case)] let ($($name,)*) = args; (self)($($name,)*) } } }; } tuple_impls! {} tuple_impls! { A } tuple_impls! { A B } tuple_impls! { A B C } tuple_impls! { A B C D } tuple_impls! { A B C D E } impl BoxedFunction { /// Creates a new boxed filter. pub fn new(f: F) -> BoxedFunction where F: Function + for<'a> Function>::Output>, Rv: FunctionResult, Args: for<'a> FunctionArgs<'a>, { BoxedFunction( Arc::new(move |state, args| -> Result { f.invoke(ok!(Args::from_values(Some(state), args)), SealedMarker) .into_result() }), #[cfg(feature = "debug")] std::any::type_name::(), ) } /// Invokes the function. pub fn invoke(&self, state: &State, args: &[Value]) -> Result { (self.0)(state, args) } /// Creates a value from a boxed function. pub fn to_value(&self) -> Value { Value::from_object(self.clone()) } } impl fmt::Debug for BoxedFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(feature = "debug")] { if !self.1.is_empty() { return f.write_str(self.1); } } f.write_str("function") } } impl fmt::Display for BoxedFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } impl Object for BoxedFunction { fn call(&self, state: &State, args: &[Value]) -> Result { self.invoke(state, args) } } #[cfg(feature = "builtins")] mod builtins { use super::*; use std::collections::BTreeMap; use crate::error::ErrorKind; use crate::value::{MapType, ValueRepr}; /// Returns a range. /// /// Return a list containing an arithmetic progression of integers. `range(i, /// j)` returns `[i, i+1, i+2, ..., j-1]`. `lower` defaults to 0. When `step` is /// given, it specifies the increment (or decrement). For example, `range(4)` /// and `range(0, 4, 1)` return `[0, 1, 2, 3]`. The end point is omitted. /// /// ```jinja ///
      /// {% for num in range(1, 11) %} ///
    • {{ num }} /// {% endfor %} ///
    /// ``` /// /// This function will refuse to create ranges over 10.000 items. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn range(lower: u32, upper: Option, step: Option) -> Result, Error> { fn to_result>(i: I) -> Result, Error> { if i.len() > 10000 { Err(Error::new( ErrorKind::InvalidOperation, "range has too many elements", )) } else { Ok(i.collect()) } } let rng = match upper { Some(upper) => lower..upper, None => 0..lower, }; if let Some(step) = step { if step == 0 { Err(Error::new( ErrorKind::InvalidOperation, "cannot create range with step of 0", )) } else { to_result(rng.step_by(step as usize)) } } else { to_result(rng) } } /// Creates a dictionary. /// /// This is a convenient alternative for a dictionary literal. /// `{"foo": "bar"}` is the same as `dict(foo="bar")`. /// /// ```jinja /// /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn dict(value: Value) -> Result { match value.0 { ValueRepr::Undefined => Ok(Value::from(BTreeMap::::new())), ValueRepr::Map(map, _) => Ok(Value(ValueRepr::Map(map, MapType::Normal))), _ => Err(Error::from(ErrorKind::InvalidOperation)), } } /// Outputs the current context stringified. /// /// This is a useful function to quickly figure out the state of affairs /// in a template. It emits a stringified debug dump of the current /// engine state including the layers of the context, the current block /// and auto escaping setting. /// /// ```jinja ///
    {{ debug() }}
    /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn debug(state: &State) -> String { format!("{state:#?}") } } #[cfg(feature = "builtins")] pub use self::builtins::*; minijinja-1.0.3/src/lib.rs000064400000000000000000000244411046102023000135050ustar 00000000000000//!
    //! //!

    MiniJinja: a powerful template engine for Rust with minimal dependencies

    //!
    //! //! MiniJinja is a powerful but minimal dependency template engine for Rust which //! is based on the syntax and behavior of the //! [Jinja2](https://jinja.palletsprojects.com/) template engine for Python. It's //! implemented on top of [`serde`]. The goal is to be able to render a large //! chunk of the Jinja2 template ecosystem from Rust with a minimal engine and to //! leverage an already existing ecosystem of editor integrations. //! //! ```jinja //! {% for user in users %} //!
  • {{ user.name }}
  • //! {% endfor %} //! ``` //! //! You can play with MiniJinja online [in the browser //! playground](https://mitsuhiko.github.io/minijinja-playground/) powered by a //! WASM build of MiniJinja. //! //! # Why MiniJinja //! //! MiniJinja by its name wants to be a good default choice if you need a little //! bit of templating with minimal dependencies. It has the following goals: //! //! * Well documented, compact API //! * Minimal dependencies, reasonable compile times and decent runtime performance //! * Stay close as possible to Jinja2 //! * Support for expression evaluation //! * Support for all `serde` compatible types //! * Excellent test coverage //! * Support for dynamic runtime objects with methods and dynamic attributes //! //! # Template Usage //! //! To use MiniJinja one needs to create an [`Environment`] and populate it with //! templates. Afterwards templates can be loaded and rendered. To pass data //! one can pass any serde serializable value. The [`context!`] macro can be //! used to quickly construct a template context: //! //! ``` //! use minijinja::{Environment, context}; //! //! let mut env = Environment::new(); //! env.add_template("hello", "Hello {{ name }}!").unwrap(); //! let tmpl = env.get_template("hello").unwrap(); //! println!("{}", tmpl.render(context!(name => "John")).unwrap()); //! ``` //! //! ```plain //! Hello John! //! ``` //! //! For super trivial cases where you need to render a string once, you can //! also use the [`render!`] macro which acts a bit like a replacement //! for the [`format!`] macro. //! //! # Expression Usage //! //! MiniJinja — like Jinja2 — allows to be used as expression language. This can be //! useful to express logic in configuration files or similar things. For this //! purpose the [`Environment::compile_expression`] method can be used. It returns //! an expression object that can then be evaluated, returning the result: //! //! ``` //! use minijinja::{Environment, context}; //! //! let env = Environment::new(); //! let expr = env.compile_expression("number < 42").unwrap(); //! let result = expr.eval(context!(number => 23)).unwrap(); //! assert_eq!(result.is_true(), true); //! ``` //! //! This becomes particularly powerful when [dynamic objects](crate::value::Object) are //! exposed to templates. //! //! # Custom Filters //! //! MiniJinja lets you register functions as filter functions (see //! [`Filter`](crate::filters::Filter)) with the engine. These can then be //! invoked directly from the template: //! //! ``` //! use minijinja::{Environment, context}; //! //! let mut env = Environment::new(); //! env.add_filter("repeat", str::repeat); //! env.add_template("hello", "{{ 'Na '|repeat(3) }} {{ name }}!").unwrap(); //! let tmpl = env.get_template("hello").unwrap(); //! println!("{}", tmpl.render(context!(name => "Batman")).unwrap()); //! ``` //! //! ```plain //! Na Na Na Batman! //! ``` //! //! # Learn more //! //! - [`Environment`]: the main API entry point. Teaches you how to configure the environment. //! - [`Template`]: the template object API. Shows you how templates can be rendered. //! - [`syntax`]: provides documentation of the template engine syntax. //! - [`filters`]: teaches you how to write custom filters and to see the list of built-in filters. //! - [`tests`]: teaches you how to write custom test functions and to see the list of built-in tests. //! - [`functions`]: teaches how to write custom functions and to see the list of built-in functions. //! - For auto reloading use the [`minijinja-autoreload`](https://docs.rs/minijinja-autoreload) crate. //! //! Additionally there is an [list of examples](https://github.com/mitsuhiko/minijinja/tree/main/examples) //! with many different small example programs on GitHub to explore. //! //! # Error Handling //! //! MiniJinja tries to give you good errors out of the box. However if you use includes or //! template inheritance your experience will improve greatly if you ensure to render chained //! errors. For more information see [`Error`] with an example. //! //! # Size and Compile Times //! //! MiniJinja attempts to compile fast so it can be used as a sensible template engine choice //! when compile times matter. Because of this it's internally modular so unnecessary bits and //! pieces can be removed. The default set of features tries to strike a balance but in //! situations where only a subset is needed (eg: `build.rs`) all default features can be //! be disabled. //! //! # Optional Features //! //! MiniJinja comes with a lot of optional features, some of which are turned on by //! default. If you plan on using MiniJinja in a library, please consider turning //! off all default features and to opt-in explicitly into the ones you actually //! need. //! //!
    Configurable Features //! //! To cut down on size of the engine some default functionality can be removed: //! //! - **Engine Features:** //! //! - `builtins`: if this feature is removed the default filters, tests and //! functions are not implemented. //! - `macros`: when removed the `{% macro %}` tag is not included. //! - `multi_template`: when removed the templates related to imports and extends //! are removed (`{% from %}`, `{% import %}`, `{% include %}`, and `{% extends %}` //! as well as `{% block %}`). //! - `adjacent_loop_items`: when removed the `previtem` and `nextitem` attributes of //! the `loop` object no longer exist. Removing this feature can provide faster //! template execution when a lot of loops are involved. //! - `unicode`: when added unicode identifiers are supported and the `sort` //! filter's case insensitive comparising changes to using unicode and not //! ASCII rules. Without this features only ASCII identifiers can be used //! for variable names and attributes. //! //! - **Rust Functionality:** //! //! - `debug`: if this feature is removed some debug functionality of the engine is //! removed as well. This mainly affects the quality of error reporting. //! - `deserialization`: when removed this disables deserialization support for //! the [`Value`](crate::value::Value) type. //! //! There are some additional features that provide extra functionality: //! //! - `fuel`: enables the `fuel` feature which makes the engine track fuel consumption which //! can be used to better protect against expensive templates. //! - `loader`: enables owned and dynamic template loading of templates. //! - `custom_syntax`: when this feature is enabled, custom delimiters are supported by //! the parser. //! - `preserve_order`: When enable the internal value implementation uses an indexmap //! which preserves the original order of maps and structs. //! - `json`: When enabled the `tojson` filter is added as builtin filter as well as //! the ability to auto escape via `AutoEscape::Json`. //! - `urlencode`: When enabled the `urlencode` filter is added as builtin filter. //! //! Performance and memory related features: //! //! - `speedups`: enables all speedups, in particular it turns on the `v_htmlescape` dependency //! for faster HTML escapling. //! - `key_interning`: if this feature is enabled the automatic string interning in //! the value type is enabled. This feature used to be turned on by default but //! has negative performance effects in newer versions of MiniJinja since a lot of //! the previous uses of key interning are no longer needed. Enabling it however //! cuts down on memory usage slightly in certain scenarios by interning all string //! keys used in dynamic map values. //! //!
    #![allow(clippy::cognitive_complexity)] #![allow(clippy::get_first)] #![cfg_attr(docsrs, feature(doc_cfg))] #![deny(missing_docs)] #![doc(html_logo_url = "https://github.com/mitsuhiko/minijinja/raw/main/artwork/logo-square.png")] #[macro_use] mod macros; mod compiler; mod defaults; mod environment; mod error; mod expression; mod output; mod template; mod utils; mod vm; pub mod filters; pub mod functions; pub mod syntax; pub mod tests; pub mod value; #[cfg(feature = "loader")] mod loader; #[cfg(feature = "loader")] pub use loader::path_loader; #[cfg(feature = "custom_syntax")] mod custom_syntax; #[cfg(feature = "debug")] mod debug; pub use self::defaults::{default_auto_escape_callback, escape_formatter}; pub use self::environment::Environment; pub use self::error::{Error, ErrorKind}; pub use self::expression::Expression; pub use self::output::Output; pub use self::template::Template; pub use self::utils::{AutoEscape, HtmlEscape, UndefinedBehavior}; #[cfg(feature = "custom_syntax")] pub use self::custom_syntax::Syntax; pub use self::macros::__context; pub use self::vm::State; /// This module gives access to the low level machinery. /// /// This module is only provided by the `unstable_machinery` feature and does not /// have a stable interface. It mostly exists for internal testing purposes and /// for debugging. #[cfg(feature = "unstable_machinery")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable_machinery")))] pub mod machinery { #![allow(missing_docs)] pub use crate::compiler::ast; pub use crate::compiler::codegen::CodeGenerator; pub use crate::compiler::instructions::{Instruction, Instructions}; pub use crate::compiler::lexer::{tokenize, SyntaxConfig}; pub use crate::compiler::parser::{parse, parse_with_syntax}; pub use crate::compiler::tokens::{Span, Token}; pub use crate::template::CompiledTemplate; pub use crate::vm::Vm; use crate::Output; /// Creates an [`Output`] that writes into a string. pub fn make_string_output(s: &mut String) -> Output<'_> { Output::with_string(s) } } minijinja-1.0.3/src/loader.rs000064400000000000000000000146361046102023000142120ustar 00000000000000use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt; use std::fs; use std::io; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use memo_map::MemoMap; use self_cell::self_cell; use crate::compiler::lexer::SyntaxConfig; use crate::error::{Error, ErrorKind}; use crate::template::CompiledTemplate; type LoadFunc = dyn for<'a> Fn(&'a str) -> Result, Error> + Send + Sync; /// Internal utility for dynamic template loading. /// /// Because an [`Environment`](crate::Environment) holds a reference to the /// source lifetime it borrows templates from, it becomes very inconvenient when /// it is shared. This object provides a solution for such cases. First templates /// are loaded into the source to decouple the lifetimes from the environment. #[derive(Clone, Default)] pub(crate) struct LoaderStore<'source> { pub syntax_config: SyntaxConfig, loader: Option>, owned_templates: MemoMap, Arc>, borrowed_templates: BTreeMap<&'source str, Arc>>, } impl<'source> fmt::Debug for LoaderStore<'source> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut l = f.debug_list(); for key in self.owned_templates.keys() { l.entry(key); } for key in self.borrowed_templates.keys() { if !self.owned_templates.contains_key(*key) { l.entry(key); } } l.finish() } } self_cell! { struct LoadedTemplate { owner: (Arc, Box), #[covariant] dependent: CompiledTemplate, } } impl fmt::Debug for LoadedTemplate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.borrow_dependent(), f) } } impl<'source> LoaderStore<'source> { pub fn insert(&mut self, name: &'source str, source: &'source str) -> Result<(), Error> { self.insert_cow(Cow::Borrowed(name), Cow::Borrowed(source)) } pub fn insert_cow( &mut self, name: Cow<'source, str>, source: Cow<'source, str>, ) -> Result<(), Error> { match (source, name) { (Cow::Borrowed(source), Cow::Borrowed(name)) => { self.owned_templates.remove(name); self.borrowed_templates.insert( name, Arc::new(ok!(CompiledTemplate::new( name, source, self.syntax_config.clone() ))), ); } (source, name) => { self.borrowed_templates.remove(&name as &str); let name: Arc = name.into(); self.owned_templates.replace( name.clone(), ok!(self.make_owned_template(name, source.to_string())), ); } } Ok(()) } pub fn remove(&mut self, name: &str) { self.borrowed_templates.remove(name); self.owned_templates.remove(name); } pub fn clear(&mut self) { self.borrowed_templates.clear(); self.owned_templates.clear(); } pub fn get(&self, name: &str) -> Result<&CompiledTemplate<'_>, Error> { if let Some(rv) = self.borrowed_templates.get(name) { Ok(&**rv) } else { let name: Arc = name.into(); self.owned_templates .get_or_try_insert(&name.clone(), || -> Result<_, Error> { let loader_result = match self.loader { Some(ref loader) => ok!(loader(&name)), None => None, } .ok_or_else(|| Error::new_not_found(&name)); self.make_owned_template(name, ok!(loader_result)) }) .map(|x| x.borrow_dependent()) } } pub fn set_loader(&mut self, f: F) where F: Fn(&str) -> Result, Error> + Send + Sync + 'static, { self.loader = Some(Arc::new(f)); } fn make_owned_template( &self, name: Arc, source: String, ) -> Result, Error> { LoadedTemplate::try_new( (name, source.into_boxed_str()), |(name, source)| -> Result<_, Error> { CompiledTemplate::new(name, source, self.syntax_config.clone()) }, ) .map(Arc::new) } } /// Safely joins two paths. pub fn safe_join(base: &Path, template: &str) -> Option { let mut rv = base.to_path_buf(); for segment in template.split('/') { if segment.starts_with('.') || segment.contains('\\') { return None; } rv.push(segment); } Some(rv) } /// Helper to load templates from a given directory. /// /// This creates a dynamic loader which looks up templates in the /// given directory. Templates that start with a dot (`.`) or are contained in /// a folder starting with a dot cannot be loaded. /// /// # Example /// /// ```rust /// # use minijinja::{path_loader, Environment}; /// fn create_env() -> Environment<'static> { /// let mut env = Environment::new(); /// env.set_loader(path_loader("path/to/templates")); /// env /// } /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "loader")))] pub fn path_loader<'x, P: AsRef + 'x>( dir: P, ) -> impl for<'a> Fn(&'a str) -> Result, Error> + Send + Sync + 'static { let dir = dir.as_ref().to_path_buf(); move |name| { let path = match safe_join(&dir, name) { Some(path) => path, None => return Ok(None), }; match fs::read_to_string(path) { Ok(result) => Ok(Some(result)), Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), Err(err) => Err( Error::new(ErrorKind::InvalidOperation, "could not read template").with_source(err), ), } } } #[cfg(test)] mod tests { use super::*; use similar_asserts::assert_eq; #[test] fn test_safe_join() { assert_eq!( safe_join(Path::new("foo"), "bar/baz"), Some(PathBuf::from("foo").join("bar").join("baz")) ); assert_eq!(safe_join(Path::new("foo"), ".bar/baz"), None); assert_eq!(safe_join(Path::new("foo"), "bar/.baz"), None); assert_eq!(safe_join(Path::new("foo"), "bar/../baz"), None); } } minijinja-1.0.3/src/macros.rs000064400000000000000000000116511046102023000142220ustar 00000000000000// `ok!` and `some!` are less bloaty alternatives to the standard library's try operator (`?`). // Since we do not need type conversions in this crate we can fall back to much easier match // patterns that compile faster and produce less bloaty code. macro_rules! ok { ($expr:expr) => { match $expr { Ok(val) => val, Err(err) => return Err(err), } }; } macro_rules! some { ($expr:expr) => { match $expr { Some(val) => val, None => return None, } }; } /// Hidden utility module for the [`context!`](crate::context!) macro. #[doc(hidden)] pub mod __context { use crate::value::{KeyRef, MapType, Value, ValueMap, ValueRepr}; use crate::Environment; use std::rc::Rc; use std::sync::Arc; #[inline(always)] pub fn value_optimization() -> impl Drop { crate::value::value_optimization() } #[inline(always)] pub fn make() -> ValueMap { ValueMap::default() } #[inline(always)] pub fn add(ctx: &mut ValueMap, key: &'static str, value: Value) { ctx.insert(KeyRef::Str(key), value); } #[inline(always)] pub fn build(ctx: ValueMap) -> Value { ValueRepr::Map(Arc::new(ctx), MapType::Normal).into() } pub fn thread_local_env() -> Rc> { thread_local! { static ENV: Rc> = Rc::new(Environment::new()); } ENV.with(|x| x.clone()) } } /// Creates a template context with keys and values. /// /// ```rust /// # use minijinja::context; /// let ctx = context! { /// name => "Peter", /// location => "World", /// }; /// ``` /// /// Alternatively if the variable name matches the key name it can /// be omitted: /// /// ```rust /// # use minijinja::context; /// let name = "Peter"; /// let ctx = context! { name }; /// ``` /// /// The return value is a [`Value`](crate::value::Value). /// /// Note that [`context!`](crate::context!) can also be used recursively if you need to /// create nested objects: /// /// ```rust /// # use minijinja::context; /// let ctx = context! { /// nav => vec![ /// context!(path => "/", title => "Index"), /// context!(path => "/downloads", title => "Downloads"), /// context!(path => "/faq", title => "FAQ"), /// ] /// }; /// ``` #[macro_export] macro_rules! context { () => { $crate::__context::build($crate::__context::make()) }; ( $($key:ident $(=> $value:expr)?),* $(,)? ) => {{ let _guard = $crate::__context::value_optimization(); let mut ctx = $crate::__context::make(); $( $crate::__context_pair!(ctx, $key $(, $value)?); )* $crate::__context::build(ctx) }} } #[macro_export] #[doc(hidden)] macro_rules! __context_pair { ($ctx:ident, $key:ident) => {{ $crate::__context_pair!($ctx, $key, $key); }}; ($ctx:ident, $key:ident, $value:expr) => { $crate::__context::add( &mut $ctx, stringify!($key), $crate::value::Value::from_serializable(&$value), ); }; } /// A macro similar to [`format!`] but that uses MiniJinja for rendering. /// /// This can be used to quickly render a MiniJinja template into a string /// without having to create an environment first which can be useful in /// some situations. Note however that the template is re-parsed every /// time the [`render!`](crate::render) macro is called which is potentially /// slow. /// /// There are two forms for this macro. The default form takes template /// source and context variables, the extended form also lets you provide /// a custom environment that should be used rather than a default one. /// The context variables are passed the same way as with the /// [`context!`](crate::context) macro. /// /// # Example /// /// Passing context explicitly: /// /// ``` /// # use minijinja::render; /// println!("{}", render!("Hello {{ name }}!", name => "World")); /// ``` /// /// Passing variables with the default name: /// /// ``` /// # use minijinja::render; /// let name = "World"; /// println!("{}", render!("Hello {{ name }}!", name)); /// ``` /// /// Passing an explicit environment: /// /// ``` /// # use minijinja::{Environment, render}; /// let env = Environment::new(); /// println!("{}", render!(in env, "Hello {{ name }}!", name => "World")); /// ``` /// /// # Panics /// /// This macro panics if the format string is an invalid template or the /// template evaluation failed. #[macro_export] macro_rules! render { ( in $env:expr, $tmpl:expr $(, $key:ident $(=> $value:expr)?)* $(,)? ) => { ($env).render_str($tmpl, $crate::context! { $($key $(=> $value)? ,)* }) .expect("failed to render expression") }; ( $tmpl:expr $(, $key:ident $(=> $value:expr)?)* $(,)? ) => { $crate::render!(in $crate::__context::thread_local_env(), $tmpl, $($key $(=> $value)? ,)*) } } minijinja-1.0.3/src/output.rs000064400000000000000000000117211046102023000142740ustar 00000000000000use std::{fmt, io}; use crate::error::{Error, ErrorKind}; use crate::utils::AutoEscape; use crate::value::Value; /// How should output be captured? #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))] pub enum CaptureMode { Capture, #[allow(unused)] Discard, } /// An abstraction over [`fmt::Write`](std::fmt::Write) for the rendering. /// /// This is a utility type used in the engine which can be written into like one /// can write into an [`std::fmt::Write`] value. It's primarily used internally /// in the engine but it's also passed to the custom formatter function. pub struct Output<'a> { w: &'a mut (dyn fmt::Write + 'a), capture_stack: Vec>, } impl<'a> Output<'a> { /// Creates an output writing to a string. pub(crate) fn with_string(buf: &'a mut String) -> Self { Self { w: buf, capture_stack: Vec::new(), } } pub(crate) fn with_write(w: &'a mut (dyn fmt::Write + 'a)) -> Self { Self { w, capture_stack: Vec::new(), } } /// Creates a null output that writes nowhere. pub(crate) fn null() -> Self { // The null writer also has a single entry on the discarding capture // stack. In fact, `w` is more or less useless here as we always // shadow it. This is done so that `is_discarding` returns true. Self { w: NullWriter::get_mut(), capture_stack: vec![None], } } /// Begins capturing into a string or discard. pub(crate) fn begin_capture(&mut self, mode: CaptureMode) { self.capture_stack.push(match mode { CaptureMode::Capture => Some(String::new()), CaptureMode::Discard => None, }); } /// Ends capturing and returns the captured string as value. pub(crate) fn end_capture(&mut self, auto_escape: AutoEscape) -> Value { if let Some(captured) = self.capture_stack.pop().unwrap() { if !matches!(auto_escape, AutoEscape::None) { Value::from_safe_string(captured) } else { Value::from(captured) } } else { Value::UNDEFINED } } #[inline(always)] fn target(&mut self) -> &mut dyn fmt::Write { match self.capture_stack.last_mut() { Some(Some(stream)) => stream as _, Some(None) => NullWriter::get_mut(), None => self.w, } } /// Returns `true` if the output is discarding. #[inline(always)] #[allow(unused)] pub(crate) fn is_discarding(&self) -> bool { matches!(self.capture_stack.last(), Some(None)) } /// Writes some data to the underlying buffer contained within this output. #[inline] pub fn write_str(&mut self, s: &str) -> fmt::Result { self.target().write_str(s) } /// Writes some formatted information into this instance. #[inline] pub fn write_fmt(&mut self, a: fmt::Arguments<'_>) -> fmt::Result { self.target().write_fmt(a) } } impl fmt::Write for Output<'_> { #[inline] fn write_str(&mut self, s: &str) -> fmt::Result { fmt::Write::write_str(self.target(), s) } #[inline] fn write_char(&mut self, c: char) -> fmt::Result { fmt::Write::write_char(self.target(), c) } #[inline] fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { fmt::Write::write_fmt(self.target(), args) } } pub struct NullWriter; impl NullWriter { /// Returns a reference to the null writer. pub fn get_mut() -> &'static mut NullWriter { static mut NULL_WRITER: NullWriter = NullWriter; // SAFETY: this is safe as the null writer is a ZST unsafe { &mut NULL_WRITER } } } impl fmt::Write for NullWriter { #[inline] fn write_str(&mut self, _s: &str) -> fmt::Result { Ok(()) } #[inline] fn write_char(&mut self, _c: char) -> fmt::Result { Ok(()) } } pub struct WriteWrapper { pub w: W, pub err: Option, } impl WriteWrapper { /// Replaces the given error with the held error if available. pub fn take_err(&mut self, original: Error) -> Error { self.err .take() .map(|io_err| { Error::new(ErrorKind::WriteFailure, "I/O error during rendering") .with_source(io_err) }) .unwrap_or(original) } } impl fmt::Write for WriteWrapper { #[inline] fn write_str(&mut self, s: &str) -> fmt::Result { self.w.write_all(s.as_bytes()).map_err(|e| { self.err = Some(e); fmt::Error }) } #[inline] fn write_char(&mut self, c: char) -> fmt::Result { self.w .write_all(c.encode_utf8(&mut [0; 4]).as_bytes()) .map_err(|e| { self.err = Some(e); fmt::Error }) } } minijinja-1.0.3/src/syntax.rs000064400000000000000000000614611046102023000142700ustar 00000000000000//! Documents the syntax for templates. //! //!
    Table of Contents //! //! - [Synopsis](#synopsis) //! - [Trailing Newlines](#trailing-newlines) //! - [Expressions](#expressions) //! - [Literals](#literals) //! - [Math](#math) //! - [Comparisons](#comparisons) //! - [Logic](#logic) //! - [Other Operators](#other-operators) //! - [If Expressions](#if-expressions) //! - [Tags](#tags) //! - [`{% for %}`](#-for-) //! - [`{% if %}`](#-if-) //! - [`{% extends %}`](#-extends-) //! - [`{% block %}`](#-block-) //! - [`{% include %}`](#-include-) //! - [`{% import %}`](#-import-) //! - [`{% with %}`](#-with-) //! - [`{% set %}`](#-set-) //! - [`{% filter %}`](#-filter-) //! - [`{% macro %}`](#-macro-) //! - [`{% call %}`](#-call-) //! - [`{% do %}`](#-do-) //! - [`{% autoescape %}`](#-autoescape-) //! - [`{% raw %}`](#-raw-) //! - [Custom Delimiters](#custom-delimiters) //! //!
    //! //! # Synopsis //! //! A MiniJinja template is simply a text file. MiniJinja can generate any text-based //! format (HTML, XML, CSV, LaTeX, etc.). A template doesn’t need to have a specific extension //! and in fact MiniJinja does not understand much about the file system. However the default //! configuration for [auto escaping](crate::Environment::set_auto_escape_callback) uses file //! extensions to configure the initial behavior. //! //! A template contains [**expressions**](#expressions), which get replaced with values when a //! template is rendered; and [**tags**](#tags), which control the logic of the template. The //! template syntax is heavily inspired by Jinja2, Django and Python. //! //! This is a minimal template that illustrates a few basics: //! //! ```jinja //! //! {% block title %}My Website{% endblock %} //! //! //!

    My Webpage

    //! {% block body %}{% endblock %} //! //! {# a comment #} //! ``` //! //! # Trailing Newlines //! //! MiniJinja, like Jinja2, will remove one trailing newline from the end of the file automatically //! on parsing. This lets templates produce a consistent output no matter if the editor adds a //! trailing newline or not. If one wants a trailing newline an extra newline can be added or the //! code rendering it adds it manually. //! //! # Expressions //! //! MiniJinja allows basic expressions everywhere. These work largely as you expect from Jinja2. //! Even if you have not used Jinja2 you should feel comfortable with it. To output the result //! of an expression wrap it in `{{ .. }}`. //! //! ## Literals //! //! The simplest form of expressions are literals. Literals are representations for //! objects such as strings and numbers. The following literals exist: //! //! - `"Hello World"`: Everything between two double or single quotes is a string. They are //! useful whenever you need a string in the template (e.g. as arguments to function calls //! and filters, or just to extend or include a template). //! - `42`: Integers are whole numbers without a decimal part. //! - `42.0`: Floating point numbers can be written using a `.` as a decimal mark. //! - `['list', 'of', 'objects']`: Everything between two brackets is a list. Lists are useful //! for storing sequential data to be iterated over. //! for compatibility with Jinja2 `('list', 'of', 'objects')` is also allowed. //! - `{'map': 'of', 'key': 'and', 'value': 'pairs'}`: A map is a structure that combines keys //! and values. Keys must be unique and always have exactly one value. Maps are rarely //! created in templates. //! - `true` / `false` / `none`: boolean values and the special `none` value which maps to the //! unit type in Rust. //! //! ## Math //! //! MiniJinja allows you to calculate with values. The following operators are supported: //! //! - ``+``: Adds two numbers up. ``{{ 1 + 1 }}`` is ``2``. //! - ``-``: Subtract the second number from the first one. ``{{ 3 - 2 }}`` is ``1``. //! - ``/``: Divide two numbers. ``{{ 1 / 2 }}`` is ``0.5``. See note on divisions below. //! - ``//``: Integer divide two numbers. ``{{ 5 // 3 }}`` is ``1``. See note on divisions below. //! - ``%``: Calculate the remainder of an integer division. ``{{ 11 % 7 }}`` is ``4``. //! - ``*``: Multiply the left operand with the right one. ``{{ 2 * 2 }}`` would return ``4``. //! - ``**``: Raise the left operand to the power of the right operand. ``{{ 2**3 }}`` //! would return ``8``. //! //! Note on divisions: divisions in Jinja2 are flooring, divisions in MiniJinja //! are at present using euclidean division. They are almost the same but not quite. //! //! ## Comparisons //! //! - ``==``: Compares two objects for equality. //! - ``!=``: Compares two objects for inequality. //! - ``>``: ``true`` if the left hand side is greater than the right hand side. //! - ``>=``: ``true`` if the left hand side is greater or equal to the right hand side. //! - ``<``:``true`` if the left hand side is less than the right hand side. //! - ``<=``: ``true`` if the left hand side is less or equal to the right hand side. //! //! ## Logic //! //! For ``if`` statements it can be useful to combine multiple expressions: //! //! - ``and``: Return true if the left and the right operand are true. //! - ``or``: Return true if the left or the right operand are true. //! - ``not``: negate a statement (see below). //! - ``(expr)``: Parentheses group an expression. //! //! ## Other Operators //! //! The following operators are very useful but don't fit into any of the other //! two categories: //! //! - ``is``/``is not``: Performs a [test](crate::tests). //! - ``in``/``not in``: Performs a containment check. //! - ``|`` (pipe, vertical bar): Applies a [filter](crate::filters). //! - ``~`` (tilde): Converts all operands into strings and concatenates them. //! ``{{ "Hello " ~ name ~ "!" }}`` would return (assuming `name` is set //! to ``'John'``) ``Hello John!``. //! - ``()``: Call a callable: ``{{ super() }}``. Inside of the parentheses you //! can use positional arguments. Additionally keyword arguments are supported //! which are treated like a dict syntax. Eg: `foo(a=1, b=2)` is the same as //! `foo({"a": 1, "b": 2})`. //! - ``.`` / ``[]``: Get an attribute of an object. If an object does not have a specific //! attribute or item then `undefined` is returned. Accessing a property of an already //! undefined value will result in an error. //! - ``[start:stop]`` / ``[start:stop:step]``: slices a list or string. All three expressions //! are optional (`start`, `stop`, `step`). For instance ``"Hello World"[:5]`` will return //! just `"Hello"`. Likewise ``"Hello"[1:-1]`` will return `"ell"`. The step component can //! be used to change the step size. `"12345"[::2]` will return `"135"`. //! //! ### If Expressions //! //! It is also possible to use inline _if_ expressions. These are useful in some situations. //! For example, you can use this to extend from one template if a variable is defined, //! otherwise from the default layout template: //! //! ```jinja //! {% extends layout_template if layout_template is defined else 'default.html' %} //! ``` //! //! The `else` part is optional. If not provided, the else block implicitly evaluates //! into an undefined value: //! //! ```jinja //! {{ title|upper if title }} //! ``` //! //! # Tags //! //! Tags control logic in templates. The following tags exist: //! //! ## `{% for %}` //! //! The for tag loops over each item in a sequence. For example, to display a list //! of users provided in a variable called `users`: //! //! ```jinja //!

    Members

    //!
      //! {% for user in users %} //!
    • {{ user.username }}
    • //! {% endfor %} //!
    //! ``` //! //! It's also possible to unpack tuples while iterating: //! //! ```jinja //!

    Members

    //!
      //! {% for (key, value) in list_of_pairs %} //!
    • {{ key }}: {{ value }}
    • //! {% endfor %} //!
    //! ``` //! //! Inside of the for block you can access some special variables: //! //! - `loop.index`: The current iteration of the loop. (1 indexed) //! - `loop.index0`: The current iteration of the loop. (0 indexed) //! - `loop.revindex`: The number of iterations from the end of the loop (1 indexed) //! - `loop.revindex0`: The number of iterations from the end of the loop (0 indexed) //! - `loop.first`: True if this is the first iteration. //! - `loop.last`: True if this is the last iteration. //! - `loop.length`: The number of items in the sequence. //! - `loop.cycle`: A helper function to cycle between a list of sequences. See the explanation below. //! - `loop.depth`: Indicates how deep in a recursive loop the rendering currently is. Starts at level 1 //! - `loop.depth0`: Indicates how deep in a recursive loop the rendering currently is. Starts at level 0 //! - `loop.previtem`: The item from the previous iteration of the loop. `Undefined` during the first iteration. //! - `loop.nextitem`: The item from the previous iteration of the loop. `Undefined` during the last iteration. //! - `loop.changed(...args)`: Returns true if the passed values have changed since the last time it was called with the same arguments. //! - `loop.cycle(...args)`: Returns a value from the passed sequence in a cycle. //! //! Within a for-loop, it’s possible to cycle among a list of strings/variables each time through //! the loop by using the special `loop.cycle` helper: //! //! ```jinja //! {% for row in rows %} //!
  • {{ row }}
  • //! {% endfor %} //! ``` //! //! A `loop.changed()` helper is also available which can be used to detect when //! a value changes between the last iteration and the current one. The method //! takes one or more arguments that are all compared. //! //! ```jinja //! {% for entry in entries %} //! {% if loop.changed(entry.category) %} //!

    {{ entry.category }}

    //! {% endif %} //!

    {{ entry.message }}

    //! {% endfor %} //! ``` //! //! Unlike in Rust or Python, it’s not possible to break or continue in a loop. You can, //! however, filter the sequence during iteration, which allows you to skip items. The //! following example skips all the users which are hidden: //! //! ```jinja //! {% for user in users if not user.hidden %} //!
  • {{ user.username }}
  • //! {% endfor %} //! ``` //! //! If no iteration took place because the sequence was empty or the filtering //! removed all the items from the sequence, you can render a default block by //! using else: //! //! ```jinja //!
      //! {% for user in users %} //!
    • {{ user.username }}
    • //! {% else %} //!
    • no users found
    • //! {% endfor %} //!
    //! ``` //! //! It is also possible to use loops recursively. This is useful if you are //! dealing with recursive data such as sitemaps. To use loops recursively, you //! basically have to add the ``recursive`` modifier to the loop definition and //! call the loop variable with the new iterable where you want to recurse. //! //! ```jinja //! //! ``` //! //! **Special note:** the `previtem` and `nextitem` attributes are available by default //! but can be disabled by removing the `adjacent_loop_items` crate feature. Removing //! these attributes can provide meaningful speedups for templates with a lot of loops. //! //! ## `{% if %}` //! //! The `if` statement is comparable with the Python if statement. In the simplest form, //! you can use it to test if a variable is defined, not empty and not false: //! //! ```jinja //! {% if users %} //!
      //! {% for user in users %} //!
    • {{ user.username }}
    • //! {% endfor %} //!
    //! {% endif %} //! ``` //! //! For multiple branches, `elif` and `else` can be used like in Python. You can use more //! complex expressions there too: //! //! ```jinja //! {% if kenny.sick %} //! Kenny is sick. //! {% elif kenny.dead %} //! You killed Kenny! You bastard!!! //! {% else %} //! Kenny looks okay --- so far //! {% endif %} //! ``` //! //! ## `{% extends %}` //! //! **Feature:** `multi_template` (included by default) //! //! The `extends` tag can be used to extend one template from another. You can have multiple //! `extends` tags in a file, but only one of them may be executed at a time. For more //! information see [block](#-block-). //! //! ## `{% block %}` //! //! Blocks are used for inheritance and act as both placeholders and replacements at the //! same time: //! //! The most powerful part of MiniJinja is template inheritance. Template inheritance allows //! you to build a base "skeleton" template that contains all the common elements of your //! site and defines **blocks** that child templates can override. //! //! **Base Template:** //! //! This template, which we'll call ``base.html``, defines a simple HTML skeleton //! document that you might use for a simple two-column page. It's the job of //! "child" templates to fill the empty blocks with content: //! //! ```jinja //! //! {% block head %} //! {% block title %}{% endblock %} //! {% endblock %} //! {% block body %}{% endblock %} //! ``` //! //! **Child Template:** //! //! ```jinja //! {% extends "base.html" %} //! {% block title %}Index{% endblock %} //! {% block head %} //! {{ super() }} //! //! {% endblock %} //! {% block body %} //!

    Index

    //!

    //! Welcome to my awesome homepage. //!

    //! {% endblock %} //! ``` //! //! The ``{% extends %}`` tag is the key here. It tells the template engine that //! this template "extends" another template. When the template system evaluates //! this template, it first locates the parent. The extends tag should be the //! first tag in the template. //! //! As you can see it's also possible to render the contents of the parent block by calling //! ``super()``. You can’t define multiple ``{% block %}`` tags with the same name in //! the same template. This limitation exists because a block tag works in “both” //! directions. That is, a block tag doesn’t just provide a placeholder to fill - //! it also defines the content that fills the placeholder in the parent. If //! there were two similarly-named ``{% block %}`` tags in a template, that //! template’s parent wouldn’t know which one of the blocks’ content to use. //! //! If you want to print a block multiple times, you can, however, use the //! special self variable and call the block with that name: //! //! ```jinja //! {% block title %}{% endblock %} //!

    {{ self.title() }}

    //! {% block body %}{% endblock %} //! ``` //! //! MiniJinja allows you to put the name of the block after the end tag for better //! readability: //! //! ```jinja //! {% block sidebar %} //! {% block inner_sidebar %} //! ... //! {% endblock inner_sidebar %} //! {% endblock sidebar %} //! ``` //! //! However, the name after the `endblock` word must match the block name. //! //! ## `{% include %}` //! //! **Feature:** `multi_template` (included by default) //! //! The `include` tag is useful to include a template and return the rendered contents of that file //! into the current namespace:: //! //! ```jinja //! {% include 'header.html' %} //! Body //! {% include 'footer.html' %} //! ``` //! //! Optionally `ignore missing` can be added in which case non existing templates //! are silently ignored. //! //! ```jinja //! {% include 'customization.html' ignore missing %} //! ``` //! //! You can also provide a list of templates that are checked for existence //! before inclusion. The first template that exists will be included. If `ignore //! missing` is set, it will fall back to rendering nothing if none of the //! templates exist, otherwise it will fail with an error. //! //! ```jinja //! {% include ['page_detailed.html', 'page.html'] %} //! {% include ['special_sidebar.html', 'sidebar.html'] ignore missing %} //! ``` //! //! Included templates have access to the variables of the active context. //! //! ## `{% import %}` //! //! **Feature:** `multi_template` (included by default) //! //! MiniJinja supports the `{% import %}` and `{% from ... import ... %}` //! syntax. With it variables or macros can be included from other templates: //! //! ```jinja //! {% from "my_template.html" import my_macro, my_variable %} //! ``` //! //! Imports can also be aliased: //! //! ```jinja //! {% from "my_template.html" import my_macro as other_name %} //! {{ other_name() }} //! ``` //! //! Full modules can be imported with `{% import ... as ... %}`: //! //! ```jinja //! {% import "my_template.html" as helpers %} //! {{ helpers.my_macro() }} //! ``` //! //! Note that unlike Jinja2, exported modules do not contain any template code. Only //! variables and macros that are defined can be imported. Also imports unlike in Jinja2 //! are not cached and they get access to the full template context. //! //! ## `{% with %}` //! //! The with statement makes it possible to create a new inner scope. Variables set within //! this scope are not visible outside of the scope: //! //! ```jinja //! {% with foo = 42 %} //! {{ foo }} foo is 42 here //! {% endwith %} //! foo is not visible here any longer //! ``` //! //! Multiple variables can be set at once and unpacking is supported: //! //! ```jinja //! {% with a = 1, (b, c) = [2, 3] %} //! {{ a }}, {{ b }}, {{ c }} (outputs 1, 2, 3) //! {% endwith %} //! ``` //! //! ## `{% set %}` //! //! The `set` statement can be used to assign to variables on the same scope. This is //! similar to how `with` works but it won't introduce a new scope. //! //! ```jinja //! {% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %} //! ``` //! //! Please keep in mind that it is not possible to set variables inside a block //! and have them show up outside of it. This also applies to loops. The only //! exception to that rule are if statements which do not introduce a scope. //! //! It's also possible to capture blocks of template code into a variable by using //! the `set` statement as a block. In that case, instead of using an equals sign //! and a value, you just write the variable name and then everything until //! `{% endset %}` is captured. //! //! ```jinja //! {% set navigation %} //!
  • Index //!
  • Downloads //! {% endset %} //! ``` //! //! The `navigation` variable then contains the navigation HTML source. //! //! This can also be combined with applying a filter: //! //! ```jinja //! {% set title | upper %}Title of the page{% endset %} //! ``` //! //! ## `{% filter %}` //! //! Filter sections allow you to apply regular [filters](crate::filters) on a //! block of template data. Just wrap the code in the special filter block: //! //! ```jinja //! {% filter upper %} //! This text becomes uppercase //! {% endfilter %} //! ``` //! //! ## `{% macro %}` //! //! **Feature:** `macros` (included by default) //! //! MiniJinja has limited support for macros. They allow you to write reusable //! template functions. They are useful to put often used idioms into reusable //! functions so you don't repeat yourself (“DRY”). //! //! Here’s a small example of a macro that renders a form element: //! //! ```jinja //! {% macro input(name, value="", type="text") -%} //! //! {%- endmacro %} //! ``` //! //! The macro can then be called like a function in the namespace: //! //! ```jinja //!

    {{ input('username') }}

    //!

    {{ input('password', type='password') }}

    //! ``` //! //! The behavior of macros with regards to undefined variables is that they capture //! them at macro declaration time (eg: they use a closure). //! //! Macros can be imported via `{% import %}` or `{% from ... import %}`. //! //! Macros also accept a hidden `caller` keyword argument for the use with //! `{% call %}`. //! //! ## `{% call %}` //! //! **Feature:** `macros` (included by default) //! //! This tag functions similar to a macro that is passed to another macro. You can //! think of it as a way to declare an anonymous macro and pass it to another macro //! with the `caller` keyword argument. The following example shows a macro that //! takes advantage of the call functionality and how it can be used: //! //! ```jinja //! {% macro dialog(title) %} //!
    //!

    {{ title }}

    //!
    {{ caller() }}
    //!
    //! {% endmacro %} //! //! {% call dialog(title="Hello World") %} //! This is the dialog body. //! {% endcall %} //! ``` //! //!
    Macro Alternative: //! //! The above example is more or less equivalent with the following: //! //! ```jinja //! {% macro dialog(title) %} //!
    //!

    {{ title }}

    //!
    {{ caller() }}
    //!
    //! {% endmacro %} //! //! {% macro caller() %} //! This is the dialog body. //! {% endmacro %} //! {{ dialog(title="Hello World", caller=caller) }} //! ``` //! //!
    //! //! It’s also possible to pass arguments back to the call block. This makes it //! useful to build macros that behave like if statements or loops. Arguments //! are placed surrounded in parentheses right after the `call` keyword and //! is followed by the macro to be called. //! //! ```jinja //! {% macro render_user_list(users) %} //!
      //! {% for user in users %} //!
    • {{ user.username }}

      {{ caller(user) }}
    • //! {% endfor %} //!
    //! {% endmacro %} //! //! {% call(user) render_user_list(list_of_user) %} //!
    //!
    Name
    //!
    {{ user.name }}
    //!
    E-Mail
    //!
    {{ user.email }}
    //!
    //! {% endcall %} //! ``` //! //! ## `{% do %}` //! //! The do tag has the same functionality as regular template tags (`{{ ... }}`); //! except it doesn't output anything when called. //! //! This is useful if you have a function or macro that has a side-effect, and //! you don’t want to display output in the template. The following example //! shows a macro that uses the do functionality, and how it can be used: //! //! ```jinja //! {% macro dialog(title) %} //! Dialog is {{ title }} //! {% endmacro %} //! //! {% do dialog(title="Hello World") %} <- does not output anything //! ``` //! //! The above example will not output anything when using the `do` tag. //! //! This tag exists for consistency with Jinja2 and can be useful if you have //! custom functionality in templates that uses side-effects. For instance if //! you expose a function to templates that can be used to log warnings: //! //! ```jinja //! {% for user in users %} //! {% if user.deleted %} //! {% log warn("Found unexpected deleted user in template") %} //! {% endif %} //! ... //! {% endfor %} //! ``` //! //! ## `{% autoescape %}` //! //! If you want you can activate and deactivate the autoescaping from within //! the templates. //! //! Example: //! //! ```jinja //! {% autoescape true %} //! Autoescaping is active within this block //! {% endautoescape %} //! //! {% autoescape false %} //! Autoescaping is inactive within this block //! {% endautoescape %} //! ``` //! //! After an `endautoescape` the behavior is reverted to what it was before. //! //! The exact auto escaping behavior is determined by the value of //! [`AutoEscape`](crate::AutoEscape) set to the template. //! //! ## `{% raw %}` //! //! A raw block is a special construct that lets you ignore the embedded template //! syntax. This is particularly useful if a segment of template code would //! otherwise require constant escaping with things like `{{ "{{" }}`: //! //! Example: //! //! ```jinja //! {% raw %} //!
      //! {% for item in seq %} //!
    • {{ item }}
    • //! {% endfor %} //!
    //! {% endraw %} //! ``` //! #![cfg_attr( feature = "custom_syntax", doc = r#" # Custom Delimiters When MiniJinja has been compiled with the `custom_syntax` feature (see [`Syntax`](crate::Syntax)), it's possible to reconfigure the delimiters of the templates. This is generally not recommended but it's useful for situations where Jinja templates are used to generate files with a syntax that would be conflicting for Jinja. With custom delimiters it can for instance be more convenient to generate LaTeX files: ``` # use minijinja::{Environment, Syntax}; let mut environment = Environment::new(); environment.set_syntax(Syntax { block_start: "\\BLOCK{".into(), block_end: "}".into(), variable_start: "\\VAR{".into(), variable_end: "}".into(), comment_start: "\\#{".into(), comment_end: "}".into(), }).unwrap(); ``` And then a template might look like this instead: ```latex \begin{itemize} \BLOCK{for item in sequence} \item \VAR{item} \BLOCK{endfor} \end{itemize} ``` "# )] minijinja-1.0.3/src/template.rs000064400000000000000000000330251046102023000145500ustar 00000000000000use std::collections::{BTreeMap, HashSet}; use std::ops::Deref; use std::sync::Arc; use std::{fmt, io}; use serde::Serialize; use crate::compiler::codegen::CodeGenerator; use crate::compiler::instructions::Instructions; use crate::compiler::lexer::SyntaxConfig; use crate::compiler::meta::find_undeclared; use crate::compiler::parser::parse_with_syntax; use crate::environment::Environment; use crate::error::{attach_basic_debug_info, Error}; use crate::output::{Output, WriteWrapper}; use crate::utils::AutoEscape; use crate::value::{self, Value}; use crate::vm::{prepare_blocks, State, Vm}; /// Represents a handle to a template. /// /// Templates are stored in the [`Environment`] as bytecode instructions. With the /// [`Environment::get_template`] method that is looked up and returned in form of /// this handle. Such a template can be cheaply copied as it only holds references. /// /// To render the [`render`](Template::render) method can be used. #[derive(Clone)] pub struct Template<'env: 'source, 'source> { env: &'env Environment<'env>, compiled: CompiledTemplateRef<'env, 'source>, initial_auto_escape: AutoEscape, } impl<'env, 'source> fmt::Debug for Template<'env, 'source> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ds = f.debug_struct("Template"); ds.field("name", &self.name()); #[cfg(feature = "internal_debug")] { ds.field("instructions", &self.compiled.instructions); ds.field("blocks", &self.compiled.blocks); } ds.field("initial_auto_escape", &self.initial_auto_escape); ds.finish() } } impl<'env, 'source> Template<'env, 'source> { pub(crate) fn new( env: &'env Environment<'env>, compiled: CompiledTemplateRef<'env, 'source>, initial_auto_escape: AutoEscape, ) -> Template<'env, 'source> { Template { env, compiled, initial_auto_escape, } } /// Returns the name of the template. pub fn name(&self) -> &str { self.compiled.instructions.name() } /// Returns the source code of the template. pub fn source(&self) -> &str { self.compiled.instructions.source() } /// Renders the template into a string. /// /// The provided value is used as the initial context for the template. It /// can be any object that implements [`Serialize`](serde::Serialize). You /// can either create your own struct and derive `Serialize` for it or the /// [`context!`](crate::context) macro can be used to create an ad-hoc context. /// /// For very large contexts and to avoid the overhead of serialization of /// potentially unused values, you might consider using a dynamic /// [`StructObject`](crate::value::StructObject) as value. For more /// information see [Struct as Context](crate::value::StructObject#struct-as-context). /// /// ``` /// # use minijinja::{Environment, context}; /// # let mut env = Environment::new(); /// # env.add_template("hello", "Hello {{ name }}!").unwrap(); /// let tmpl = env.get_template("hello").unwrap(); /// println!("{}", tmpl.render(context!(name => "John")).unwrap()); /// ``` /// /// To render a single block use [`eval_to_state`](Self::eval_to_state) in /// combination with [`State::render_block`]. /// /// **Note on values:** The [`Value`] type implements `Serialize` and can be /// efficiently passed to render. It does not undergo actual serialization. pub fn render(&self, ctx: S) -> Result { // reduce total amount of code faling under mono morphization into // this function, and share the rest in _render. self._render(Value::from_serializable(&ctx)).map(|x| x.0) } /// Like [`render`](Self::render) but also return the evaluated [`State`]. /// /// This can be used to inspect the [`State`] of the template post evaluation /// for instance to get fuel consumption numbers or to access globally set /// variables. /// /// ``` /// # use minijinja::{Environment, context, value::Value}; /// # let mut env = Environment::new(); /// let tmpl = env.template_from_str("{% set x = 42 %}Hello {{ what }}!").unwrap(); /// let (rv, state) = tmpl.render_and_return_state(context!{ what => "World" }).unwrap(); /// assert_eq!(rv, "Hello World!"); /// assert_eq!(state.lookup("x"), Some(Value::from(42))); /// ``` /// /// **Note on values:** The [`Value`] type implements `Serialize` and can be /// efficiently passed to render. It does not undergo actual serialization. pub fn render_and_return_state( &self, ctx: S, ) -> Result<(String, State<'_, 'env>), Error> { // reduce total amount of code faling under mono morphization into // this function, and share the rest in _render. self._render(Value::from_serializable(&ctx)) } fn _render(&self, root: Value) -> Result<(String, State<'_, 'env>), Error> { let mut rv = String::with_capacity(self.compiled.buffer_size_hint); self._eval(root, &mut Output::with_string(&mut rv)) .map(|(_, state)| (rv, state)) } /// Renders the template into an [`io::Write`]. /// /// This works exactly like [`render`](Self::render) but instead writes the template /// as it's evaluating into an [`io::Write`]. It also returns the [`State`] like /// [`render_and_return_state`](Self::render_and_return_state) does. /// /// ``` /// # use minijinja::{Environment, context}; /// # let mut env = Environment::new(); /// # env.add_template("hello", "Hello {{ name }}!").unwrap(); /// use std::io::stdout; /// /// let tmpl = env.get_template("hello").unwrap(); /// tmpl.render_to_write(context!(name => "John"), &mut stdout()).unwrap(); /// ``` /// /// **Note on values:** The [`Value`] type implements `Serialize` and can be /// efficiently passed to render. It does not undergo actual serialization. pub fn render_to_write( &self, ctx: S, w: W, ) -> Result, Error> { let mut wrapper = WriteWrapper { w, err: None }; self._eval( Value::from_serializable(&ctx), &mut Output::with_write(&mut wrapper), ) .map(|(_, state)| state) .map_err(|err| wrapper.take_err(err)) } /// Evaluates the template into a [`State`]. /// /// This evaluates the template, discards the output and returns the final /// `State` for introspection. From there global variables or blocks /// can be accessed. What this does is quite similar to how the engine /// interally works with tempaltes that are extended or imported from. /// /// ``` /// # use minijinja::{Environment, context}; /// # fn test() -> Result<(), minijinja::Error> { /// # let mut env = Environment::new(); /// # env.add_template("hello", "{% block hi %}Hello {{ name }}!{% endblock %}")?; /// let tmpl = env.get_template("hello")?; /// let rv = tmpl /// .eval_to_state(context!(name => "John"))? /// .render_block("hi")?; /// println!("{}", rv); /// # Ok(()) } /// ``` /// /// If you also want to render, use [`render_and_return_state`](Self::render_and_return_state). /// /// For more information see [`State`]. pub fn eval_to_state(&self, ctx: S) -> Result, Error> { let root = Value::from_serializable(&ctx); let mut out = Output::null(); let vm = Vm::new(self.env); let state = ok!(vm.eval( &self.compiled.instructions, root, &self.compiled.blocks, &mut out, self.initial_auto_escape, )) .1; Ok(state) } fn _eval( &self, root: Value, out: &mut Output, ) -> Result<(Option, State<'_, 'env>), Error> { Vm::new(self.env).eval( &self.compiled.instructions, root, &self.compiled.blocks, out, self.initial_auto_escape, ) } /// Returns a set of all undeclared variables in the template. /// /// This returns a set of all variables that might be looked up /// at runtime by the template. Since this is runs a static /// analysis, the actual control flow is not considered. This /// also cannot take into account what happens due to includes, /// imports or extending. If `nested` is set to `true`, then also /// nested trivial attribute lookups are considered and returned. /// /// ```rust /// # use minijinja::Environment; /// let mut env = Environment::new(); /// env.add_template("x", "{% set x = foo %}{{ x }}{{ bar.baz }}").unwrap(); /// let tmpl = env.get_template("x").unwrap(); /// let undeclared = tmpl.undeclared_variables(false); /// // returns ["foo", "bar"] /// let undeclared = tmpl.undeclared_variables(true); /// // returns ["foo", "bar.baz"] /// ``` pub fn undeclared_variables(&self, nested: bool) -> HashSet { match parse_with_syntax( self.compiled.instructions.source(), self.name(), self.compiled.syntax.clone(), ) { Ok(ast) => find_undeclared(&ast, nested), Err(_) => HashSet::new(), } } /// Creates an empty [`State`] for this template. /// /// It's very rare that you need to actually do this but it can be useful when /// testing values or working with macros or other callable objects from outside /// the template environment. pub fn new_state(&self) -> State<'_, 'env> { State::new( self.env, Default::default(), self.initial_auto_escape, &self.compiled.instructions, prepare_blocks(&self.compiled.blocks), ) } /// Returns the instructions and blocks if the template is loaded from the /// environment. /// /// For templates loaded as string on the environment this API contract /// cannot be upheld because the template might not live long enough. Under /// normal cirumstances however such a template object would never make it /// to the callers of this API as this API is used for including or extending, /// both of which should only ever get access to a template from the environment /// which holds a borrowed ref. #[cfg(feature = "multi_template")] pub(crate) fn instructions_and_blocks( &self, ) -> Result< ( &'env Instructions<'env>, &'env BTreeMap<&'env str, Instructions<'env>>, ), Error, > { match self.compiled { CompiledTemplateRef::Borrowed(x) => Ok((&x.instructions, &x.blocks)), CompiledTemplateRef::Owned(_) => Err(Error::new( crate::ErrorKind::InvalidOperation, "cannot extend or include template not borrowed from environment", )), } } /// Returns the initial auto escape setting. #[cfg(feature = "multi_template")] pub(crate) fn initial_auto_escape(&self) -> AutoEscape { self.initial_auto_escape } } #[derive(Clone)] pub(crate) enum CompiledTemplateRef<'env: 'source, 'source> { Owned(Arc>), Borrowed(&'env CompiledTemplate<'source>), } impl<'env, 'source> Deref for CompiledTemplateRef<'env, 'source> { type Target = CompiledTemplate<'source>; fn deref(&self) -> &Self::Target { match self { CompiledTemplateRef::Owned(ref x) => x, CompiledTemplateRef::Borrowed(x) => x, } } } /// Represents a compiled template in memory. pub struct CompiledTemplate<'source> { /// The root instructions. pub instructions: Instructions<'source>, /// Block local instructions. pub blocks: BTreeMap<&'source str, Instructions<'source>>, /// Optional size hint for string rendering. pub buffer_size_hint: usize, /// The syntax config that created it. pub syntax: SyntaxConfig, } impl<'env> fmt::Debug for CompiledTemplate<'env> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ds = f.debug_struct("CompiledTemplate"); #[cfg(feature = "internal_debug")] { ds.field("instructions", &self.instructions); ds.field("blocks", &self.blocks); } ds.finish() } } impl<'source> CompiledTemplate<'source> { /// Creates a compiled template from name and source using the given settings. pub fn new( name: &'source str, source: &'source str, syntax: SyntaxConfig, ) -> Result, Error> { attach_basic_debug_info(Self::_new_impl(name, source, syntax), source) } fn _new_impl( name: &'source str, source: &'source str, syntax: SyntaxConfig, ) -> Result, Error> { // the parser/compiler combination can create constants in which case // we can probably benefit from the value optimization a bit. let _guard = value::value_optimization(); let ast = ok!(parse_with_syntax(source, name, syntax.clone())); let mut gen = CodeGenerator::new(name, source); gen.compile_stmt(&ast); let buffer_size_hint = gen.buffer_size_hint(); let (instructions, blocks) = gen.finish(); Ok(CompiledTemplate { instructions, blocks, buffer_size_hint, syntax, }) } } minijinja-1.0.3/src/tests.rs000064400000000000000000000336301046102023000141010ustar 00000000000000//! Test functions and abstractions. //! //! Test functions in MiniJinja are like [`filters`](crate::filters) but a //! different syntax is used to invoke them and they have to return boolean //! values. For instance the expression `{% if foo is defined %}` invokes the //! [`is_defined`] test to check if the value is indeed an odd number. //! //! MiniJinja comes with some built-in test functions that are listed below. To //! create a custom test write a function that takes at least a value argument //! that returns a boolean result, then register it with //! [`add_filter`](crate::Environment::add_test). //! //! # Using Tests //! //! Tests are useful to "test" a value in a specific way. For instance if //! you want to assign different classes to alternating rows one way is //! using the `odd` test: //! //! ```jinja //! {% if seq is defined %} //!
      //! {% for item in seq %} //!
    • {{ item }}
    • //! {% endfor %} //!
    //! {% endif %} //! ``` //! //! # Custom Tests //! //! A custom test function is just a simple function which accepts its //! inputs as parameters and then returns a bool. For instance the following //! shows a test function which takes an input value and checks if it's //! lowercase: //! //! ``` //! # use minijinja::Environment; //! # let mut env = Environment::new(); //! fn is_lowercase(value: String) -> bool { //! value.chars().all(|x| x.is_lowercase()) //! } //! //! env.add_test("lowercase", is_lowercase); //! ``` //! //! MiniJinja will perform the necessary conversions automatically. For more //! information see the [`Test`] trait. //! //! # Built-in Tests //! //! When the `builtins` feature is enabled a range of built-in tests are //! automatically added to the environment. These are also all provided in //! this module. Note though that these functions are not to be //! called from Rust code as their exact interface (arguments and return types) //! might change from one MiniJinja version to another. use std::sync::Arc; use crate::error::Error; use crate::utils::SealedMarker; use crate::value::{ArgType, FunctionArgs, Value}; use crate::vm::State; type TestFunc = dyn Fn(&State, &[Value]) -> Result + Sync + Send + 'static; #[derive(Clone)] pub(crate) struct BoxedTest(Arc); /// A utility trait that represents the return value of filters. /// /// It's implemented for the following types: /// /// * `bool` /// * `Result` /// /// The equivalent for filters or functions is [`FunctionResult`](crate::value::FunctionResult). pub trait TestResult { #[doc(hidden)] fn into_result(self) -> Result; } impl TestResult for Result { fn into_result(self) -> Result { self } } impl TestResult for bool { fn into_result(self) -> Result { Ok(self) } } /// A utility trait that represents test functions. /// /// This trait is used by the [`add_test`](crate::Environment::add_test) method to abstract over /// different types of functions that implement tests. Tests are similar to /// [`filters`](crate::filters) but they always return boolean values and use a /// slightly different syntax to filters. Like filters they accept the [`State`] by /// reference as first parameter and the value that that the test is applied to as second. /// Additionally up to 4 further parameters are supported. /// /// A test function can return any of the following types: /// /// * `bool` /// * `Result` /// /// Tests accept one mandatory parameter which is the value the filter is /// applied to and up to 4 extra parameters. The extra parameters can be /// marked optional by using `Option`. The last argument can also use /// [`Rest`](crate::value::Rest) to capture the remaining arguments. All /// types are supported for which [`ArgType`] is implemented. /// /// For a list of built-in tests see [`tests`](crate::tests). /// /// # Basic Example /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// use minijinja::State; /// /// fn is_lowercase(value: String) -> bool { /// value.chars().all(|x| x.is_lowercase()) /// } /// /// env.add_test("lowercase", is_lowercase); /// ``` /// /// ```jinja /// {{ "foo" is lowercase }} -> true /// ``` /// /// # Arguments and Optional Arguments /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// use minijinja::State; /// /// fn is_containing(value: String, other: String) -> bool { /// value.contains(&other) /// } /// /// env.add_test("containing", is_containing); /// ``` /// /// ```jinja /// {{ "foo" is containing("o") }} -> true /// ``` pub trait Test: Send + Sync + 'static { /// Performs a test to value with the given arguments. #[doc(hidden)] fn perform(&self, args: Args, _: SealedMarker) -> Rv; } macro_rules! tuple_impls { ( $( $name:ident )* ) => { impl Test for Func where Func: Fn($($name),*) -> Rv + Send + Sync + 'static, Rv: TestResult, $($name: for<'a> ArgType<'a>),* { fn perform(&self, args: ($($name,)*), _: SealedMarker) -> Rv { #[allow(non_snake_case)] let ($($name,)*) = args; (self)($($name,)*) } } }; } tuple_impls! {} tuple_impls! { A } tuple_impls! { A B } tuple_impls! { A B C } tuple_impls! { A B C D } tuple_impls! { A B C D E } impl BoxedTest { /// Creates a new boxed filter. pub fn new(f: F) -> BoxedTest where F: Test + for<'a> Test>::Output>, Rv: TestResult, Args: for<'a> FunctionArgs<'a>, { BoxedTest(Arc::new(move |state, args| -> Result { f.perform(ok!(Args::from_values(Some(state), args)), SealedMarker) .into_result() })) } /// Applies the filter to a value and argument. pub fn perform(&self, state: &State, args: &[Value]) -> Result { (self.0)(state, args) } } /// Checks if a value is undefined. /// /// ```jinja /// {{ 42 is undefined }} -> false /// ``` pub fn is_undefined(v: Value) -> bool { v.is_undefined() } /// Checks if a value is defined. /// /// ```jinja /// {{ 42 is defined }} -> true /// ``` pub fn is_defined(v: Value) -> bool { !v.is_undefined() } /// Checks if a value is none. /// /// ```jinja /// {{ none is none }} -> true /// ``` pub fn is_none(v: Value) -> bool { v.is_none() } /// Checks if a value is safe. /// /// ```jinja /// {{ ""|escape is safe }} -> true /// ``` /// /// This filter is also registered with the `escaped` alias. pub fn is_safe(v: Value) -> bool { v.is_safe() } #[cfg(feature = "builtins")] mod builtins { use super::*; use std::borrow::Cow; use std::convert::TryFrom; use crate::value::ValueKind; /// Checks if a value is odd. /// /// ```jinja /// {{ 41 is odd }} -> true /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn is_odd(v: Value) -> bool { i128::try_from(v).ok().map_or(false, |x| x % 2 != 0) } /// Checks if a value is even. /// /// ```jinja /// {{ 42 is even }} -> true /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn is_even(v: Value) -> bool { i128::try_from(v).ok().map_or(false, |x| x % 2 == 0) } /// Checks if this value is a number. /// /// ```jinja /// {{ 42 is number }} -> true /// {{ "42" is number }} -> false /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn is_number(v: Value) -> bool { matches!(v.kind(), ValueKind::Number) } /// Checks if this value is a string. /// /// ```jinja /// {{ "42" is string }} -> true /// {{ 42 is string }} -> false /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn is_string(v: Value) -> bool { matches!(v.kind(), ValueKind::String) } /// Checks if this value is a sequence /// /// ```jinja /// {{ [1, 2, 3] is sequence }} -> true /// {{ 42 is sequence }} -> false /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn is_sequence(v: Value) -> bool { matches!(v.kind(), ValueKind::Seq) } /// Checks if this value is a mapping /// /// ```jinja /// {{ {"foo": "bar"} is mapping }} -> true /// {{ [1, 2, 3] is mapping }} -> false /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn is_mapping(v: Value) -> bool { matches!(v.kind(), ValueKind::Map) } /// Checks if the value is starting with a string. /// /// ```jinja /// {{ "foobar" is startingwith("foo") }} -> true /// {{ "foobar" is startingwith("bar") }} -> false /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn is_startingwith(v: Cow<'_, str>, other: Cow<'_, str>) -> bool { v.starts_with(&other as &str) } /// Checks if the value is ending with a string. /// /// ```jinja /// {{ "foobar" is endingwith("bar") }} -> true /// {{ "foobar" is endingwith("foo") }} -> false /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] pub fn is_endingwith(v: Cow<'_, str>, other: Cow<'_, str>) -> bool { v.ends_with(&other as &str) } /// Test version of `==`. /// /// This is useful when combined with [`select`](crate::filters::select). /// /// ```jinja /// {{ 1 is eq 1 }} -> true /// {{ [1, 2, 3]|select("==", 1) }} => [1] /// ``` /// /// By default aliased to `equalto` and `==`. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_eq(value: &Value, other: &Value) -> bool { *value == *other } /// Test version of `!=`. /// /// This is useful when combined with [`select`](crate::filters::select). /// /// ```jinja /// {{ 2 is ne 1 }} -> true /// {{ [1, 2, 3]|select("!=", 1) }} => [2, 3] /// ``` /// /// By default aliased to `!=`. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_ne(value: &Value, other: &Value) -> bool { *value != *other } /// Test version of `<`. /// /// This is useful when combined with [`select`](crate::filters::select). /// /// ```jinja /// {{ 1 is lt 2 }} -> true /// {{ [1, 2, 3]|select("<", 2) }} => [1] /// ``` /// /// By default aliased to `lessthan` and `<`. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_lt(value: &Value, other: &Value) -> bool { *value < *other } /// Test version of `<=`. /// /// This is useful when combined with [`select`](crate::filters::select). /// /// ```jinja /// {{ 1 is le 2 }} -> true /// {{ [1, 2, 3]|select("<=", 2) }} => [1, 2] /// ``` /// /// By default aliased to `<=`. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_le(value: &Value, other: &Value) -> bool { *value <= *other } /// Test version of `>`. /// /// This is useful when combined with [`select`](crate::filters::select). /// /// ```jinja /// {{ 2 is gt 1 }} -> true /// {{ [1, 2, 3]|select(">", 2) }} => [3] /// ``` /// /// By default aliased to `greaterthan` and `>`. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_gt(value: &Value, other: &Value) -> bool { *value > *other } /// Test version of `>=`. /// /// This is useful when combined with [`select`](crate::filters::select). /// /// ```jinja /// {{ 2 is ge 1 }} -> true /// {{ [1, 2, 3]|select(">=", 2) }} => [2, 3] /// ``` /// /// By default aliased to `>=`. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_ge(value: &Value, other: &Value) -> bool { *value >= *other } /// Test version of `in`. /// /// ```jinja /// {{ 1 is in [1, 2, 3] }} -> true /// {{ [1, 2, 3]|select("in", [1, 2]) }} => [1, 2] /// ``` /// /// This is useful when combined with [`select`](crate::filters::select). #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_in(value: &Value, other: &Value) -> bool { crate::value::ops::contains(other, value) .map(|value| value.is_true()) .unwrap_or(false) } /// Checks if a value is `true`. /// /// ```jinja /// {% if value is true %}...{% endif %} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_true(value: &Value) -> bool { matches!(value.0, crate::value::ValueRepr::Bool(true)) } /// Checks if a value is `false`. /// /// ```jinja /// {% if value is false %}...{% endif %} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_false(value: &Value) -> bool { matches!(value.0, crate::value::ValueRepr::Bool(false)) } /// Checks if a filter with a given name is available. /// /// ```jinja /// {% if 'tojson' is filter %}...{% endif %} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_filter(state: &State, name: &str) -> bool { state.env.get_filter(name).is_some() } /// Checks if a test with a given name is available. /// /// ```jinja /// {% if 'greaterthan' is test %}...{% endif %} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] #[cfg(feature = "builtins")] pub fn is_test(state: &State, name: &str) -> bool { state.env.get_test(name).is_some() } } #[cfg(feature = "builtins")] pub use self::builtins::*; minijinja-1.0.3/src/utils.rs000064400000000000000000000275651046102023000141110ustar 00000000000000use std::char::decode_utf16; use std::collections::BTreeMap; use std::fmt; use std::iter::{once, repeat}; use std::str::Chars; use crate::error::{Error, ErrorKind}; use crate::value::{OwnedValueIterator, StringType, Value, ValueKind, ValueRepr}; use crate::Output; /// internal marker to seal up some trait methods pub struct SealedMarker; pub fn memchr(haystack: &[u8], needle: u8) -> Option { haystack.iter().position(|&x| x == needle) } pub fn memstr(haystack: &[u8], needle: &[u8]) -> Option { haystack .windows(needle.len()) .position(|window| window == needle) } /// Helper for dealing with untrusted size hints. #[inline(always)] pub(crate) fn untrusted_size_hint(value: usize) -> usize { value.min(1024) } fn write_with_html_escaping(out: &mut Output, value: &Value) -> fmt::Result { if matches!( value.kind(), ValueKind::Undefined | ValueKind::None | ValueKind::Bool | ValueKind::Number ) { write!(out, "{value}") } else if let Some(s) = value.as_str() { write!(out, "{}", HtmlEscape(s)) } else { write!(out, "{}", HtmlEscape(&value.to_string())) } } fn invalid_autoescape(name: &str) -> Result<(), Error> { Err(Error::new( ErrorKind::InvalidOperation, format!("Default formatter does not know how to format to custom format '{name}'"), )) } #[inline(always)] pub fn write_escaped( out: &mut Output, auto_escape: AutoEscape, value: &Value, ) -> Result<(), Error> { // common case of safe strings or strings without auto escaping if let ValueRepr::String(ref s, ty) = value.0 { if matches!(ty, StringType::Safe) || matches!(auto_escape, AutoEscape::None) { return out.write_str(s).map_err(Error::from); } } match auto_escape { AutoEscape::None => write!(out, "{value}").map_err(Error::from), AutoEscape::Html => write_with_html_escaping(out, value).map_err(Error::from), #[cfg(feature = "json")] AutoEscape::Json => { let value = ok!(serde_json::to_string(&value).map_err(|err| { Error::new(ErrorKind::BadSerialization, "unable to format to JSON").with_source(err) })); write!(out, "{value}").map_err(Error::from) } AutoEscape::Custom(name) => invalid_autoescape(name), } } /// Controls the autoescaping behavior. /// /// For more information see /// [`set_auto_escape_callback`](crate::Environment::set_auto_escape_callback). #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum AutoEscape { /// Do not apply auto escaping. None, /// Use HTML auto escaping rules. /// /// Any value will be converted into a string and the following characters /// will be escaped in ways compatible to XML and HTML: `<`, `>`, `&`, `"`, /// `'`, and `/`. Html, /// Use escaping rules suitable for JSON/JavaScript or YAML. /// /// Any value effectively ends up being serialized to JSON upon printing. The /// serialized values will be compatible with JavaScript and YAML as well. #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] Json, /// A custom auto escape format. /// /// The default formatter does not know how to deal with a custom escaping /// format and would error. The use of these requires a custom formatter. /// See [`set_formatter`](crate::Environment::set_formatter). Custom(&'static str), } /// Defines the behavior of undefined values in the engine. /// /// At present there are three types of behaviors available which mirror the behaviors /// that Jinja2 provides out of the box. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum UndefinedBehavior { /// The default, somewhat lenient undefined behavior. /// /// * **printing:** allowed (returns empty string) /// * **iteration:** allowed (returns empty array) /// * **attribute access of undefined values:** fails Lenient, /// Like `Lenient`, but also allows chaining of undefined lookups. /// /// * **printing:** allowed (returns empty string) /// * **iteration:** allowed (returns empty array) /// * **attribute access of undefined values:** allowed (returns [`undefined`](Value::UNDEFINED)) Chainable, /// Complains very quickly about undefined values. /// /// * **printing:** fails /// * **iteration:** fails /// * **attribute access of undefined values:** fails Strict, } impl Default for UndefinedBehavior { fn default() -> UndefinedBehavior { UndefinedBehavior::Lenient } } impl UndefinedBehavior { /// Utility method used in the engine to determine what to do when an undefined is /// encountered. /// /// The flag indicates if this is the first or second level of undefined value. If /// `parent_was_undefined` is set to `true`, the undefined was created by looking up /// a missing attribute on an undefined value. If `false` the undefined was created by /// looing up a missing attribute on a defined value. pub(crate) fn handle_undefined(self, parent_was_undefined: bool) -> Result { match (self, parent_was_undefined) { (UndefinedBehavior::Lenient, false) | (UndefinedBehavior::Chainable, _) => { Ok(Value::UNDEFINED) } (UndefinedBehavior::Lenient, true) | (UndefinedBehavior::Strict, _) => { Err(Error::from(ErrorKind::UndefinedError)) } } } /// Tries to iterate over a value while handling the undefined value. /// /// If the value is undefined, then iteration fails if the behavior is set to strict, /// otherwise it succeeds with an empty iteration. This is also internally used in the /// engine to convert values to lists. pub(crate) fn try_iter(self, value: Value) -> Result { self.assert_iterable(&value) .and_then(|_| value.try_iter_owned()) } /// Are we strict on iteration? pub(crate) fn assert_iterable(self, value: &Value) -> Result<(), Error> { if matches!(self, UndefinedBehavior::Strict) && value.is_undefined() { Err(Error::from(ErrorKind::UndefinedError)) } else { Ok(()) } } } /// Helper to HTML escape a string. pub struct HtmlEscape<'a>(pub &'a str); impl<'a> fmt::Display for HtmlEscape<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(feature = "v_htmlescape")] { fmt::Display::fmt(&v_htmlescape::escape(self.0), f) } // this is taken from askama-escape #[cfg(not(feature = "v_htmlescape"))] { let bytes = self.0.as_bytes(); let mut start = 0; for (i, b) in bytes.iter().enumerate() { macro_rules! escaping_body { ($quote:expr) => {{ if start < i { // SAFETY: this is safe because we only push valid utf-8 bytes over ok!(f.write_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) })); } ok!(f.write_str($quote)); start = i + 1; }}; } if b.wrapping_sub(b'"') <= b'>' - b'"' { match *b { b'<' => escaping_body!("<"), b'>' => escaping_body!(">"), b'&' => escaping_body!("&"), b'"' => escaping_body!("""), b'\'' => escaping_body!("'"), b'/' => escaping_body!("/"), _ => (), } } } if start < bytes.len() { // SAFETY: this is safe because we only push valid utf-8 bytes over f.write_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..]) }) } else { Ok(()) } } } } struct Unescaper { out: String, pending_surrogate: u16, } impl Unescaper { fn unescape(mut self, s: &str) -> Result { let mut char_iter = s.chars(); while let Some(c) = char_iter.next() { if c == '\\' { match char_iter.next() { None => return Err(ErrorKind::BadEscape.into()), Some(d) => match d { '"' | '\\' | '/' | '\'' => ok!(self.push_char(d)), 'b' => ok!(self.push_char('\x08')), 'f' => ok!(self.push_char('\x0C')), 'n' => ok!(self.push_char('\n')), 'r' => ok!(self.push_char('\r')), 't' => ok!(self.push_char('\t')), 'u' => { let val = ok!(self.parse_u16(&mut char_iter)); ok!(self.push_u16(val)); } _ => return Err(ErrorKind::BadEscape.into()), }, } } else { ok!(self.push_char(c)); } } if self.pending_surrogate != 0 { Err(ErrorKind::BadEscape.into()) } else { Ok(self.out) } } fn parse_u16(&self, chars: &mut Chars) -> Result { let hexnum = chars.chain(repeat('\0')).take(4).collect::(); u16::from_str_radix(&hexnum, 16).map_err(|_| ErrorKind::BadEscape.into()) } fn push_u16(&mut self, c: u16) -> Result<(), Error> { match (self.pending_surrogate, (0xD800..=0xDFFF).contains(&c)) { (0, false) => match decode_utf16(once(c)).next() { Some(Ok(c)) => self.out.push(c), _ => return Err(ErrorKind::BadEscape.into()), }, (_, false) => return Err(ErrorKind::BadEscape.into()), (0, true) => self.pending_surrogate = c, (prev, true) => match decode_utf16(once(prev).chain(once(c))).next() { Some(Ok(c)) => { self.out.push(c); self.pending_surrogate = 0; } _ => return Err(ErrorKind::BadEscape.into()), }, } Ok(()) } fn push_char(&mut self, c: char) -> Result<(), Error> { if self.pending_surrogate != 0 { Err(ErrorKind::BadEscape.into()) } else { self.out.push(c); Ok(()) } } } /// Un-escape a string, following JSON rules. pub fn unescape(s: &str) -> Result { Unescaper { out: String::new(), pending_surrogate: 0, } .unescape(s) } pub struct BTreeMapKeysDebug<'a, K: fmt::Debug, V>(pub &'a BTreeMap); impl<'a, K: fmt::Debug, V> fmt::Debug for BTreeMapKeysDebug<'a, K, V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list().entries(self.0.iter().map(|x| x.0)).finish() } } pub struct OnDrop(Option); impl OnDrop { pub fn new(f: F) -> Self { Self(Some(f)) } } impl Drop for OnDrop { fn drop(&mut self) { self.0.take().unwrap()(); } } #[cfg(test)] mod tests { use super::*; use similar_asserts::assert_eq; #[test] fn test_html_escape() { let input = "<>&\"'/"; let output = HtmlEscape(input).to_string(); assert_eq!(output, "<>&"'/"); } #[test] fn test_unescape() { assert_eq!(unescape(r"foo\u2603bar").unwrap(), "foo\u{2603}bar"); assert_eq!(unescape(r"\t\b\f\r\n\\\/").unwrap(), "\t\x08\x0c\r\n\\/"); assert_eq!(unescape("foobarbaz").unwrap(), "foobarbaz"); assert_eq!(unescape(r"\ud83d\udca9").unwrap(), "💩"); } } minijinja-1.0.3/src/value/argtypes.rs000064400000000000000000000740621046102023000157150ustar 00000000000000use std::borrow::Cow; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; use std::ops::{Deref, DerefMut}; use crate::error::{Error, ErrorKind}; use crate::utils::UndefinedBehavior; use crate::value::{ Arc, KeyRef, MapType, Object, Packed, SeqObject, StringType, Value, ValueKind, ValueMap, ValueRepr, }; use crate::vm::State; /// A utility trait that represents the return value of functions and filters. /// /// It's implemented for the following types: /// /// * `Rv` where `Rv` implements `Into` /// * `Result` where `Rv` implements `Into` /// /// The equivalent for test functions is [`TestResult`](crate::tests::TestResult). pub trait FunctionResult { #[doc(hidden)] fn into_result(self) -> Result; } impl> FunctionResult for Result { fn into_result(self) -> Result { self.map(Into::into) } } impl> FunctionResult for I { fn into_result(self) -> Result { Ok(self.into()) } } /// Helper trait representing valid filter, test and function arguments. /// /// Since it's more convenient to write filters and tests with concrete /// types instead of values, this helper trait exists to automatically /// perform this conversion. It is implemented for functions up to an /// arity of 5 parameters. /// /// For each argument the conversion is performed via the [`ArgType`] /// trait which is implemented for many common types. For manual /// conversions the [`from_args`] utility should be used. pub trait FunctionArgs<'a> { /// The output type of the function arguments. type Output; /// Converts to function arguments from a slice of values. #[doc(hidden)] fn from_values(state: Option<&'a State>, values: &'a [Value]) -> Result; } /// Utility function to convert a slice of values into arguments. /// /// This performs the same conversion that [`Function`](crate::functions::Function) /// performs. It exists so that you one can leverage the same functionality when /// implementing [`Object::call_method`](crate::value::Object::call_method). /// /// ``` /// # use minijinja::value::from_args; /// # use minijinja::value::Value; /// # fn foo() -> Result<(), minijinja::Error> { /// # let args = vec![Value::from("foo"), Value::from(42i64)]; let args = &args[..]; /// // args is &[Value] /// let (string, num): (&str, i64) = from_args(args)?; /// # Ok(()) } fn main() { foo().unwrap(); } /// ``` /// /// Note that only value conversions are supported which means that `&State` is not /// a valid conversion type. /// /// You can also use this function to split positional and keyword arguments ([`Kwargs`]): /// /// ``` /// # use minijinja::value::{Value, Rest, Kwargs, from_args}; /// # use minijinja::Error; /// # fn foo() -> Result<(), minijinja::Error> { /// # let args = vec![Value::from("foo"), Value::from(42i64)]; let args = &args[..]; /// // args is &[Value], kwargs is Kwargs /// let (args, kwargs): (&[Value], Kwargs) = from_args(args)?; /// # Ok(()) /// # } fn main() { foo().unwrap(); } /// ``` #[inline(always)] pub fn from_args<'a, Args>(values: &'a [Value]) -> Result where Args: FunctionArgs<'a, Output = Args>, { Args::from_values(None, values) } /// A trait implemented by all filter/test argument types. /// /// This trait is used by [`FunctionArgs`]. It's implemented for many common /// types that are typically passed to filters, tests or functions. It's /// implemented for the following types: /// /// * eval state: [`&State`](crate::State) (see below for notes) /// * unsigned integers: [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] /// * signed integers: [`i8`], [`i16`], [`i32`], [`i64`], [`i128`] /// * floats: [`f32`], [`f64`] /// * bool: [`bool`] /// * string: [`String`], [`&str`], `Cow<'_, str>`, [`char`] /// * bytes: [`&[u8]`][`slice`] /// * values: [`Value`], `&Value` /// * vectors: [`Vec`] /// * sequences: [`&dyn SeqObject`](crate::value::SeqObject) /// /// The type is also implemented for optional values (`Option`) which is used /// to encode optional parameters to filters, functions or tests. Additionally /// it's implemented for [`Rest`] which is used to encode the remaining arguments /// of a function call. /// /// ## Notes on Borrowing /// /// Note on that there is an important difference between `String` and `&str`: /// the former will be valid for all values and an implicit conversion to string /// via [`ToString`] will take place, for the latter only values which are already /// strings will be passed. A compromise between the two is `Cow<'_, str>` which /// will behave like `String` but borrows when possible. /// /// Byte slices will borrow out of values carrying bytes or strings. In the latter /// case the utf-8 bytes are returned. /// /// There are also further restrictions imposed on borrowing in some situations. /// For instance you cannot implicitly borrow out of sequences which means that /// for instance `Vec<&str>` is not a legal argument. /// /// ## Notes on State /// /// When `&State` is used, it does not consume a passed parameter. This means that /// a filter that takes `(&State, String)` actually only has one argument. The /// state is passed implicitly. pub trait ArgType<'a> { /// The output type of this argument. type Output; #[doc(hidden)] fn from_value(value: Option<&'a Value>) -> Result; #[doc(hidden)] fn from_value_owned(_value: Value) -> Result { Err(Error::new( ErrorKind::InvalidOperation, "type conversion is not legal in this situation (implicit borrow)", )) } #[doc(hidden)] fn from_state_and_value( state: Option<&'a State>, value: Option<&'a Value>, ) -> Result<(Self::Output, usize), Error> { if value.map_or(false, |x| x.is_undefined()) && state.map_or(false, |x| { matches!(x.undefined_behavior(), UndefinedBehavior::Strict) }) { Err(Error::from(ErrorKind::UndefinedError)) } else { Ok((ok!(Self::from_value(value)), 1)) } } #[doc(hidden)] #[inline(always)] fn from_state_and_values( state: Option<&'a State>, values: &'a [Value], offset: usize, ) -> Result<(Self::Output, usize), Error> { Self::from_state_and_value(state, values.get(offset)) } #[doc(hidden)] #[inline(always)] fn is_trailing() -> bool { false } } macro_rules! tuple_impls { ( $( $name:ident )* * $rest_name:ident ) => { impl<'a, $($name,)* $rest_name> FunctionArgs<'a> for ($($name,)* $rest_name,) where $($name: ArgType<'a>,)* $rest_name: ArgType<'a> { type Output = ($($name::Output,)* $rest_name::Output ,); fn from_values(state: Option<&'a State>, mut values: &'a [Value]) -> Result { #![allow(non_snake_case, unused)] $( let $name; )* let mut $rest_name = None; let mut idx = 0; // special case: the last type is marked trailing (eg: for Kwargs) and we have at // least one value. In that case we need to read it first before going to the rest // of the arguments. This is needed to support from_args::<(&[Value], Kwargs)> // or similar. let rest_first = $rest_name::is_trailing() && !values.is_empty(); if rest_first { let (val, offset) = ok!($rest_name::from_state_and_values(state, values, values.len() - 1)); $rest_name = Some(val); values = &values[..values.len() - offset]; } $( let (val, offset) = ok!($name::from_state_and_values(state, values, idx)); $name = val; idx += offset; )* if !rest_first { let (val, offset) = ok!($rest_name::from_state_and_values(state, values, idx)); $rest_name = Some(val); idx += offset; } if values.get(idx).is_some() { Err(Error::from(ErrorKind::TooManyArguments)) } else { // SAFETY: this is safe because both no batter what `rest_first` is set to // either way the variable is set. Ok(($($name,)* unsafe { $rest_name.unwrap_unchecked() },)) } } } }; } impl<'a> FunctionArgs<'a> for () { type Output = (); fn from_values(_state: Option<&'a State>, values: &'a [Value]) -> Result { if values.is_empty() { Ok(()) } else { Err(Error::from(ErrorKind::TooManyArguments)) } } } tuple_impls! { *A } tuple_impls! { A *B } tuple_impls! { A B *C } tuple_impls! { A B C *D } tuple_impls! { A B C D *E } impl From for Value { #[inline(always)] fn from(val: ValueRepr) -> Value { Value(val) } } impl<'a> From<&'a [u8]> for Value { #[inline(always)] fn from(val: &'a [u8]) -> Self { ValueRepr::Bytes(Arc::new(val.into())).into() } } impl<'a> From<&'a str> for Value { #[inline(always)] fn from(val: &'a str) -> Self { ValueRepr::String(Arc::from(val.to_string()), StringType::Normal).into() } } impl From for Value { #[inline(always)] fn from(val: String) -> Self { ValueRepr::String(Arc::from(val), StringType::Normal).into() } } impl<'a> From> for Value { #[inline(always)] fn from(val: Cow<'a, str>) -> Self { match val { Cow::Borrowed(x) => x.into(), Cow::Owned(x) => x.into(), } } } impl From<()> for Value { #[inline(always)] fn from(_: ()) -> Self { ValueRepr::None.into() } } impl> FromIterator for Value { fn from_iter>(iter: T) -> Self { ValueRepr::Seq(Arc::new(iter.into_iter().map(Into::into).collect())).into() } } impl, V: Into> FromIterator<(K, V)> for Value { fn from_iter>(iter: T) -> Self { let map = iter .into_iter() .map(|(k, v)| (KeyRef::Value(k.into()), v.into())) .collect(); ValueRepr::Map(Arc::new(map), MapType::Normal).into() } } impl, V: Into> From> for Value { fn from(val: BTreeMap) -> Self { val.into_iter().map(|(k, v)| (k.into(), v.into())).collect() } } impl, V: Into> From> for Value { fn from(val: HashMap) -> Self { val.into_iter().map(|(k, v)| (k.into(), v.into())).collect() } } impl> From> for Value { fn from(val: Vec) -> Self { val.into_iter().map(|v| v.into()).collect() } } impl From> for Value { fn from(object: Arc) -> Self { Value::from(object as Arc) } } impl From> for Value { fn from(value: Arc) -> Self { Value(ValueRepr::String(value, StringType::Normal)) } } macro_rules! value_from { ($src:ty, $dst:ident) => { impl From<$src> for Value { #[inline(always)] fn from(val: $src) -> Self { ValueRepr::$dst(val as _).into() } } }; } impl From for Value { #[inline(always)] fn from(val: i128) -> Self { ValueRepr::I128(Packed(val)).into() } } impl From for Value { #[inline(always)] fn from(val: u128) -> Self { ValueRepr::U128(Packed(val)).into() } } impl From for Value { #[inline(always)] fn from(val: char) -> Self { ValueRepr::String(Arc::from(val.to_string()), StringType::Normal).into() } } value_from!(bool, Bool); value_from!(u8, U64); value_from!(u16, U64); value_from!(u32, U64); value_from!(u64, U64); value_from!(i8, I64); value_from!(i16, I64); value_from!(i32, I64); value_from!(i64, I64); value_from!(f32, F64); value_from!(f64, F64); value_from!(Arc>, Bytes); value_from!(Arc>, Seq); value_from!(Arc, Dynamic); fn unsupported_conversion(kind: ValueKind, target: &str) -> Error { Error::new( ErrorKind::InvalidOperation, format!("cannot convert {kind} to {target}"), ) } macro_rules! primitive_try_from { ($ty:ident, { $($pat:pat $(if $if_expr:expr)? => $expr:expr,)* }) => { impl TryFrom for $ty { type Error = Error; fn try_from(value: Value) -> Result { match value.0 { $($pat $(if $if_expr)? => TryFrom::try_from($expr).ok(),)* _ => None }.ok_or_else(|| unsupported_conversion(value.kind(), stringify!($ty))) } } impl<'a> ArgType<'a> for $ty { type Output = Self; fn from_value(value: Option<&Value>) -> Result { match value { Some(value) => TryFrom::try_from(value.clone()), None => Err(Error::from(ErrorKind::MissingArgument)) } } fn from_value_owned(value: Value) -> Result { TryFrom::try_from(value) } } } } macro_rules! primitive_int_try_from { ($ty:ident) => { primitive_try_from!($ty, { ValueRepr::Bool(val) => val as usize, ValueRepr::I64(val) => val, ValueRepr::U64(val) => val, // for the intention here see Key::from_borrowed_value ValueRepr::F64(val) if (val as i64 as f64 == val) => val as i64, ValueRepr::I128(val) => val.0, ValueRepr::U128(val) => val.0, }); } } primitive_int_try_from!(u8); primitive_int_try_from!(u16); primitive_int_try_from!(u32); primitive_int_try_from!(u64); primitive_int_try_from!(u128); primitive_int_try_from!(i8); primitive_int_try_from!(i16); primitive_int_try_from!(i32); primitive_int_try_from!(i64); primitive_int_try_from!(i128); primitive_int_try_from!(usize); primitive_try_from!(bool, { ValueRepr::Bool(val) => val, }); primitive_try_from!(char, { ValueRepr::String(ref val, _) => { let mut char_iter = val.chars(); ok!(char_iter.next().filter(|_| char_iter.next().is_none()).ok_or_else(|| { unsupported_conversion(ValueKind::String, "non single character string") })) }, }); primitive_try_from!(f32, { ValueRepr::U64(val) => val as f32, ValueRepr::I64(val) => val as f32, ValueRepr::U128(val) => val.0 as f32, ValueRepr::I128(val) => val.0 as f32, ValueRepr::F64(val) => val as f32, }); primitive_try_from!(f64, { ValueRepr::U64(val) => val as f64, ValueRepr::I64(val) => val as f64, ValueRepr::U128(val) => val.0 as f64, ValueRepr::I128(val) => val.0 as f64, ValueRepr::F64(val) => val, }); impl<'a> ArgType<'a> for &str { type Output = &'a str; fn from_value(value: Option<&'a Value>) -> Result { match value { Some(value) => value .as_str() .ok_or_else(|| Error::new(ErrorKind::InvalidOperation, "value is not a string")), None => Err(Error::from(ErrorKind::MissingArgument)), } } } impl TryFrom for Arc { type Error = Error; fn try_from(value: Value) -> Result { match value.0 { ValueRepr::String(x, _) => Ok(x), _ => Err(Error::new( ErrorKind::InvalidOperation, "value is not a string", )), } } } impl<'a> ArgType<'a> for Arc { type Output = Arc; fn from_value(value: Option<&'a Value>) -> Result { match value { Some(value) => TryFrom::try_from(value.clone()), None => Err(Error::from(ErrorKind::MissingArgument)), } } } impl<'a> ArgType<'a> for &[u8] { type Output = &'a [u8]; fn from_value(value: Option<&'a Value>) -> Result { match value { Some(value) => value .as_bytes() .ok_or_else(|| Error::new(ErrorKind::InvalidOperation, "value is not in bytes")), None => Err(Error::from(ErrorKind::MissingArgument)), } } } impl<'a> ArgType<'a> for &dyn SeqObject { type Output = &'a dyn SeqObject; fn from_value(value: Option<&'a Value>) -> Result { match value { Some(value) => value .as_seq() .ok_or_else(|| Error::new(ErrorKind::InvalidOperation, "value is not a sequence")), None => Err(Error::from(ErrorKind::MissingArgument)), } } } impl<'a, T: ArgType<'a>> ArgType<'a> for Option { type Output = Option; fn from_value(value: Option<&'a Value>) -> Result { match value { Some(value) => { if value.is_undefined() { Ok(None) } else { T::from_value(Some(value)).map(Some) } } None => Ok(None), } } fn from_value_owned(value: Value) -> Result { if value.is_undefined() || value.is_none() { Ok(None) } else { T::from_value_owned(value).map(Some) } } } impl<'a> ArgType<'a> for Cow<'_, str> { type Output = Cow<'a, str>; #[inline(always)] fn from_value(value: Option<&'a Value>) -> Result, Error> { match value { Some(value) => Ok(match value.0 { ValueRepr::String(ref s, _) => Cow::Borrowed(s as &str), _ => Cow::Owned(value.to_string()), }), None => Err(Error::from(ErrorKind::MissingArgument)), } } } impl<'a> ArgType<'a> for &Value { type Output = &'a Value; #[inline(always)] fn from_value(value: Option<&'a Value>) -> Result<&'a Value, Error> { match value { Some(value) => Ok(value), None => Err(Error::from(ErrorKind::MissingArgument)), } } } impl<'a> ArgType<'a> for &[Value] { type Output = &'a [Value]; #[inline(always)] fn from_value(value: Option<&'a Value>) -> Result<&'a [Value], Error> { match value { Some(value) => Ok(std::slice::from_ref(value)), None => Err(Error::from(ErrorKind::MissingArgument)), } } fn from_state_and_values( _state: Option<&'a State>, values: &'a [Value], offset: usize, ) -> Result<(&'a [Value], usize), Error> { let args = values.get(offset..).unwrap_or_default(); Ok((args, args.len())) } } /// Utility type to capture remaining arguments. /// /// In some cases you might want to have a variadic function. In that case /// you can define the last argument to a [`Filter`](crate::filters::Filter), /// [`Test`](crate::tests::Test) or [`Function`](crate::functions::Function) /// this way. The `Rest` type will collect all the remaining arguments /// here. It's implemented for all [`ArgType`]s. The type itself deref's /// into the inner vector. /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// use minijinja::State; /// use minijinja::value::Rest; /// /// fn sum(_state: &State, values: Rest) -> i64 { /// values.iter().sum() /// } /// ``` #[derive(Debug)] pub struct Rest(pub Vec); impl Deref for Rest { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Rest { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl<'a, T: ArgType<'a, Output = T>> ArgType<'a> for Rest { type Output = Self; fn from_value(value: Option<&'a Value>) -> Result { Ok(Rest(ok!(value .iter() .map(|v| T::from_value(Some(v))) .collect::>()))) } fn from_state_and_values( _state: Option<&'a State>, values: &'a [Value], offset: usize, ) -> Result<(Self, usize), Error> { let args = values.get(offset..).unwrap_or_default(); Ok(( Rest(ok!(args .iter() .map(|v| T::from_value(Some(v))) .collect::>())), args.len(), )) } } /// Utility to accept keyword arguments. /// /// Keyword arguments are represented as regular values as the last argument /// in an argument list. This can be quite complex to use manually so this /// type is added as a utility. You can use [`get`](Self::get) to fetch a /// single keyword argument and then use [`assert_all_used`](Self::assert_all_used) /// to make sure extra arguments create an error. /// /// Here an example of a function modifying values in different ways. /// /// ``` /// use minijinja::value::{Value, Kwargs}; /// use minijinja::Error; /// /// fn modify(mut values: Vec, options: Kwargs) -> Result, Error> { /// // get pulls a parameter of any type. Same as from_args. For optional /// // boolean values the type inference is particularly convenient. /// if let Some(true) = options.get("reverse")? { /// values.reverse(); /// } /// if let Some(limit) = options.get("limit")? { /// values.truncate(limit); /// } /// options.assert_all_used()?; /// Ok(values) /// } /// ``` /// /// If for whatever reason you need a value again you can use [`Into`] to /// convert it back into a [`Value`]. This is particularly useful when performing /// calls into values. To create a [`Kwargs`] object from scratch you can use /// [`FromIterator`]: /// /// ``` /// use minijinja::value::{Value, Kwargs}; /// let kwargs = Kwargs::from_iter([ /// ("foo", Value::from(true)), /// ("bar", Value::from(42)), /// ]); /// let value = Value::from(kwargs); /// assert!(value.is_kwargs()); /// ``` /// /// When working with [`Rest`] you can use [`from_args`] to split all arguments into /// positional arguments and keyword arguments: /// /// ``` /// # use minijinja::value::{Value, Rest, Kwargs, from_args}; /// # use minijinja::Error; /// fn my_func(args: Rest) -> Result { /// let (args, kwargs) = from_args::<(&[Value], Kwargs)>(&args)?; /// // do something with args and kwargs /// # todo!() /// } /// ``` #[derive(Debug, Clone)] pub struct Kwargs { values: Arc, used: RefCell>, } impl<'a> ArgType<'a> for Kwargs { type Output = Self; fn from_value(value: Option<&'a Value>) -> Result { match value { Some(value) => { if let ValueRepr::Map(ref map, MapType::Kwargs) = value.0 { Ok(Kwargs::new(map.clone())) } else { Err(Error::from(ErrorKind::MissingArgument)) } } None => Ok(Kwargs::new(Default::default())), } } fn from_state_and_values( _state: Option<&'a State>, values: &'a [Value], offset: usize, ) -> Result<(Self, usize), Error> { if let Some(value) = values.get(offset) { if let ValueRepr::Map(ref map, MapType::Kwargs) = value.0 { return Ok((Kwargs::new(map.clone()), 1)); } } Ok((Kwargs::new(Default::default()), 0)) } fn is_trailing() -> bool { true } } impl Kwargs { fn new(map: Arc) -> Kwargs { Kwargs { values: map, used: RefCell::new(HashSet::new()), } } /// Get a single argument from the kwargs but don't mark it as used. pub fn peek<'a, T>(&'a self, key: &'a str) -> Result where T: ArgType<'a, Output = T>, { T::from_value(self.values.get(&KeyRef::Str(key))) } /// Gets a single argument from the kwargs and marks it as used. /// /// This method works pretty much like [`from_args`] and marks any parameter /// used internally. For optional arguments you would typically use /// `Option` and for non optional ones directly `T`. /// /// Examples: /// /// ``` /// # use minijinja::Error; /// # use minijinja::value::Kwargs; fn f(kwargs: Kwargs) -> Result<(), Error> { /// // f(int=42) -> Some(42) /// // f() -> None /// let optional_int: Option = kwargs.get("int")?; /// // f(int=42) -> 42 /// // f() -> Error /// let required_int: u32 = kwargs.get("int")?; /// # Ok(()) } /// ``` /// /// If you don't want to mark it as used, us [`peek`](Self::peek) instead. pub fn get<'a, T>(&'a self, key: &'a str) -> Result where T: ArgType<'a, Output = T>, { let rv = ok!(self.peek::(key)); self.used.borrow_mut().insert(key.to_string()); Ok(rv) } /// Checks if a keyword argument exists. pub fn has(&self, key: &str) -> bool { self.values.contains_key(&KeyRef::Str(key)) } /// Iterates over all passed keyword arguments. pub fn args(&self) -> impl Iterator { self.values.iter().filter_map(|x| x.0.as_str()) } /// Asserts that all kwargs were used. pub fn assert_all_used(&self) -> Result<(), Error> { let used = self.used.borrow(); for key in self.values.keys() { if let Some(key) = key.as_str() { if !used.contains(key) { return Err(Error::new( ErrorKind::TooManyArguments, format!("unknown keyword argument '{}'", key), )); } } else { return Err(Error::new( ErrorKind::InvalidOperation, "non string keys passed to kwargs", )); } } Ok(()) } } impl FromIterator<(String, Value)> for Kwargs { fn from_iter(iter: T) -> Self where T: IntoIterator, { Kwargs::new(Arc::new( iter.into_iter() .map(|(k, v)| (KeyRef::Value(Value::from(k)), v)) .collect(), )) } } impl<'a> FromIterator<(&'a str, Value)> for Kwargs { fn from_iter(iter: T) -> Self where T: IntoIterator, { Kwargs::new(Arc::new( iter.into_iter() .map(|(k, v)| (KeyRef::Value(Value::from(k)), v)) .collect(), )) } } impl From for Value { fn from(value: Kwargs) -> Self { Value(ValueRepr::Map(value.values, MapType::Kwargs)) } } impl TryFrom for Kwargs { type Error = Error; fn try_from(value: Value) -> Result { match value.0 { ValueRepr::Undefined => Ok(Kwargs::new(Default::default())), ValueRepr::Map(ref val, MapType::Kwargs) => Ok(Kwargs::new(val.clone())), _ => Err(Error::from(ErrorKind::InvalidOperation)), } } } impl<'a> ArgType<'a> for Value { type Output = Self; fn from_state_and_value( _state: Option<&'a State>, value: Option<&'a Value>, ) -> Result<(Self::Output, usize), Error> { Ok((ok!(Self::from_value(value)), 1)) } fn from_value(value: Option<&'a Value>) -> Result { match value { Some(value) => Ok(value.clone()), None => Err(Error::from(ErrorKind::MissingArgument)), } } fn from_value_owned(value: Value) -> Result { Ok(value) } } impl<'a> ArgType<'a> for String { type Output = Self; fn from_value(value: Option<&'a Value>) -> Result { match value { Some(value) => Ok(value.to_string()), None => Err(Error::from(ErrorKind::MissingArgument)), } } fn from_value_owned(value: Value) -> Result { Ok(value.to_string()) } } impl<'a, T: ArgType<'a, Output = T>> ArgType<'a> for Vec { type Output = Vec; fn from_value(value: Option<&'a Value>) -> Result { match value { None => Ok(Vec::new()), Some(value) => { let seq = ok!(value .as_seq() .ok_or_else(|| { Error::new(ErrorKind::InvalidOperation, "not a sequence") })); let mut rv = Vec::new(); for value in seq.iter() { rv.push(ok!(T::from_value_owned(value))); } Ok(rv) } } } fn from_value_owned(value: Value) -> Result { let seq = ok!(value .as_seq() .ok_or_else(|| { Error::new(ErrorKind::InvalidOperation, "not a sequence") })); let mut rv = Vec::new(); for value in seq.iter() { rv.push(ok!(T::from_value_owned(value))); } Ok(rv) } } impl From for String { fn from(val: Value) -> Self { val.to_string() } } impl From for Value { fn from(val: usize) -> Self { Value::from(val as u64) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_as_f64() { let v = Value::from(42u32); let f: f64 = v.try_into().unwrap(); assert_eq!(f, 42.0); let v = Value::from(42.5); let f: f64 = v.try_into().unwrap(); assert_eq!(f, 42.5); } #[test] fn test_split_kwargs() { let args = [ Value::from(42), Value::from(true), Value::from(Kwargs::from_iter([ ("foo", Value::from(1)), ("bar", Value::from(2)), ])), ]; let (args, kwargs) = from_args::<(&[Value], Kwargs)>(&args).unwrap(); assert_eq!(args, &[Value::from(42), Value::from(true)]); assert_eq!(kwargs.get::("foo").unwrap(), Value::from(1)); assert_eq!(kwargs.get::("bar").unwrap(), Value::from(2)); } } minijinja-1.0.3/src/value/deserialize.rs000064400000000000000000000055071046102023000163550ustar 00000000000000use serde::de::{self, MapAccess, SeqAccess, Visitor}; use serde::Deserialize; use crate::value::{KeyRef, MapType, Value, ValueMap, ValueRepr}; impl<'de> Deserialize<'de> for Value { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let visitor = ValueVisitor; deserializer.deserialize_any(visitor) } } struct ValueVisitor; macro_rules! visit_value_primitive { ($name:ident, $ty:ty) => { fn $name(self, v: $ty) -> Result where E: serde::de::Error, { Ok(Value::from(v)) } }; } impl<'de> Visitor<'de> for ValueVisitor { type Value = Value; fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { fmt.write_str("any MiniJinja compatible value") } visit_value_primitive!(visit_bool, bool); visit_value_primitive!(visit_i8, i8); visit_value_primitive!(visit_i16, i16); visit_value_primitive!(visit_i32, i32); visit_value_primitive!(visit_i64, i64); visit_value_primitive!(visit_i128, i128); visit_value_primitive!(visit_u16, u16); visit_value_primitive!(visit_u32, u32); visit_value_primitive!(visit_u64, u64); visit_value_primitive!(visit_u128, u128); visit_value_primitive!(visit_f32, f32); visit_value_primitive!(visit_f64, f64); visit_value_primitive!(visit_char, char); visit_value_primitive!(visit_str, &str); visit_value_primitive!(visit_string, String); visit_value_primitive!(visit_bytes, &[u8]); visit_value_primitive!(visit_byte_buf, Vec); fn visit_none(self) -> Result where E: de::Error, { Ok(Value::from(())) } fn visit_some(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { Deserialize::deserialize(deserializer) } fn visit_unit(self) -> Result where E: de::Error, { Ok(Value::from(())) } fn visit_newtype_struct(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { Deserialize::deserialize(deserializer) } fn visit_seq(self, mut visitor: A) -> Result where A: SeqAccess<'de>, { let mut rv = Vec::::new(); while let Some(e) = ok!(visitor.next_element()) { rv.push(e); } Ok(Value::from(rv)) } fn visit_map(self, mut map: A) -> Result where A: MapAccess<'de>, { let mut rv = ValueMap::default(); while let Some((k, v)) = ok!(map.next_entry()) { rv.insert(KeyRef::Value(k), v); } Ok(Value(ValueRepr::Map(rv.into(), MapType::Normal))) } } minijinja-1.0.3/src/value/keyref.rs000064400000000000000000000100411046102023000153270ustar 00000000000000use std::cmp::Ordering; use std::fmt; use std::hash::{Hash, Hasher}; use serde::Serialize; use crate::value::{intern, Value}; /// Internal abstraction over keys #[derive(Clone)] pub enum KeyRef<'a> { /// The key is a value Value(Value), /// The key is a string slice Str(&'a str), } impl<'a> KeyRef<'a> { /// If this is a str, return it. pub fn as_str(&self) -> Option<&str> { match self { KeyRef::Value(v) => v.as_str(), KeyRef::Str(s) => Some(s), } } /// If this is an i64 return it pub fn as_i64(&self) -> Option { match self { KeyRef::Value(v) => i64::try_from(v.clone()).ok(), KeyRef::Str(_) => None, } } /// Return this as value. pub fn as_value(&self) -> Value { match self { KeyRef::Value(v) => v.clone(), KeyRef::Str(s) => Value::from(intern(s)), } } } impl<'a> Serialize for KeyRef<'a> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { KeyRef::Value(v) => v.serialize(serializer), KeyRef::Str(s) => s.serialize(serializer), } } } impl<'a> fmt::Debug for KeyRef<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Value(v) => fmt::Debug::fmt(v, f), Self::Str(v) => fmt::Debug::fmt(v, f), } } } impl<'a> PartialEq for KeyRef<'a> { fn eq(&self, other: &Self) -> bool { if let (Some(a), Some(b)) = (self.as_str(), other.as_str()) { a.eq(b) } else { self.as_value().eq(&other.as_value()) } } } impl<'a> Eq for KeyRef<'a> {} impl<'a> PartialOrd for KeyRef<'a> { fn partial_cmp(&self, other: &Self) -> Option { if let (Some(a), Some(b)) = (self.as_str(), other.as_str()) { a.partial_cmp(b) } else { self.as_value().partial_cmp(&other.as_value()) } } } impl<'a> Ord for KeyRef<'a> { fn cmp(&self, other: &Self) -> Ordering { if let (Some(a), Some(b)) = (self.as_str(), other.as_str()) { a.cmp(b) } else { self.as_value().cmp(&other.as_value()) } } } impl<'a> Hash for KeyRef<'a> { fn hash(&self, state: &mut H) { if let Some(s) = self.as_str() { s.hash(state) } else { self.as_value().hash(state) } } } #[cfg(feature = "key_interning")] pub mod key_interning { use crate::utils::OnDrop; use std::cell::{Cell, RefCell}; use std::collections::HashSet; use std::sync::Arc; thread_local! { static STRING_KEY_CACHE: RefCell>> = Default::default(); static USE_STRING_KEY_CACHE: Cell = Cell::new(false); } pub fn use_string_cache() -> impl Drop { let was_enabled = USE_STRING_KEY_CACHE.with(|flag| { let was_enabled = flag.get(); flag.set(true); was_enabled }); OnDrop::new(move || { if !was_enabled { USE_STRING_KEY_CACHE.with(|flag| flag.set(false)); STRING_KEY_CACHE.with(|cache| cache.borrow_mut().clear()); } }) } #[inline(always)] pub(crate) fn try_intern(s: &str) -> Arc { // strings longer than 16 bytes are never interned or if we're at // depth 0. (serialization code outside of internal serialization) // not checking for depth can cause a memory leak. if s.len() > 16 || !USE_STRING_KEY_CACHE.with(|flag| flag.get()) { return Arc::from(s); } STRING_KEY_CACHE.with(|cache| { let mut set = cache.borrow_mut(); match set.get(s) { Some(stored) => stored.clone(), None => { let rv: Arc = Arc::from(s.to_string()); set.insert(rv.clone()); rv } } }) } } minijinja-1.0.3/src/value/mod.rs000064400000000000000000001416151046102023000146350ustar 00000000000000//! Provides a dynamic value type abstraction. //! //! This module gives access to a dynamically typed value which is used by //! the template engine during execution. //! //! For the most part the existence of the value type can be ignored as //! MiniJinja will perform the necessary conversions for you. For instance //! if you write a filter that converts a string you can directly declare the //! filter to take a [`String`](std::string::String). However for some more //! advanced use cases it's useful to know that this type exists. //! //! # Converting Values //! //! Values are typically created via the [`From`] trait: //! //! ``` //! # use minijinja::value::Value; //! let int_value = Value::from(42); //! let none_value = Value::from(()); //! let true_value = Value::from(true); //! ``` //! //! Or via the [`FromIterator`] trait: //! //! ``` //! # use minijinja::value::Value; //! // collection into a sequence //! let value: Value = (1..10).into_iter().collect(); //! //! // collection into a map //! let value: Value = [("key", "value")].into_iter().collect(); //! ``` //! //! The special [`Undefined`](Value::UNDEFINED) value also exists but does not //! have a rust equivalent. It can be created via the [`UNDEFINED`](Value::UNDEFINED) //! constant. //! //! MiniJinja will however create values via an indirection via [`serde`] when //! a template is rendered or an expression is evaluated. This can also be //! triggered manually by using the [`Value::from_serializable`] method: //! //! ``` //! # use minijinja::value::Value; //! let value = Value::from_serializable(&[1, 2, 3]); //! ``` //! //! To to into the inverse directly the various [`TryFrom`](std::convert::TryFrom) //! implementations can be used: //! //! ``` //! # use minijinja::value::Value; //! use std::convert::TryFrom; //! let v = u64::try_from(Value::from(42)).unwrap(); //! ``` //! //! # Value Function Arguments //! //! [Filters](crate::filters) and [tests](crate::tests) can take values as arguments //! but optionally also rust types directly. This conversion for function arguments //! is performed by the [`FunctionArgs`] and related traits ([`ArgType`], [`FunctionResult`]). //! //! # Memory Management //! //! Values are immutable objects which are internally reference counted which //! means they can be copied relatively cheaply. Special care must be taken //! so that cycles are not created to avoid causing memory leaks. //! //! # HTML Escaping //! //! MiniJinja inherits the general desire to be clever about escaping. For this //! prupose a value will (when auto escaping is enabled) always be escaped. To //! prevent this behavior the [`safe`](crate::filters::safe) filter can be used //! in the template. Outside of templates the [`Value::from_safe_string`] method //! can be used to achieve the same result. //! //! # Dynamic Objects //! //! Values can also hold "dynamic" objects. These are objects which implement the //! [`Object`] trait and optionally [`SeqObject`] or [`StructObject`] These can //! be used to implement dynamic functionality such as stateful values and more. //! Dynamic objects are internally also used to implement the special `loop` //! variable or macros. //! //! To create a dynamic `Value` object, use [`Value::from_object`], //! [`Value::from_seq_object`], [`Value::from_struct_object`] or the `From>` implementations for `Value`: //! //! ```rust //! # use std::sync::Arc; //! # use minijinja::value::{Value, Object}; //! #[derive(Debug)] //! struct Foo; //! //! # impl std::fmt::Display for Foo { //! # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } //! # } //! # //! impl Object for Foo { //! /* implementation */ //! } //! //! let value = Value::from_object(Foo); //! let value = Value::from(Arc::new(Foo)); //! let value = Value::from(Arc::new(Foo) as Arc); //! ``` // this module is based on the content module in insta which in turn is based // on the content module in serde::private::ser. use std::cell::{Cell, RefCell}; use std::cmp::Ordering; use std::collections::BTreeMap; use std::convert::TryFrom; use std::fmt; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::sync::Arc; use serde::ser::{Serialize, Serializer}; use crate::error::{Error, ErrorKind}; use crate::functions; use crate::utils::OnDrop; use crate::value::object::{SimpleSeqObject, SimpleStructObject}; use crate::value::ops::as_f64; use crate::value::serialize::transform; use crate::vm::State; pub use crate::value::argtypes::{from_args, ArgType, FunctionArgs, FunctionResult, Kwargs, Rest}; pub use crate::value::object::{Object, ObjectKind, SeqObject, SeqObjectIter, StructObject}; mod argtypes; #[cfg(feature = "deserialization")] mod deserialize; mod keyref; mod object; pub(crate) mod ops; mod serialize; pub(crate) use crate::value::keyref::KeyRef; // We use in-band signalling to roundtrip some internal values. This is // not ideal but unfortunately there is no better system in serde today. const VALUE_HANDLE_MARKER: &str = "\x01__minijinja_ValueHandle"; #[cfg(feature = "preserve_order")] pub(crate) type ValueMap = indexmap::IndexMap, Value>; #[cfg(not(feature = "preserve_order"))] pub(crate) type ValueMap = std::collections::BTreeMap, Value>; #[inline(always)] pub(crate) fn value_map_with_capacity(capacity: usize) -> ValueMap { #[cfg(not(feature = "preserve_order"))] { let _ = capacity; ValueMap::new() } #[cfg(feature = "preserve_order")] { ValueMap::with_capacity(crate::utils::untrusted_size_hint(capacity)) } } thread_local! { static INTERNAL_SERIALIZATION: Cell = Cell::new(false); // This should be an AtomicU64 but sadly 32bit targets do not necessarily have // AtomicU64 available. static LAST_VALUE_HANDLE: Cell = Cell::new(0); static VALUE_HANDLES: RefCell> = RefCell::new(BTreeMap::new()); } /// Function that returns true when serialization for [`Value`] is taking place. /// /// MiniJinja internally creates [`Value`] objects from all values passed to the /// engine. It does this by going through the regular serde serialization trait. /// In some cases users might want to customize the serialization specifically for /// MiniJinja because they want to tune the object for the template engine /// independently of what is normally serialized to disk. /// /// This function returns `true` when MiniJinja is serializing to [`Value`] and /// `false` otherwise. You can call this within your own [`Serialize`] /// implementation to change the output format. /// /// This is particularly useful as serialization for MiniJinja does not need to /// support deserialization. So it becomes possible to completely change what /// gets sent there, even at the cost of serializing something that cannot be /// deserialized. pub fn serializing_for_value() -> bool { INTERNAL_SERIALIZATION.with(|flag| flag.get()) } /// Enables value optimizations. /// /// If `key_interning` is enabled, this turns on that feature, otherwise /// it becomes a noop. #[inline(always)] pub(crate) fn value_optimization() -> impl Drop { #[cfg(feature = "key_interning")] { crate::value::keyref::key_interning::use_string_cache() } #[cfg(not(feature = "key_interning"))] { OnDrop::new(|| {}) } } fn mark_internal_serialization() -> impl Drop { let old = INTERNAL_SERIALIZATION.with(|flag| { let old = flag.get(); flag.set(true); old }); OnDrop::new(move || { if !old { INTERNAL_SERIALIZATION.with(|flag| flag.set(false)); } }) } /// Describes the kind of value. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum ValueKind { /// The value is undefined Undefined, /// The value is the none singleton ([`()`]) None, /// The value is a [`bool`] Bool, /// The value is a number of a supported type. Number, /// The value is a string. String, /// The value is a byte array. Bytes, /// The value is an array of other values. Seq, /// The value is a key/value mapping. Map, } impl fmt::Display for ValueKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match *self { ValueKind::Undefined => "undefined", ValueKind::None => "none", ValueKind::Bool => "bool", ValueKind::Number => "number", ValueKind::String => "string", ValueKind::Bytes => "bytes", ValueKind::Seq => "sequence", ValueKind::Map => "map", }) } } /// The type of map #[derive(Copy, Clone, Debug)] pub(crate) enum MapType { /// A regular map Normal, /// A map representing keyword arguments Kwargs, } /// Type type of string #[derive(Copy, Clone, Debug)] pub(crate) enum StringType { Normal, Safe, } /// Wraps an internal copyable value but marks it as packed. /// /// This is used for `i128`/`u128` in the value repr to avoid /// the excessive 16 byte alignment. #[derive(Copy)] #[repr(packed)] pub(crate) struct Packed(pub T); impl Clone for Packed { fn clone(&self) -> Self { Self(self.0) } } #[derive(Clone)] pub(crate) enum ValueRepr { Undefined, Bool(bool), U64(u64), I64(i64), F64(f64), None, Invalid(Arc), U128(Packed), I128(Packed), String(Arc, StringType), Bytes(Arc>), Seq(Arc>), Map(Arc, MapType), Dynamic(Arc), } impl fmt::Debug for ValueRepr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ValueRepr::Undefined => f.write_str("Undefined"), ValueRepr::Bool(val) => fmt::Debug::fmt(val, f), ValueRepr::U64(val) => fmt::Debug::fmt(val, f), ValueRepr::I64(val) => fmt::Debug::fmt(val, f), ValueRepr::F64(val) => fmt::Debug::fmt(val, f), ValueRepr::None => f.write_str("None"), ValueRepr::Invalid(ref val) => write!(f, "", val), ValueRepr::U128(val) => fmt::Debug::fmt(&{ val.0 }, f), ValueRepr::I128(val) => fmt::Debug::fmt(&{ val.0 }, f), ValueRepr::String(val, _) => fmt::Debug::fmt(val, f), ValueRepr::Bytes(val) => fmt::Debug::fmt(val, f), ValueRepr::Seq(val) => fmt::Debug::fmt(val, f), ValueRepr::Map(val, _) => fmt::Debug::fmt(val, f), ValueRepr::Dynamic(val) => fmt::Debug::fmt(val, f), } } } impl Hash for Value { fn hash(&self, state: &mut H) { match &self.0 { ValueRepr::None | ValueRepr::Undefined => 0u8.hash(state), ValueRepr::String(ref s, _) => s.hash(state), ValueRepr::Bool(b) => b.hash(state), ValueRepr::Invalid(s) => s.hash(state), ValueRepr::Bytes(b) => b.hash(state), ValueRepr::Seq(b) => b.hash(state), ValueRepr::Map(m, _) => m.iter().for_each(|(k, v)| { k.hash(state); v.hash(state); }), ValueRepr::Dynamic(d) => match d.kind() { ObjectKind::Plain => 0u8.hash(state), ObjectKind::Seq(s) => s.iter().for_each(|x| x.hash(state)), ObjectKind::Struct(s) => { if let Some(fields) = s.static_fields() { fields.iter().for_each(|k| { k.hash(state); s.get_field(k).hash(state); }); } else { s.fields().iter().for_each(|k| { k.hash(state); s.get_field(k).hash(state); }); } } }, ValueRepr::U64(_) | ValueRepr::I64(_) | ValueRepr::F64(_) | ValueRepr::U128(_) | ValueRepr::I128(_) => { if let Ok(val) = i64::try_from(self.clone()) { val.hash(state) } else { as_f64(self).map(|x| x.to_bits()).hash(state) } } } } } /// Represents a dynamically typed value in the template engine. #[derive(Clone)] pub struct Value(pub(crate) ValueRepr); impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match (&self.0, &other.0) { (ValueRepr::None, ValueRepr::None) => true, (ValueRepr::Undefined, ValueRepr::Undefined) => true, (ValueRepr::String(ref a, _), ValueRepr::String(ref b, _)) => a == b, (ValueRepr::Bytes(a), ValueRepr::Bytes(b)) => a == b, _ => match ops::coerce(self, other) { Some(ops::CoerceResult::F64(a, b)) => a == b, Some(ops::CoerceResult::I128(a, b)) => a == b, Some(ops::CoerceResult::Str(a, b)) => a == b, None => { if let (Some(a), Some(b)) = (self.as_seq(), other.as_seq()) { a.iter().eq(b.iter()) } else if self.kind() == ValueKind::Map && other.kind() == ValueKind::Map { if self.len() != other.len() { return false; } if let Ok(mut iter) = self.try_iter() { iter.all(|x| self.get_item_opt(&x) == other.get_item_opt(&x)) } else { false } } else { false } } }, } } } impl Eq for Value {} impl PartialOrd for Value { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } fn f64_total_cmp(left: f64, right: f64) -> Ordering { // this is taken from f64::total_cmp on newer rust versions let mut left = left.to_bits() as i64; let mut right = right.to_bits() as i64; left ^= (((left >> 63) as u64) >> 1) as i64; right ^= (((right >> 63) as u64) >> 1) as i64; left.cmp(&right) } impl Ord for Value { fn cmp(&self, other: &Self) -> Ordering { let value_ordering = match (&self.0, &other.0) { (ValueRepr::None, ValueRepr::None) => Ordering::Equal, (ValueRepr::Undefined, ValueRepr::Undefined) => Ordering::Equal, (ValueRepr::String(ref a, _), ValueRepr::String(ref b, _)) => a.cmp(b), (ValueRepr::Bytes(a), ValueRepr::Bytes(b)) => a.cmp(b), _ => match ops::coerce(self, other) { Some(ops::CoerceResult::F64(a, b)) => f64_total_cmp(a, b), Some(ops::CoerceResult::I128(a, b)) => a.cmp(&b), Some(ops::CoerceResult::Str(a, b)) => a.cmp(b), None => { if let (Some(a), Some(b)) = (self.as_seq(), other.as_seq()) { a.iter().cmp(b.iter()) } else if self.kind() == ValueKind::Map && other.kind() == ValueKind::Map { if let (Ok(a), Ok(b)) = (self.try_iter(), other.try_iter()) { a.map(|k| (k.clone(), self.get_item_opt(&k))) .cmp(b.map(|k| (k.clone(), other.get_item_opt(&k)))) } else { Ordering::Equal } } else { Ordering::Equal } } }, }; value_ordering.then((self.kind() as usize).cmp(&(other.kind() as usize))) } } impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fmt::Debug::fmt(&self.0, f) } } impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { ValueRepr::Undefined => Ok(()), ValueRepr::Bool(val) => val.fmt(f), ValueRepr::U64(val) => val.fmt(f), ValueRepr::I64(val) => val.fmt(f), ValueRepr::F64(val) => { if val.is_nan() { f.write_str("NaN") } else if val.is_infinite() { write!(f, "{}inf", if val.is_sign_negative() { "-" } else { "" }) } else { let mut num = val.to_string(); if !num.contains('.') { num.push_str(".0"); } write!(f, "{num}") } } ValueRepr::None => f.write_str("none"), ValueRepr::Invalid(ref val) => write!(f, "", val), ValueRepr::I128(val) => write!(f, "{}", { val.0 }), ValueRepr::String(val, _) => write!(f, "{val}"), ValueRepr::Bytes(val) => write!(f, "{}", String::from_utf8_lossy(val)), ValueRepr::Seq(values) => { ok!(f.write_str("[")); for (idx, val) in values.iter().enumerate() { if idx > 0 { ok!(f.write_str(", ")); } ok!(write!(f, "{val:?}")); } f.write_str("]") } ValueRepr::Map(m, _) => { ok!(f.write_str("{")); for (idx, (key, val)) in m.iter().enumerate() { if idx > 0 { ok!(f.write_str(", ")); } ok!(write!(f, "{key:?}: {val:?}")); } f.write_str("}") } ValueRepr::U128(val) => write!(f, "{}", { val.0 }), ValueRepr::Dynamic(x) => write!(f, "{x}"), } } } impl Default for Value { fn default() -> Value { ValueRepr::Undefined.into() } } /// Intern a string. /// /// When the `key_interning` feature is in used, then MiniJinja will attempt to /// reuse strings in certain cases. This function can be used to utilize the /// same functionality. There is no guarantee that a string will be interned /// as there are heuristics involved for it. Additionally the string interning /// will only work during the template engine execution (eg: within filters etc.). pub fn intern(s: &str) -> Arc { #[cfg(feature = "key_interning")] { crate::value::keyref::key_interning::try_intern(s) } #[cfg(not(feature = "key_interning"))] { Arc::from(s.to_string()) } } #[allow(clippy::len_without_is_empty)] impl Value { /// The undefined value. /// /// This constant exists because the undefined type does not exist in Rust /// and this is the only way to construct it. pub const UNDEFINED: Value = Value(ValueRepr::Undefined); /// Creates a value from something that can be serialized. /// /// This is the method that MiniJinja will generally use whenever a serializable /// object is passed to one of the APIs that internally want to create a value. /// For instance this is what [`context!`](crate::context) and /// [`render`](crate::Template::render) will use. /// /// During serialization of the value, [`serializing_for_value`] will return /// `true` which makes it possible to customize serialization for MiniJinja. /// For more information see [`serializing_for_value`]. /// /// ``` /// # use minijinja::value::Value; /// let val = Value::from_serializable(&vec![1, 2, 3]); /// ``` /// /// This method does not fail but it might return a value that is not valid. Such /// values will when operated on fail in the template engine in most situations. /// This for instance can happen if the underlying implementation of [`Serialize`] /// fails. There are also cases where invalid objects are silently hidden in the /// engine today. This is for instance the case for when keys are used in hash maps /// that the engine cannot deal with. Invalid values are considered an implementation /// detail. There is currently no API to validate a value. pub fn from_serializable(value: &T) -> Value { let _serialization_guard = mark_internal_serialization(); let _optimization_guard = value_optimization(); transform(value) } /// Creates a value from a safe string. /// /// A safe string is one that will bypass auto escaping. For instance if you /// want to have the template engine render some HTML without the user having to /// supply the `|safe` filter, you can use a value of this type instead. /// /// ``` /// # use minijinja::value::Value; /// let val = Value::from_safe_string("note".into()); /// ``` pub fn from_safe_string(value: String) -> Value { ValueRepr::String(Arc::from(value), StringType::Safe).into() } /// Creates a value from a dynamic object. /// /// For more information see [`Object`]. /// /// ```rust /// # use minijinja::value::{Value, Object}; /// use std::fmt; /// /// #[derive(Debug)] /// struct Thing { /// id: usize, /// } /// /// impl fmt::Display for Thing { /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// fmt::Debug::fmt(self, f) /// } /// } /// /// impl Object for Thing {} /// /// let val = Value::from_object(Thing { id: 42 }); /// ``` /// /// Objects are internally reference counted. If you want to hold on to the /// `Arc` you can directly create the value from an arc'ed object: /// /// ```rust /// # use minijinja::value::{Value, Object}; /// # #[derive(Debug)] /// # struct Thing { id: usize }; /// # impl std::fmt::Display for Thing { /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { /// # todo!(); /// # } /// # } /// # impl Object for Thing {} /// use std::sync::Arc; /// let val = Value::from(Arc::new(Thing { id: 42 })); /// ``` pub fn from_object(value: T) -> Value { Value::from(Arc::new(value) as Arc) } /// Creates a value from an owned [`SeqObject`]. /// /// This is a simplified API for creating dynamic sequences /// without having to implement the entire [`Object`] protocol. /// /// **Note:** objects created this way cannot be downcasted via /// [`downcast_object_ref`](Self::downcast_object_ref). pub fn from_seq_object(value: T) -> Value { Value::from_object(SimpleSeqObject(value)) } /// Creates a value from an owned [`StructObject`]. /// /// This is a simplified API for creating dynamic structs /// without having to implement the entire [`Object`] protocol. /// /// **Note:** objects created this way cannot be downcasted via /// [`downcast_object_ref`](Self::downcast_object_ref). pub fn from_struct_object(value: T) -> Value { Value::from_object(SimpleStructObject(value)) } /// Creates a callable value from a function. /// /// ``` /// # use minijinja::value::Value; /// let pow = Value::from_function(|a: u32| a * a); /// ``` pub fn from_function(f: F) -> Value where // the crazy bounds here exist to enable borrowing in closures F: functions::Function + for<'a> functions::Function>::Output>, Rv: FunctionResult, Args: for<'a> FunctionArgs<'a>, { functions::BoxedFunction::new(f).to_value() } /// Returns the kind of the value. /// /// This can be used to determine what's in the value before trying to /// perform operations on it. pub fn kind(&self) -> ValueKind { match self.0 { ValueRepr::Undefined => ValueKind::Undefined, ValueRepr::Bool(_) => ValueKind::Bool, ValueRepr::U64(_) | ValueRepr::I64(_) | ValueRepr::F64(_) => ValueKind::Number, ValueRepr::None => ValueKind::None, ValueRepr::I128(_) => ValueKind::Number, ValueRepr::String(..) => ValueKind::String, ValueRepr::Bytes(_) => ValueKind::Bytes, ValueRepr::U128(_) => ValueKind::Number, ValueRepr::Seq(_) => ValueKind::Seq, ValueRepr::Map(..) => ValueKind::Map, // XXX: invalid values report themselves as maps which is a lie ValueRepr::Invalid(_) => ValueKind::Map, ValueRepr::Dynamic(ref dy) => match dy.kind() { // XXX: basic objects should probably not report as map ObjectKind::Plain => ValueKind::Map, ObjectKind::Seq(_) => ValueKind::Seq, ObjectKind::Struct(_) => ValueKind::Map, }, } } /// Returns `true` if the value is a number. /// /// To convert a value into a primitive number, use [`TryFrom`] or [`TryInto`]. pub fn is_number(&self) -> bool { matches!( self.0, ValueRepr::U64(_) | ValueRepr::I64(_) | ValueRepr::F64(_) | ValueRepr::I128(_) | ValueRepr::U128(_) ) } /// Returns `true` if the map represents keyword arguments. pub fn is_kwargs(&self) -> bool { matches!(self.0, ValueRepr::Map(_, MapType::Kwargs)) } /// Is this value true? pub fn is_true(&self) -> bool { match self.0 { ValueRepr::Bool(val) => val, ValueRepr::U64(x) => x != 0, ValueRepr::U128(x) => x.0 != 0, ValueRepr::I64(x) => x != 0, ValueRepr::I128(x) => x.0 != 0, ValueRepr::F64(x) => x != 0.0, ValueRepr::String(ref x, _) => !x.is_empty(), ValueRepr::Bytes(ref x) => !x.is_empty(), ValueRepr::None | ValueRepr::Undefined | ValueRepr::Invalid(_) => false, ValueRepr::Seq(ref x) => !x.is_empty(), ValueRepr::Map(ref x, _) => !x.is_empty(), ValueRepr::Dynamic(ref x) => match x.kind() { ObjectKind::Plain => true, ObjectKind::Seq(s) => s.item_count() != 0, ObjectKind::Struct(s) => s.field_count() != 0, }, } } /// Returns `true` if this value is safe. pub fn is_safe(&self) -> bool { matches!(&self.0, ValueRepr::String(_, StringType::Safe)) } /// Returns `true` if this value is undefined. pub fn is_undefined(&self) -> bool { matches!(&self.0, ValueRepr::Undefined) } /// Returns `true` if this value is none. pub fn is_none(&self) -> bool { matches!(&self.0, ValueRepr::None) } /// If the value is a string, return it. pub fn as_str(&self) -> Option<&str> { match &self.0 { ValueRepr::String(ref s, _) => Some(s as &str), _ => None, } } /// Returns the bytes of this value if they exist. pub fn as_bytes(&self) -> Option<&[u8]> { match &self.0 { ValueRepr::String(ref s, _) => Some(s.as_bytes()), ValueRepr::Bytes(ref b) => Some(&b[..]), _ => None, } } /// If the value is an object, it's returned as [`Object`]. pub fn as_object(&self) -> Option<&dyn Object> { match self.0 { ValueRepr::Dynamic(ref dy) => Some(&**dy as &dyn Object), _ => None, } } /// If the value is a sequence it's returned as [`SeqObject`]. pub fn as_seq(&self) -> Option<&dyn SeqObject> { match self.0 { ValueRepr::Seq(ref v) => return Some(&**v as &dyn SeqObject), ValueRepr::Dynamic(ref dy) => { if let ObjectKind::Seq(seq) = dy.kind() { return Some(seq); } } _ => {} } None } /// If the value is a struct, return it as [`StructObject`]. pub fn as_struct(&self) -> Option<&dyn StructObject> { if let ValueRepr::Dynamic(ref dy) = self.0 { if let ObjectKind::Struct(s) = dy.kind() { return Some(s); } } None } /// Returns the length of the contained value. /// /// Values without a length will return `None`. /// /// ``` /// # use minijinja::value::Value; /// let seq = Value::from(vec![1, 2, 3, 4]); /// assert_eq!(seq.len(), Some(4)); /// ``` pub fn len(&self) -> Option { match self.0 { ValueRepr::String(ref s, _) => Some(s.chars().count()), ValueRepr::Map(ref items, _) => Some(items.len()), ValueRepr::Seq(ref items) => Some(items.len()), ValueRepr::Dynamic(ref dy) => match dy.kind() { ObjectKind::Plain => None, ObjectKind::Seq(s) => Some(s.item_count()), ObjectKind::Struct(s) => Some(s.field_count()), }, _ => None, } } /// Looks up an attribute by attribute name. /// /// This this returns [`UNDEFINED`](Self::UNDEFINED) when an invalid key is /// resolved. An error is returned when if the value does not contain an object /// that has attributes. /// /// ``` /// # use minijinja::value::Value; /// # fn test() -> Result<(), minijinja::Error> { /// let ctx = minijinja::context! { /// foo => "Foo" /// }; /// let value = ctx.get_attr("foo")?; /// assert_eq!(value.to_string(), "Foo"); /// # Ok(()) } /// ``` pub fn get_attr(&self, key: &str) -> Result { Ok(match self.0 { ValueRepr::Undefined => return Err(Error::from(ErrorKind::UndefinedError)), ValueRepr::Map(ref items, _) => items.get(&KeyRef::Str(key)).cloned(), ValueRepr::Dynamic(ref dy) => match dy.kind() { ObjectKind::Struct(s) => s.get_field(key), ObjectKind::Plain | ObjectKind::Seq(_) => None, }, _ => None, } .unwrap_or(Value::UNDEFINED)) } /// Alternative lookup strategy without error handling exclusively for context /// resolution. /// /// The main difference is that the return value will be `None` if the value is /// unable to look up the key rather than returning `Undefined` and errors will /// also not be created. pub(crate) fn get_attr_fast(&self, key: &str) -> Option { match self.0 { ValueRepr::Map(ref items, _) => items.get(&KeyRef::Str(key)).cloned(), ValueRepr::Dynamic(ref dy) => match dy.kind() { ObjectKind::Struct(s) => s.get_field(key), ObjectKind::Plain | ObjectKind::Seq(_) => None, }, _ => None, } } /// Looks up an index of the value. /// /// This is a shortcut for [`get_item`](Self::get_item). /// /// ``` /// # use minijinja::value::Value; /// let seq = Value::from(vec![0u32, 1, 2]); /// let value = seq.get_item_by_index(1).unwrap(); /// assert_eq!(value.try_into().ok(), Some(1)); /// ``` pub fn get_item_by_index(&self, idx: usize) -> Result { self.get_item(&Value(ValueRepr::U64(idx as _))) } /// Looks up an item (or attribute) by key. /// /// This is similar to [`get_attr`](Self::get_attr) but instead of using /// a string key this can be any key. For instance this can be used to /// index into sequences. Like [`get_attr`](Self::get_attr) this returns /// [`UNDEFINED`](Self::UNDEFINED) when an invalid key is looked up. /// /// ``` /// # use minijinja::value::Value; /// let ctx = minijinja::context! { /// foo => "Foo", /// }; /// let value = ctx.get_item(&Value::from("foo")).unwrap(); /// assert_eq!(value.to_string(), "Foo"); /// ``` pub fn get_item(&self, key: &Value) -> Result { if let ValueRepr::Undefined = self.0 { Err(Error::from(ErrorKind::UndefinedError)) } else { Ok(self.get_item_opt(key).unwrap_or(Value::UNDEFINED)) } } /// Iterates over the value. /// /// Depending on the [`kind`](Self::kind) of the value the iterator /// has a different behavior. /// /// * [`ValueKind::Map`]: the iterator yields the keys of the map. /// * [`ValueKind::Seq`]: the iterator yields the items in the sequence. /// * [`ValueKind::None`] / [`ValueKind::Undefined`]: the iterator is empty. /// /// ``` /// # use minijinja::value::Value; /// # fn test() -> Result<(), minijinja::Error> { /// let value = Value::from({ /// let mut m = std::collections::BTreeMap::new(); /// m.insert("foo", 42); /// m.insert("bar", 23); /// m /// }); /// for key in value.try_iter()? { /// let value = value.get_item(&key)?; /// println!("{} = {}", key, value); /// } /// # Ok(()) } /// ``` pub fn try_iter(&self) -> Result, Error> { self.try_iter_owned().map(|inner| ValueIter { _marker: PhantomData, inner, }) } /// Returns some reference to the boxed object if it is of type `T`, or None if it isn’t. /// /// This is basically the "reverse" of [`from_object`](Self::from_object). It's also /// a shortcut for [`downcast_ref`](trait.Object.html#method.downcast_ref) /// on the return value of [`as_object`](Self::as_object). /// /// # Example /// /// ```rust /// # use minijinja::value::{Value, Object}; /// use std::fmt; /// /// #[derive(Debug)] /// struct Thing { /// id: usize, /// } /// /// impl fmt::Display for Thing { /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// fmt::Debug::fmt(self, f) /// } /// } /// /// impl Object for Thing {} /// /// let x_value = Value::from_object(Thing { id: 42 }); /// let thing = x_value.downcast_object_ref::().unwrap(); /// assert_eq!(thing.id, 42); /// ``` pub fn downcast_object_ref(&self) -> Option<&T> { self.as_object().and_then(|x| x.downcast_ref()) } pub(crate) fn get_item_opt(&self, key: &Value) -> Option { let key = KeyRef::Value(key.clone()); let seq = match self.0 { ValueRepr::Map(ref items, _) => return items.get(&key).cloned(), ValueRepr::Seq(ref items) => &**items as &dyn SeqObject, ValueRepr::Dynamic(ref dy) => match dy.kind() { ObjectKind::Plain => return None, ObjectKind::Seq(s) => s, ObjectKind::Struct(s) => { return if let Some(key) = key.as_str() { s.get_field(key) } else { None }; } }, ValueRepr::String(ref s, _) => { if let Some(idx) = key.as_i64() { let idx = some!(isize::try_from(idx).ok()); let idx = if idx < 0 { some!(s.chars().count().checked_sub(-idx as usize)) } else { idx as usize }; return s.chars().nth(idx).map(Value::from); } else { return None; } } _ => return None, }; if let Some(idx) = key.as_i64() { let idx = some!(isize::try_from(idx).ok()); let idx = if idx < 0 { some!(seq.item_count().checked_sub(-idx as usize)) } else { idx as usize }; seq.get_item(idx) } else { None } } /// Calls the value directly. /// /// If the value holds a function or macro, this invokes it. Note that in /// MiniJinja there is a separate namespace for methods on objects and callable /// items. To call methods (which should be a rather rare occurrence) you /// have to use [`call_method`](Self::call_method). /// /// The `args` slice is for the arguments of the function call. To pass /// keyword arguments use the [`Kwargs`](crate::value::Kwargs) type. /// /// Usually the state is already available when it's useful to call this method, /// but when it's not available you can get a fresh template state straight /// from the [`Template`](crate::Template) via [`new_state`](crate::Template::new_state). /// /// ``` /// # use minijinja::{Environment, value::{Value, Kwargs}}; /// # let mut env = Environment::new(); /// # env.add_template("foo", "").unwrap(); /// # let tmpl = env.get_template("foo").unwrap(); /// # let state = tmpl.new_state(); let state = &state; /// let func = Value::from_function(|v: i64, kwargs: Kwargs| { /// v * kwargs.get::("mult").unwrap_or(1) /// }); /// let rv = func.call( /// state, /// &[ /// Value::from(42), /// Value::from(Kwargs::from_iter([("mult", Value::from(2))])), /// ], /// ).unwrap(); /// assert_eq!(rv, Value::from(84)); /// ``` pub fn call(&self, state: &State, args: &[Value]) -> Result { if let ValueRepr::Dynamic(ref dy) = self.0 { dy.call(state, args) } else { Err(Error::new( ErrorKind::InvalidOperation, format!("value of type {} is not callable", self.kind()), )) } } /// Calls a method on the value. /// /// The name of the method is `name`, the arguments passed are in the `args` /// slice. pub fn call_method(&self, state: &State, name: &str, args: &[Value]) -> Result { match self.0 { ValueRepr::Dynamic(ref dy) => return dy.call_method(state, name, args), ValueRepr::Map(ref map, _) => { if let Some(value) = map.get(&KeyRef::Str(name)) { return value.call(state, args); } } _ => {} } Err(Error::new( ErrorKind::InvalidOperation, format!("object has no method named {name}"), )) } /// Iterates over the value without holding a reference. pub(crate) fn try_iter_owned(&self) -> Result { let (iter_state, len) = match self.0 { ValueRepr::None | ValueRepr::Undefined => (ValueIteratorState::Empty, 0), ValueRepr::String(ref s, _) => ( ValueIteratorState::Chars(0, Arc::clone(s)), s.chars().count(), ), ValueRepr::Seq(ref seq) => (ValueIteratorState::Seq(0, Arc::clone(seq)), seq.len()), #[cfg(feature = "preserve_order")] ValueRepr::Map(ref items, _) => { (ValueIteratorState::Map(0, Arc::clone(items)), items.len()) } #[cfg(not(feature = "preserve_order"))] ValueRepr::Map(ref items, _) => ( ValueIteratorState::Map( items.iter().next().map(|x| x.0.clone()), Arc::clone(items), ), items.len(), ), ValueRepr::Dynamic(ref obj) => { match obj.kind() { ObjectKind::Plain => (ValueIteratorState::Empty, 0), ObjectKind::Seq(s) => ( ValueIteratorState::DynSeq(0, Arc::clone(obj)), s.item_count(), ), ObjectKind::Struct(s) => { // the assumption is that structs don't have excessive field counts // and that most iterations go over all fields, so creating a // temporary vector here is acceptable. if let Some(fields) = s.static_fields() { (ValueIteratorState::StaticStr(0, fields), fields.len()) } else { let attrs = s.fields(); let attr_count = attrs.len(); (ValueIteratorState::ArcStr(0, attrs), attr_count) } } } } _ => { return Err(Error::new( ErrorKind::InvalidOperation, format!("{} is not iterable", self.kind()), )) } }; Ok(OwnedValueIterator { iter_state, len }) } #[cfg(feature = "builtins")] pub(crate) fn get_path(&self, path: &str) -> Result { let mut rv = self.clone(); for part in path.split('.') { if let Ok(num) = part.parse::() { rv = ok!(rv.get_item_by_index(num)); } else { rv = ok!(rv.get_attr(part)); } } Ok(rv) } } impl Serialize for Value { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // enable round tripping of values if serializing_for_value() { let handle = LAST_VALUE_HANDLE.with(|x| { // we are okay with overflowing the handle here because these values only // live for a very short period of time and it's not likely that you run out // of an entire u32 worth of handles in a single serialization operation. // This lets us stick the handle into a unit variant in the serde data model. let rv = x.get().wrapping_add(1); x.set(rv); rv }); VALUE_HANDLES.with(|handles| handles.borrow_mut().insert(handle, self.clone())); return serializer.serialize_unit_variant( VALUE_HANDLE_MARKER, handle, VALUE_HANDLE_MARKER, ); } match self.0 { ValueRepr::Bool(b) => serializer.serialize_bool(b), ValueRepr::U64(u) => serializer.serialize_u64(u), ValueRepr::I64(i) => serializer.serialize_i64(i), ValueRepr::F64(f) => serializer.serialize_f64(f), ValueRepr::None | ValueRepr::Undefined | ValueRepr::Invalid(_) => { serializer.serialize_unit() } ValueRepr::U128(u) => serializer.serialize_u128(u.0), ValueRepr::I128(i) => serializer.serialize_i128(i.0), ValueRepr::String(ref s, _) => serializer.serialize_str(s), ValueRepr::Bytes(ref b) => serializer.serialize_bytes(b), ValueRepr::Seq(ref elements) => elements.serialize(serializer), ValueRepr::Map(ref entries, _) => { use serde::ser::SerializeMap; let mut map = ok!(serializer.serialize_map(Some(entries.len()))); for (ref k, ref v) in entries.iter() { ok!(map.serialize_entry(k, v)); } map.end() } ValueRepr::Dynamic(ref dy) => match dy.kind() { ObjectKind::Plain => serializer.serialize_str(&dy.to_string()), ObjectKind::Seq(s) => { use serde::ser::SerializeSeq; let mut seq = ok!(serializer.serialize_seq(Some(s.item_count()))); for item in s.iter() { ok!(seq.serialize_element(&item)); } seq.end() } ObjectKind::Struct(s) => { use serde::ser::SerializeMap; let mut map = ok!(serializer.serialize_map(None)); if let Some(fields) = s.static_fields() { for k in fields { let v = s.get_field(k).unwrap_or(Value::UNDEFINED); ok!(map.serialize_entry(k, &v)); } } else { for k in s.fields() { let v = s.get_field(&k).unwrap_or(Value::UNDEFINED); ok!(map.serialize_entry(&*k as &str, &v)); } } map.end() } }, } } } /// Iterates over a value. pub struct ValueIter<'a> { _marker: PhantomData<&'a Value>, inner: OwnedValueIterator, } impl<'a> Iterator for ValueIter<'a> { type Item = Value; #[inline(always)] fn next(&mut self) -> Option { self.inner.next() } } pub(crate) struct OwnedValueIterator { iter_state: ValueIteratorState, len: usize, } impl Iterator for OwnedValueIterator { type Item = Value; fn next(&mut self) -> Option { self.iter_state.advance_state().map(|x| { self.len -= 1; x }) } fn size_hint(&self) -> (usize, Option) { (self.len, Some(self.len)) } } impl ExactSizeIterator for OwnedValueIterator {} impl fmt::Debug for OwnedValueIterator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ValueIterator").finish() } } enum ValueIteratorState { Empty, Chars(usize, Arc), Seq(usize, Arc>), StaticStr(usize, &'static [&'static str]), ArcStr(usize, Vec>), DynSeq(usize, Arc), #[cfg(not(feature = "preserve_order"))] Map(Option>, Arc), #[cfg(feature = "preserve_order")] Map(usize, Arc), } impl ValueIteratorState { fn advance_state(&mut self) -> Option { match self { ValueIteratorState::Empty => None, ValueIteratorState::Chars(offset, ref s) => { (s as &str)[*offset..].chars().next().map(|c| { *offset += c.len_utf8(); Value::from(c) }) } ValueIteratorState::Seq(idx, items) => items .get(*idx) .map(|x| { *idx += 1; x }) .cloned(), ValueIteratorState::StaticStr(idx, items) => items.get(*idx).map(|x| { *idx += 1; Value::from(intern(x)) }), ValueIteratorState::ArcStr(idx, items) => items.get(*idx).map(|x| { *idx += 1; Value::from(x.clone()) }), ValueIteratorState::DynSeq(idx, obj) => { if let ObjectKind::Seq(seq) = obj.kind() { seq.get_item(*idx).map(|x| { *idx += 1; x }) } else { unreachable!() } } #[cfg(feature = "preserve_order")] ValueIteratorState::Map(idx, map) => map.get_index(*idx).map(|x| { *idx += 1; x.0.as_value() }), #[cfg(not(feature = "preserve_order"))] ValueIteratorState::Map(ptr, map) => { if let Some(current) = ptr.take() { let next = map.range(¤t..).nth(1).map(|x| x.0.clone()); let rv = current.as_value(); *ptr = next; Some(rv) } else { None } } } } } #[cfg(test)] mod tests { use super::*; use similar_asserts::assert_eq; #[test] fn test_dynamic_object_roundtrip() { use std::sync::atomic::{self, AtomicUsize}; #[derive(Debug)] struct X(AtomicUsize); impl fmt::Display for X { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0.load(atomic::Ordering::Relaxed)) } } impl Object for X { fn kind(&self) -> ObjectKind<'_> { ObjectKind::Struct(self) } } impl crate::value::object::StructObject for X { fn get_field(&self, name: &str) -> Option { match name { "value" => Some(Value::from(self.0.load(atomic::Ordering::Relaxed))), _ => None, } } fn static_fields(&self) -> Option<&'static [&'static str]> { Some(&["value"][..]) } } let x = Arc::new(X(Default::default())); let x_value = Value::from(x.clone()); x.0.fetch_add(42, atomic::Ordering::Relaxed); let x_clone = Value::from_serializable(&x_value); x.0.fetch_add(23, atomic::Ordering::Relaxed); assert_eq!(x_value.to_string(), "65"); assert_eq!(x_clone.to_string(), "65"); } #[test] fn test_string_char() { let val = Value::from('a'); assert_eq!(char::try_from(val).unwrap(), 'a'); let val = Value::from("a"); assert_eq!(char::try_from(val).unwrap(), 'a'); let val = Value::from("wat"); assert!(char::try_from(val).is_err()); } #[test] #[cfg(target_pointer_width = "64")] fn test_sizes() { assert_eq!(std::mem::size_of::(), 24); } } minijinja-1.0.3/src/value/object.rs000064400000000000000000000470471046102023000153300ustar 00000000000000use std::any::{Any, TypeId}; use std::fmt; use std::ops::Range; use std::sync::Arc; use crate::error::{Error, ErrorKind}; use crate::value::{intern, Value}; use crate::vm::State; /// A utility trait that represents a dynamic object. /// /// The engine uses the [`Value`] type to represent values that the engine /// knows about. Most of these values are primitives such as integers, strings /// or maps. However it is also possible to expose custom types without /// undergoing a serialization step to the engine. For this to work a type /// needs to implement the [`Object`] trait and be wrapped in a value with /// [`Value::from_object`](crate::value::Value::from_object). The ownership of /// the object will then move into the value type. // /// The engine uses reference counted objects with interior mutability in the /// value type. This means that all trait methods take `&self` and types like /// [`Mutex`](std::sync::Mutex) need to be used to enable mutability. // /// Objects need to implement [`Display`](std::fmt::Display) which is used by /// the engine to convert the object into a string if needed. Additionally /// [`Debug`](std::fmt::Debug) is required as well. /// /// The exact runtime characteristics of the object are influenced by the /// [`kind`](Self::kind) of the object. By default an object can just be /// stringified and methods can be called. /// /// For examples of how to implement objects refer to [`SeqObject`] and /// [`StructObject`]. pub trait Object: fmt::Display + fmt::Debug + Any + Sync + Send { /// Describes the kind of an object. /// /// If not implemented behavior for an object is [`ObjectKind::Plain`] /// which just means that it's stringifyable and potentially can be /// called or has methods. /// /// For more information see [`ObjectKind`]. fn kind(&self) -> ObjectKind<'_> { ObjectKind::Plain } /// Called when the engine tries to call a method on the object. /// /// It's the responsibility of the implementer to ensure that an /// error is generated if an invalid method is invoked. /// /// To convert the arguments into arguments use the /// [`from_args`](crate::value::from_args) function. fn call_method(&self, state: &State, name: &str, args: &[Value]) -> Result { let _state = state; let _args = args; Err(Error::new( ErrorKind::UnknownMethod, format!("object has no method named {name}"), )) } /// Called when the object is invoked directly. /// /// The default implementation just generates an error that the object /// cannot be invoked. /// /// To convert the arguments into arguments use the /// [`from_args`](crate::value::from_args) function. fn call(&self, state: &State, args: &[Value]) -> Result { let _state = state; let _args = args; Err(Error::new( ErrorKind::InvalidOperation, "tried to call non callable object", )) } } impl dyn Object { /// Returns some reference to the boxed object if it is of type `T`, or None if it isn’t. /// /// This is basically the "reverse" of [`from_object`](Value::from_object). /// /// # Example /// /// ```rust /// # use minijinja::value::{Value, Object}; /// use std::fmt; /// /// #[derive(Debug)] /// struct Thing { /// id: usize, /// } /// /// impl fmt::Display for Thing { /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// fmt::Debug::fmt(self, f) /// } /// } /// /// impl Object for Thing {} /// /// let x_value = Value::from_object(Thing { id: 42 }); /// let value_as_obj = x_value.as_object().unwrap(); /// let thing = value_as_obj.downcast_ref::().unwrap(); /// assert_eq!(thing.id, 42); /// ``` pub fn downcast_ref(&self) -> Option<&T> { self.is::().then(|| { // SAFETY: `is` ensures this type cast is correct unsafe { &*(self as *const dyn Object as *const T) } }) } /// Checks if the object is of a specific type. pub fn is(&self) -> bool { (*self).type_id() == TypeId::of::() } } impl Object for Arc { #[inline] fn kind(&self) -> ObjectKind<'_> { T::kind(self) } #[inline] fn call_method(&self, state: &State, name: &str, args: &[Value]) -> Result { T::call_method(self, state, name, args) } #[inline] fn call(&self, state: &State, args: &[Value]) -> Result { T::call(self, state, args) } } /// A kind defines the object's behavior. /// /// When a dynamic [`Object`] is implemented, it can be of one of the kinds /// here. The default behavior will be a [`Plain`](Self::Plain) object which /// doesn't do much other than that it can be printed. For an object to turn /// into a [struct](Self::Struct) or [sequence](Self::Seq) the necessary kind /// has to be returned with a pointer to itself. /// /// Today object's can have the behavior of structs and sequences but this /// might expand in the future. It does mean that not all types of values can /// be represented by objects. #[non_exhaustive] pub enum ObjectKind<'a> { /// This object is a plain object. /// /// Such an object has no attributes but it might be callable and it /// can be stringified. When serialized it's serialized in it's /// stringified form. Plain, /// This object is a sequence. /// /// Requires that the object implements [`SeqObject`]. Seq(&'a dyn SeqObject), /// This object is a struct (map with string keys). /// /// Requires that the object implements [`StructObject`]. Struct(&'a dyn StructObject), } /// Provides the behavior of an [`Object`] holding sequence of values. /// /// An object holding a sequence of values (tuple, list etc.) can be /// represented by this trait. /// /// # Simplified Example /// /// For sequences which do not need any special method behavior, the [`Value`] /// type is capable of automatically constructing a wrapper [`Object`] by using /// [`Value::from_seq_object`]. In that case only [`SeqObject`] needs to be /// implemented and the value will provide default implementations for /// stringification and debug printing. /// /// ``` /// use minijinja::value::{Value, SeqObject}; /// /// struct Point(f32, f32, f32); /// /// impl SeqObject for Point { /// fn get_item(&self, idx: usize) -> Option { /// match idx { /// 0 => Some(Value::from(self.0)), /// 1 => Some(Value::from(self.1)), /// 2 => Some(Value::from(self.2)), /// _ => None, /// } /// } /// /// fn item_count(&self) -> usize { /// 3 /// } /// } /// /// let value = Value::from_seq_object(Point(1.0, 2.5, 3.0)); /// ``` /// /// # Full Example /// /// This example shows how one can use [`SeqObject`] in conjunction /// with a fully customized [`Object`]. Note that in this case not /// only [`Object`] needs to be implemented, but also [`Debug`] and /// [`Display`](std::fmt::Display) no longer come for free. /// /// ``` /// use std::fmt; /// use minijinja::value::{Value, Object, ObjectKind, SeqObject}; /// /// #[derive(Debug, Clone)] /// struct Point(f32, f32, f32); /// /// impl fmt::Display for Point { /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// write!(f, "({}, {}, {})", self.0, self.1, self.2) /// } /// } /// /// impl Object for Point { /// fn kind(&self) -> ObjectKind<'_> { /// ObjectKind::Seq(self) /// } /// } /// /// impl SeqObject for Point { /// fn get_item(&self, idx: usize) -> Option { /// match idx { /// 0 => Some(Value::from(self.0)), /// 1 => Some(Value::from(self.1)), /// 2 => Some(Value::from(self.2)), /// _ => None, /// } /// } /// /// fn item_count(&self) -> usize { /// 3 /// } /// } /// /// let value = Value::from_object(Point(1.0, 2.5, 3.0)); /// ``` pub trait SeqObject: Send + Sync { /// Looks up an item by index. /// /// Sequences should provide a value for all items in the range of `0..item_count` /// but the engine will assume that items within the range are `Undefined` /// if `None` is returned. fn get_item(&self, idx: usize) -> Option; /// Returns the number of items in the sequence. fn item_count(&self) -> usize; } impl dyn SeqObject + '_ { /// Convenient iterator over a [`SeqObject`]. pub fn iter(&self) -> SeqObjectIter<'_> { SeqObjectIter { seq: self, range: 0..self.item_count(), } } } impl SeqObject for Arc { #[inline] fn get_item(&self, idx: usize) -> Option { T::get_item(self, idx) } #[inline] fn item_count(&self) -> usize { T::item_count(self) } } impl<'a, T: SeqObject + ?Sized> SeqObject for &'a T { #[inline] fn get_item(&self, idx: usize) -> Option { T::get_item(self, idx) } #[inline] fn item_count(&self) -> usize { T::item_count(self) } } impl + Send + Sync + Clone> SeqObject for [T] { #[inline(always)] fn get_item(&self, idx: usize) -> Option { self.get(idx).cloned().map(Into::into) } #[inline(always)] fn item_count(&self) -> usize { self.len() } } impl + Send + Sync + Clone> SeqObject for Vec { #[inline(always)] fn get_item(&self, idx: usize) -> Option { self.get(idx).cloned().map(Into::into) } #[inline(always)] fn item_count(&self) -> usize { self.len() } } /// Iterates over [`SeqObject`] pub struct SeqObjectIter<'a> { seq: &'a dyn SeqObject, range: Range, } impl<'a> Iterator for SeqObjectIter<'a> { type Item = Value; #[inline(always)] fn next(&mut self) -> Option { self.range .next() .map(|idx| self.seq.get_item(idx).unwrap_or(Value::UNDEFINED)) } #[inline(always)] fn size_hint(&self) -> (usize, Option) { self.range.size_hint() } } impl<'a> DoubleEndedIterator for SeqObjectIter<'a> { #[inline(always)] fn next_back(&mut self) -> Option { self.range .next_back() .map(|idx| self.seq.get_item(idx).unwrap_or(Value::UNDEFINED)) } } impl<'a> ExactSizeIterator for SeqObjectIter<'a> {} /// Provides the behavior of an [`Object`] holding a struct. /// /// An basic object with the shape and behavior of a struct (that means a /// map with string keys) can be represented by this trait. /// /// # Simplified Example /// /// For structs which do not need any special method behavior or methods, the /// [`Value`] type is capable of automatically constructing a wrapper [`Object`] /// by using [`Value::from_struct_object`]. In that case only [`StructObject`] /// needs to be implemented and the value will provide default implementations /// for stringification and debug printing. /// /// ``` /// use minijinja::value::{Value, StructObject}; /// /// struct Point(f32, f32, f32); /// /// impl StructObject for Point { /// fn get_field(&self, name: &str) -> Option { /// match name { /// "x" => Some(Value::from(self.0)), /// "y" => Some(Value::from(self.1)), /// "z" => Some(Value::from(self.2)), /// _ => None, /// } /// } /// /// fn static_fields(&self) -> Option<&'static [&'static str]> { /// Some(&["x", "y", "z"][..]) /// } /// } /// /// let value = Value::from_struct_object(Point(1.0, 2.5, 3.0)); /// ``` /// /// # Full Example /// /// The following example shows how to implement a dynamic object which /// represents a struct. Note that in this case not only [`Object`] needs to be /// implemented, but also [`Debug`] and [`Display`](std::fmt::Display) no longer /// come for free. /// /// ``` /// use std::fmt; /// use minijinja::value::{Value, Object, ObjectKind, StructObject}; /// /// #[derive(Debug, Clone)] /// struct Point(f32, f32, f32); /// /// impl fmt::Display for Point { /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// write!(f, "({}, {}, {})", self.0, self.1, self.2) /// } /// } /// /// impl Object for Point { /// fn kind(&self) -> ObjectKind<'_> { /// ObjectKind::Struct(self) /// } /// } /// /// impl StructObject for Point { /// fn get_field(&self, name: &str) -> Option { /// match name { /// "x" => Some(Value::from(self.0)), /// "y" => Some(Value::from(self.1)), /// "z" => Some(Value::from(self.2)), /// _ => None, /// } /// } /// /// fn static_fields(&self) -> Option<&'static [&'static str]> { /// Some(&["x", "y", "z"][..]) /// } /// } /// /// let value = Value::from_object(Point(1.0, 2.5, 3.0)); /// ``` /// /// # Struct As context /// /// Structs can also be used as template rendering context. This has a lot of /// benefits as it means that the serialization overhead can be largely to /// completely avoided. This means that even if templates take hundreds of /// values, MiniJinja does not spend time eagerly converting them into values. /// /// Here is a very basic example of how a template can be rendered with a dynamic /// context. Note that the implementation of [`fields`](Self::fields) is optional /// for this to work. It's in fact not used by the engine during rendering but /// it is necessary for the [`debug()`](crate::functions::debug) function to be /// able to show which values exist in the context. /// /// ``` /// # fn main() -> Result<(), minijinja::Error> { /// # use minijinja::Environment; /// use minijinja::value::{Value, StructObject}; /// /// pub struct DynamicContext { /// magic: i32, /// } /// /// impl StructObject for DynamicContext { /// fn get_field(&self, field: &str) -> Option { /// match field { /// "pid" => Some(Value::from(std::process::id())), /// "env" => Some(Value::from_iter(std::env::vars())), /// "magic" => Some(Value::from(self.magic)), /// _ => None, /// } /// } /// } /// /// # let env = Environment::new(); /// let tmpl = env.template_from_str("HOME={{ env.HOME }}; PID={{ pid }}; MAGIG={{ magic }}")?; /// let ctx = Value::from_struct_object(DynamicContext { magic: 42 }); /// let rv = tmpl.render(ctx)?; /// # Ok(()) } /// ``` /// /// One thing of note here is that in the above example `env` would be re-created every /// time the template needs it. A better implementation would cache the value after it /// was created first. pub trait StructObject: Send + Sync { /// Invoked by the engine to get a field of a struct. /// /// Where possible it's a good idea for this to align with the return value /// of [`fields`](Self::fields) but it's not necessary. /// /// If an field does not exist, `None` shall be returned. /// /// A note should be made here on side effects: unlike calling objects or /// calling methods on objects, accessing fields is not supposed to /// have side effects. Neither does this API get access to the interpreter /// [`State`] nor is there a channel to send out failures as only an option /// can be returned. If you do plan on doing something in field access /// that is fallible, instead use a method call. fn get_field(&self, name: &str) -> Option; /// If possible returns a static vector of field names. /// /// If fields cannot be statically determined, then this must return `None` /// and [`fields`](Self::fields) should be implemented instead. If however /// this method is implemented, then [`fields`](Self::fields) should not be /// implemented as the default implementation dispatches to here, or it has /// to be implemented to match the output. fn static_fields(&self) -> Option<&'static [&'static str]> { None } /// Returns a vector of field names. /// /// This should be implemented if [`static_fields`](Self::static_fields) cannot /// be implemented due to lifetime restrictions. The default implementation /// converts the return value of [`static_fields`](Self::static_fields) into /// a compatible format automatically. fn fields(&self) -> Vec> { self.static_fields() .into_iter() .flat_map(|fields| fields.iter().copied().map(intern)) .collect() } /// Returns the number of fields. /// /// The default implementation uses [`fields`](Self::fields) and /// [`static_fields`](Self::static_fields) automatically. fn field_count(&self) -> usize { if let Some(fields) = self.static_fields() { fields.len() } else { self.fields().len() } } } impl StructObject for Arc { #[inline] fn get_field(&self, name: &str) -> Option { T::get_field(self, name) } #[inline] fn static_fields(&self) -> Option<&'static [&'static str]> { T::static_fields(self) } #[inline] fn fields(&self) -> Vec> { T::fields(self) } #[inline] fn field_count(&self) -> usize { T::field_count(self) } } impl<'a, T: StructObject + ?Sized> StructObject for &'a T { #[inline] fn get_field(&self, name: &str) -> Option { T::get_field(self, name) } #[inline] fn static_fields(&self) -> Option<&'static [&'static str]> { T::static_fields(self) } #[inline] fn fields(&self) -> Vec> { T::fields(self) } #[inline] fn field_count(&self) -> usize { T::field_count(self) } } #[repr(transparent)] pub struct SimpleSeqObject(pub T); impl fmt::Display for SimpleSeqObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ok!(f.write_str("[")); for (idx, val) in (&self.0 as &dyn SeqObject).iter().enumerate() { if idx > 0 { ok!(f.write_str(", ")); } ok!(write!(f, "{val:?}")); } f.write_str("]") } } impl fmt::Debug for SimpleSeqObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list() .entries((&self.0 as &dyn SeqObject).iter()) .finish() } } impl Object for SimpleSeqObject { fn kind(&self) -> ObjectKind<'_> { ObjectKind::Seq(&self.0) } } #[repr(transparent)] pub struct SimpleStructObject(pub T); impl fmt::Display for SimpleStructObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ok!(f.write_str("{")); for (idx, field) in self.0.fields().iter().enumerate() { if idx > 0 { ok!(f.write_str(", ")); } let val = self.0.get_field(field).unwrap_or(Value::UNDEFINED); ok!(write!(f, "{field:?}: {val:?}")); } f.write_str("}") } } impl fmt::Debug for SimpleStructObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut m = f.debug_map(); for field in self.0.fields() { let value = self.0.get_field(&field).unwrap_or(Value::UNDEFINED); m.entry(&field, &value); } m.finish() } } impl Object for SimpleStructObject { fn kind(&self) -> ObjectKind<'_> { ObjectKind::Struct(&self.0) } } minijinja-1.0.3/src/value/ops.rs000064400000000000000000000253701046102023000146560ustar 00000000000000use std::convert::{TryFrom, TryInto}; use crate::error::{Error, ErrorKind}; use crate::value::{KeyRef, ObjectKind, SeqObject, Value, ValueKind, ValueRepr}; pub enum CoerceResult<'a> { I128(i128, i128), F64(f64, f64), Str(&'a str, &'a str), } pub(crate) fn as_f64(value: &Value) -> Option { Some(match value.0 { ValueRepr::Bool(x) => x as i64 as f64, ValueRepr::U64(x) => x as f64, ValueRepr::U128(x) => x.0 as f64, ValueRepr::I64(x) => x as f64, ValueRepr::I128(x) => x.0 as f64, ValueRepr::F64(x) => x, _ => return None, }) } pub fn coerce<'x>(a: &'x Value, b: &'x Value) -> Option> { match (&a.0, &b.0) { // equal mappings are trivial (ValueRepr::U64(a), ValueRepr::U64(b)) => Some(CoerceResult::I128(*a as i128, *b as i128)), (ValueRepr::U128(a), ValueRepr::U128(b)) => { Some(CoerceResult::I128(a.0 as i128, b.0 as i128)) } (ValueRepr::String(a, _), ValueRepr::String(b, _)) => Some(CoerceResult::Str(a, b)), (ValueRepr::I64(a), ValueRepr::I64(b)) => Some(CoerceResult::I128(*a as i128, *b as i128)), (ValueRepr::I128(a), ValueRepr::I128(b)) => Some(CoerceResult::I128(a.0, b.0)), (ValueRepr::F64(a), ValueRepr::F64(b)) => Some(CoerceResult::F64(*a, *b)), // are floats involved? (ValueRepr::F64(a), _) => Some(CoerceResult::F64(*a, some!(as_f64(b)))), (_, ValueRepr::F64(b)) => Some(CoerceResult::F64(some!(as_f64(a)), *b)), // everything else goes up to i128 _ => Some(CoerceResult::I128( some!(i128::try_from(a.clone()).ok()), some!(i128::try_from(b.clone()).ok()), )), } } fn get_offset_and_len usize>( start: i64, stop: Option, end: F, ) -> (usize, usize) { if start < 0 || stop.map_or(true, |x| x < 0) { let end = end(); let start = if start < 0 { (end as i64 + start) as usize } else { start as usize }; let stop = match stop { None => end, Some(x) if x < 0 => (end as i64 + x) as usize, Some(x) => x as usize, }; (start, stop.saturating_sub(start)) } else { ( start as usize, (stop.unwrap() as usize).saturating_sub(start as usize), ) } } pub fn slice(value: Value, start: Value, stop: Value, step: Value) -> Result { let start: i64 = if start.is_none() { 0 } else { ok!(start.try_into()) }; let stop = if stop.is_none() { None } else { Some(ok!(i64::try_from(stop))) }; let step = if step.is_none() { 1 } else { ok!(u64::try_from(step)) as usize }; if step == 0 { return Err(Error::new( ErrorKind::InvalidOperation, "cannot slice by step size of 0", )); } let maybe_seq = match value.0 { ValueRepr::String(..) => { let s = value.as_str().unwrap(); let (start, len) = get_offset_and_len(start, stop, || s.chars().count()); return Ok(Value::from( s.chars() .skip(start) .take(len) .step_by(step) .collect::(), )); } ValueRepr::Undefined | ValueRepr::None => return Ok(Value::from(Vec::::new())), ValueRepr::Seq(ref s) => Some(&**s as &dyn SeqObject), ValueRepr::Dynamic(ref dy) => { if let ObjectKind::Seq(seq) = dy.kind() { Some(seq) } else { None } } _ => None, }; match maybe_seq { Some(seq) => { let (start, len) = get_offset_and_len(start, stop, || seq.item_count()); Ok(Value::from( seq.iter() .skip(start) .take(len) .step_by(step) .collect::>(), )) } None => Err(Error::new( ErrorKind::InvalidOperation, format!("value of type {} cannot be sliced", value.kind()), )), } } fn int_as_value(val: i128) -> Value { if val as i64 as i128 == val { (val as i64).into() } else { val.into() } } fn impossible_op(op: &str, lhs: &Value, rhs: &Value) -> Error { Error::new( ErrorKind::InvalidOperation, format!( "tried to use {} operator on unsupported types {} and {}", op, lhs.kind(), rhs.kind() ), ) } fn failed_op(op: &str, lhs: &Value, rhs: &Value) -> Error { Error::new( ErrorKind::InvalidOperation, format!("unable to calculate {lhs} {op} {rhs}"), ) } macro_rules! math_binop { ($name:ident, $int:ident, $float:tt) => { pub fn $name(lhs: &Value, rhs: &Value) -> Result { match coerce(lhs, rhs) { Some(CoerceResult::I128(a, b)) => match a.$int(b) { Some(val) => Ok(int_as_value(val)), None => Err(failed_op(stringify!($float), lhs, rhs)) }, Some(CoerceResult::F64(a, b)) => Ok((a $float b).into()), _ => Err(impossible_op(stringify!($float), lhs, rhs)) } } } } pub fn add(lhs: &Value, rhs: &Value) -> Result { match coerce(lhs, rhs) { Some(CoerceResult::I128(a, b)) => Ok(int_as_value(a.wrapping_add(b))), Some(CoerceResult::F64(a, b)) => Ok((a + b).into()), Some(CoerceResult::Str(a, b)) => Ok(Value::from([a, b].concat())), _ => Err(impossible_op("+", lhs, rhs)), } } math_binop!(sub, checked_sub, -); math_binop!(mul, checked_mul, *); math_binop!(rem, checked_rem_euclid, %); pub fn div(lhs: &Value, rhs: &Value) -> Result { fn do_it(lhs: &Value, rhs: &Value) -> Option { let a = some!(as_f64(lhs)); let b = some!(as_f64(rhs)); Some((a / b).into()) } do_it(lhs, rhs).ok_or_else(|| impossible_op("/", lhs, rhs)) } pub fn int_div(lhs: &Value, rhs: &Value) -> Result { match coerce(lhs, rhs) { Some(CoerceResult::I128(a, b)) => { if b != 0 { Ok(int_as_value(a.div_euclid(b))) } else { Err(failed_op("//", lhs, rhs)) } } Some(CoerceResult::F64(a, b)) => Ok(a.div_euclid(b).into()), _ => Err(impossible_op("//", lhs, rhs)), } } /// Implements a binary `pow` operation on values. pub fn pow(lhs: &Value, rhs: &Value) -> Result { match coerce(lhs, rhs) { Some(CoerceResult::I128(a, b)) => { match TryFrom::try_from(b).ok().and_then(|b| a.checked_pow(b)) { Some(val) => Ok(int_as_value(val)), None => Err(failed_op("**", lhs, rhs)), } } Some(CoerceResult::F64(a, b)) => Ok((a.powf(b)).into()), _ => Err(impossible_op("**", lhs, rhs)), } } /// Implements an unary `neg` operation on value. pub fn neg(val: &Value) -> Result { if val.kind() == ValueKind::Number { match val.0 { ValueRepr::F64(x) => Ok((-x).into()), _ => { if let Ok(x) = i128::try_from(val.clone()) { Ok(int_as_value(-x)) } else { Err(Error::from(ErrorKind::InvalidOperation)) } } } } else { Err(Error::from(ErrorKind::InvalidOperation)) } } /// Attempts a string concatenation. pub fn string_concat(left: Value, right: &Value) -> Value { Value::from(format!("{left}{right}")) } /// Implements a containment operation on values. pub fn contains(container: &Value, value: &Value) -> Result { // Special case where if the container is undefined, it cannot hold // values. For strict containment checks the vm has a special case. if container.is_undefined() { return Ok(Value::from(false)); } let rv = if let Some(s) = container.as_str() { if let Some(s2) = value.as_str() { s.contains(s2) } else { s.contains(&value.to_string()) } } else if let Some(seq) = container.as_seq() { seq.iter().any(|item| &item == value) } else if let ValueRepr::Map(ref map, _) = container.0 { map.get(&KeyRef::Value(value.clone())).is_some() } else { return Err(Error::new( ErrorKind::InvalidOperation, "cannot perform a containment check on this value", )); }; Ok(Value::from(rv)) } #[cfg(test)] mod tests { use super::*; use similar_asserts::assert_eq; #[test] fn test_adding() { let err = add(&Value::from("a"), &Value::from(42)).unwrap_err(); assert_eq!( err.to_string(), "invalid operation: tried to use + operator on unsupported types string and number" ); assert_eq!( add(&Value::from(1), &Value::from(2)).unwrap(), Value::from(3) ); assert_eq!( add(&Value::from("foo"), &Value::from("bar")).unwrap(), Value::from("foobar") ); } #[test] fn test_subtracting() { let err = sub(&Value::from("a"), &Value::from(42)).unwrap_err(); assert_eq!( err.to_string(), "invalid operation: tried to use - operator on unsupported types string and number" ); let err = sub(&Value::from("foo"), &Value::from("bar")).unwrap_err(); assert_eq!( err.to_string(), "invalid operation: tried to use - operator on unsupported types string and string" ); assert_eq!( sub(&Value::from(2), &Value::from(1)).unwrap(), Value::from(1) ); } #[test] fn test_dividing() { let err = div(&Value::from("a"), &Value::from(42)).unwrap_err(); assert_eq!( err.to_string(), "invalid operation: tried to use / operator on unsupported types string and number" ); let err = div(&Value::from("foo"), &Value::from("bar")).unwrap_err(); assert_eq!( err.to_string(), "invalid operation: tried to use / operator on unsupported types string and string" ); assert_eq!( div(&Value::from(100), &Value::from(2)).unwrap(), Value::from(50.0) ); } #[test] fn test_concat() { assert_eq!( string_concat(Value::from("foo"), &Value::from(42)), Value::from("foo42") ); assert_eq!( string_concat(Value::from(23), &Value::from(42)), Value::from("2342") ); } } minijinja-1.0.3/src/value/serialize.rs000064400000000000000000000266321046102023000160460ustar 00000000000000use std::collections::BTreeMap; use std::fmt; use serde::{ser, Serialize, Serializer}; use crate::utils::untrusted_size_hint; use crate::value::{ value_map_with_capacity, Arc, KeyRef, MapType, Packed, StringType, Value, ValueMap, ValueRepr, VALUE_HANDLES, VALUE_HANDLE_MARKER, }; #[derive(Debug)] pub struct InvalidValue(Arc); impl std::error::Error for InvalidValue {} impl fmt::Display for InvalidValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl serde::ser::Error for InvalidValue { fn custom(msg: T) -> Self where T: fmt::Display, { InvalidValue(Arc::from(msg.to_string())) } } /// Transforms a serializable value to a value object. /// /// This neither fails nor panics. For objects that cannot be represented /// the value might be represented as a half broken error object. pub fn transform(value: T) -> Value { match value.serialize(ValueSerializer) { Ok(rv) => rv, Err(invalid) => ValueRepr::Invalid(invalid.0).into(), } } pub struct ValueSerializer; impl Serializer for ValueSerializer { type Ok = Value; type Error = InvalidValue; type SerializeSeq = SerializeSeq; type SerializeTuple = SerializeTuple; type SerializeTupleStruct = SerializeTupleStruct; type SerializeTupleVariant = SerializeTupleVariant; type SerializeMap = SerializeMap; type SerializeStruct = SerializeStruct; type SerializeStructVariant = SerializeStructVariant; fn serialize_bool(self, v: bool) -> Result { Ok(ValueRepr::Bool(v).into()) } fn serialize_i8(self, v: i8) -> Result { Ok(ValueRepr::I64(v as i64).into()) } fn serialize_i16(self, v: i16) -> Result { Ok(ValueRepr::I64(v as i64).into()) } fn serialize_i32(self, v: i32) -> Result { Ok(ValueRepr::I64(v as i64).into()) } fn serialize_i64(self, v: i64) -> Result { Ok(ValueRepr::I64(v).into()) } fn serialize_i128(self, v: i128) -> Result { Ok(ValueRepr::I128(Packed(v)).into()) } fn serialize_u8(self, v: u8) -> Result { Ok(ValueRepr::U64(v as u64).into()) } fn serialize_u16(self, v: u16) -> Result { Ok(ValueRepr::U64(v as u64).into()) } fn serialize_u32(self, v: u32) -> Result { Ok(ValueRepr::U64(v as u64).into()) } fn serialize_u64(self, v: u64) -> Result { Ok(ValueRepr::U64(v).into()) } fn serialize_u128(self, v: u128) -> Result { Ok(ValueRepr::U128(Packed(v)).into()) } fn serialize_f32(self, v: f32) -> Result { Ok(ValueRepr::F64(v as f64).into()) } fn serialize_f64(self, v: f64) -> Result { Ok(ValueRepr::F64(v).into()) } fn serialize_char(self, v: char) -> Result { Ok(ValueRepr::String(Arc::from(v.to_string()), StringType::Normal).into()) } fn serialize_str(self, value: &str) -> Result { Ok(ValueRepr::String(Arc::from(value.to_owned()), StringType::Normal).into()) } fn serialize_bytes(self, value: &[u8]) -> Result { Ok(ValueRepr::Bytes(Arc::new(value.to_owned())).into()) } fn serialize_none(self) -> Result { Ok(ValueRepr::None.into()) } fn serialize_some(self, value: &T) -> Result where T: Serialize, { Ok(transform(value)) } fn serialize_unit(self) -> Result { Ok(ValueRepr::None.into()) } fn serialize_unit_struct(self, _name: &'static str) -> Result { Ok(ValueRepr::None.into()) } fn serialize_unit_variant( self, name: &'static str, variant_index: u32, variant: &'static str, ) -> Result { if name == VALUE_HANDLE_MARKER && variant == VALUE_HANDLE_MARKER { Ok(VALUE_HANDLES.with(|handles| { let mut handles = handles.borrow_mut(); handles .remove(&variant_index) .expect("value handle not in registry") })) } else { Ok(Value::from(variant)) } } fn serialize_newtype_struct( self, _name: &'static str, value: &T, ) -> Result where T: Serialize, { Ok(transform(value)) } fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, value: &T, ) -> Result where T: Serialize, { let mut map = value_map_with_capacity(1); map.insert(KeyRef::Str(variant), transform(value)); Ok(ValueRepr::Map(Arc::new(map), MapType::Normal).into()) } fn serialize_seq(self, len: Option) -> Result { Ok(SerializeSeq { elements: Vec::with_capacity(untrusted_size_hint(len.unwrap_or(0))), }) } fn serialize_tuple(self, len: usize) -> Result { Ok(SerializeTuple { elements: Vec::with_capacity(untrusted_size_hint(len)), }) } fn serialize_tuple_struct( self, _name: &'static str, len: usize, ) -> Result { Ok(SerializeTupleStruct { fields: Vec::with_capacity(untrusted_size_hint(len)), }) } fn serialize_tuple_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result { Ok(SerializeTupleVariant { name: variant, fields: Vec::with_capacity(untrusted_size_hint(len)), }) } fn serialize_map(self, len: Option) -> Result { Ok(SerializeMap { entries: value_map_with_capacity(len.unwrap_or(0)), key: None, }) } fn serialize_struct( self, _name: &'static str, len: usize, ) -> Result { Ok(SerializeStruct { fields: value_map_with_capacity(len), }) } fn serialize_struct_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result { Ok(SerializeStructVariant { variant, map: value_map_with_capacity(len), }) } } pub struct SerializeSeq { elements: Vec, } impl ser::SerializeSeq for SerializeSeq { type Ok = Value; type Error = InvalidValue; fn serialize_element(&mut self, value: &T) -> Result<(), InvalidValue> where T: Serialize, { self.elements.push(transform(value)); Ok(()) } fn end(self) -> Result { Ok(ValueRepr::Seq(Arc::new(self.elements)).into()) } } pub struct SerializeTuple { elements: Vec, } impl ser::SerializeTuple for SerializeTuple { type Ok = Value; type Error = InvalidValue; fn serialize_element(&mut self, value: &T) -> Result<(), InvalidValue> where T: Serialize, { self.elements.push(transform(value)); Ok(()) } fn end(self) -> Result { Ok(ValueRepr::Seq(Arc::new(self.elements)).into()) } } pub struct SerializeTupleStruct { fields: Vec, } impl ser::SerializeTupleStruct for SerializeTupleStruct { type Ok = Value; type Error = InvalidValue; fn serialize_field(&mut self, value: &T) -> Result<(), InvalidValue> where T: Serialize, { self.fields.push(transform(value)); Ok(()) } fn end(self) -> Result { Ok(Value(ValueRepr::Seq(Arc::new(self.fields)))) } } pub struct SerializeTupleVariant { name: &'static str, fields: Vec, } impl ser::SerializeTupleVariant for SerializeTupleVariant { type Ok = Value; type Error = InvalidValue; fn serialize_field(&mut self, value: &T) -> Result<(), InvalidValue> where T: Serialize, { self.fields.push(transform(value)); Ok(()) } fn end(self) -> Result { let mut map = value_map_with_capacity(1); map.insert( KeyRef::Str(self.name), Value(ValueRepr::Seq(self.fields.into())), ); Ok(Value(ValueRepr::Map(map.into(), MapType::Normal))) } } pub struct SerializeMap { entries: ValueMap, key: Option, } impl ser::SerializeMap for SerializeMap { type Ok = Value; type Error = InvalidValue; fn serialize_key(&mut self, key: &T) -> Result<(), InvalidValue> where T: Serialize, { match key.serialize(ValueSerializer) { Ok(key) => self.key = Some(key), Err(_) => self.key = None, } Ok(()) } fn serialize_value(&mut self, value: &T) -> Result<(), InvalidValue> where T: Serialize, { if let Some(key) = self.key.take() { self.entries.insert(KeyRef::Value(key), transform(value)); } Ok(()) } fn end(self) -> Result { Ok(Value(ValueRepr::Map( Arc::new(self.entries), MapType::Normal, ))) } fn serialize_entry( &mut self, key: &K, value: &V, ) -> Result<(), InvalidValue> where K: Serialize, V: Serialize, { if let Ok(key) = key.serialize(ValueSerializer) { self.entries.insert(KeyRef::Value(key), transform(value)); } Ok(()) } } pub struct SerializeStruct { fields: ValueMap, } impl ser::SerializeStruct for SerializeStruct { type Ok = Value; type Error = InvalidValue; fn serialize_field( &mut self, key: &'static str, value: &T, ) -> Result<(), InvalidValue> where T: Serialize, { self.fields.insert(KeyRef::Str(key), transform(value)); Ok(()) } fn end(self) -> Result { Ok(ValueRepr::Map(Arc::new(self.fields), MapType::Normal).into()) } } pub struct SerializeStructVariant { variant: &'static str, map: ValueMap, } impl ser::SerializeStructVariant for SerializeStructVariant { type Ok = Value; type Error = InvalidValue; fn serialize_field( &mut self, key: &'static str, value: &T, ) -> Result<(), InvalidValue> where T: Serialize, { self.map.insert(KeyRef::Str(key), transform(value)); Ok(()) } fn end(self) -> Result { let mut rv = BTreeMap::new(); rv.insert( self.variant, Value::from(ValueRepr::Map(Arc::new(self.map), MapType::Normal)), ); Ok(rv.into()) } } minijinja-1.0.3/src/vm/closure_object.rs000064400000000000000000000026741046102023000163670ustar 00000000000000use std::collections::BTreeMap; use std::fmt; use std::sync::{Arc, Mutex}; use crate::value::{Object, ObjectKind, StructObject, Value}; /// Utility to enclose values for macros. /// /// See `closure` on the [`Frame`] for how it's used. #[derive(Debug, Default)] pub(crate) struct Closure { values: Mutex, Value>>, } impl Closure { /// Stores a value by key in the closure. pub fn store(&self, key: &str, value: Value) { self.values.lock().unwrap().insert(Arc::from(key), value); } /// Upset a value into the closure. #[cfg(feature = "macros")] pub fn store_if_missing Value>(&self, key: &str, f: F) { let mut values = self.values.lock().unwrap(); if !values.contains_key(key) { values.insert(Arc::from(key), f()); } } } impl fmt::Display for Closure { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut m = f.debug_map(); for (key, value) in self.values.lock().unwrap().iter() { m.entry(&key, &value); } m.finish() } } impl Object for Closure { fn kind(&self) -> ObjectKind<'_> { ObjectKind::Struct(self) } } impl StructObject for Closure { fn fields(&self) -> Vec> { self.values.lock().unwrap().keys().cloned().collect() } fn get_field(&self, name: &str) -> Option { self.values.lock().unwrap().get(name).cloned() } } minijinja-1.0.3/src/vm/context.rs000064400000000000000000000243231046102023000150440ustar 00000000000000use std::borrow::Cow; use std::collections::{BTreeMap, HashSet}; use std::fmt; use std::sync::Arc; use crate::environment::Environment; use crate::error::{Error, ErrorKind}; use crate::value::{OwnedValueIterator, Value, ValueRepr}; use crate::vm::closure_object::Closure; use crate::vm::loop_object::Loop; type Locals<'env> = BTreeMap<&'env str, Value>; /// The maximum recursion in the VM. Normally each stack frame /// adds one to this counter (eg: every time a frame is added). /// However in some situations more depth is pushed if the cost /// of the stack frame is higher. const MAX_RECURSION: usize = 500; pub(crate) struct LoopState { pub(crate) with_loop_var: bool, pub(crate) recurse_jump_target: Option, // if we're popping the frame, do we want to jump somewhere? The // first item is the target jump instruction, the second argument // tells us if we need to end capturing. pub(crate) current_recursion_jump: Option<(usize, bool)>, pub(crate) iterator: OwnedValueIterator, pub(crate) object: Arc, } pub(crate) struct Frame<'env> { pub(crate) locals: Locals<'env>, pub(crate) ctx: Value, pub(crate) current_loop: Option, // normally a frame does not carry a closure, but it can when a macro is // declared. Once that happens, all writes to the frames locals are also // duplicated into the closure. Macros declared on that level, then share // the closure object to enclose the parent values. This emulates the // behavior of closures in Jinja2. pub(crate) closure: Option>, } impl<'env> Default for Frame<'env> { fn default() -> Frame<'env> { Frame::new(Value::UNDEFINED) } } impl<'env> Frame<'env> { /// Creates a new frame with the given context and no validation pub fn new(ctx: Value) -> Frame<'env> { Frame { locals: Locals::new(), ctx, current_loop: None, closure: None, } } /// Creates a new frame with the given context and validates the the value is not invalid pub fn new_checked(root: Value) -> Result, Error> { if let ValueRepr::Invalid(ref err) = root.0 { Err(Error::new(ErrorKind::BadSerialization, err.to_string())) } else { Ok(Frame::new(root)) } } } #[cfg(feature = "internal_debug")] impl<'env> fmt::Debug for Frame<'env> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut m = f.debug_map(); m.entry(&"locals", &self.locals); if let Some(LoopState { object: ref controller, .. }) = self.current_loop { m.entry(&"loop", controller); } if !self.ctx.is_undefined() { m.entry(&"ctx", &self.ctx); } m.finish() } } #[cfg_attr(feature = "internal_debug", derive(Debug))] pub(crate) struct Stack { values: Vec, } impl Default for Stack { fn default() -> Stack { Stack { values: Vec::with_capacity(16), } } } impl Stack { pub fn push(&mut self, arg: Value) { self.values.push(arg); } #[track_caller] pub fn pop(&mut self) -> Value { self.values.pop().unwrap() } pub fn slice_top(&mut self, n: usize) -> &[Value] { &self.values[self.values.len() - n..] } pub fn drop_top(&mut self, n: usize) { self.values.truncate(self.values.len() - n); } pub fn try_pop(&mut self) -> Option { self.values.pop() } #[track_caller] pub fn peek(&self) -> &Value { self.values.last().unwrap() } } impl From> for Stack { fn from(values: Vec) -> Stack { Stack { values } } } pub(crate) struct Context<'env> { stack: Vec>, outer_stack_depth: usize, } impl<'env> fmt::Debug for Context<'env> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn dump<'a>( m: &mut std::fmt::DebugMap, seen: &mut HashSet>, ctx: &'a Context<'a>, ) -> fmt::Result { for frame in ctx.stack.iter().rev() { for (key, value) in frame.locals.iter() { if !seen.contains(&Cow::Borrowed(*key)) { m.entry(&key, value); seen.insert(Cow::Borrowed(key)); } } if let Some(ref l) = frame.current_loop { if l.with_loop_var && !seen.contains("loop") { m.entry(&"loop", &l.object); seen.insert(Cow::Borrowed("loop")); } } if let Ok(iter) = frame.ctx.try_iter() { for key in iter { if let Some(str_key) = key.as_str() { if !seen.contains(&Cow::Borrowed(str_key)) { if let Ok(value) = frame.ctx.get_item(&key) { m.entry(&str_key, &value); seen.insert(Cow::Owned(str_key.to_owned())); } } } } } } Ok(()) } let mut m = f.debug_map(); let mut seen = HashSet::new(); ok!(dump(&mut m, &mut seen, self)); m.finish() } } impl<'env> Default for Context<'env> { fn default() -> Context<'env> { Context { stack: Vec::with_capacity(32), outer_stack_depth: 0, } } } impl<'env> Context<'env> { /// Creates a context pub fn new(frame: Frame<'env>) -> Context<'env> { let mut rv = Context::default(); rv.stack.push(frame); rv } /// Stores a variable in the context. pub fn store(&mut self, key: &'env str, value: Value) { let top = self.stack.last_mut().unwrap(); if let Some(ref closure) = top.closure { closure.store(key, value.clone()); } top.locals.insert(key, value); } /// Adds a value to a closure if missing. /// /// All macros declare on a certain level reuse the same closure. This is done /// to emulate the behavior of how scopes work in Jinja2 in Python. The /// unfortunate downside is that this has to be done with a `Mutex`. #[cfg(feature = "macros")] pub fn enclose(&mut self, env: &Environment, key: &str) { self.closure() .store_if_missing(key, || self.load(env, key).unwrap_or(Value::UNDEFINED)); } /// Loads the closure and returns it. #[cfg(feature = "macros")] pub fn closure(&mut self) -> Arc { let top = self.stack.last_mut().unwrap(); top.closure .get_or_insert_with(|| Arc::new(Closure::default())) .clone() } /// Temporarily takes the closure. /// /// This is done because includes are in the same scope as the module that /// triggers the import, but we do not want to allow closures to be modified /// from another file as this would be very confusing. /// /// This means that if you override a variable referenced by a macro after /// including in the parent template, it will not override the value seen by /// the macro. #[cfg(feature = "multi_template")] pub fn take_closure(&mut self) -> Option> { self.stack.last_mut().unwrap().closure.take() } /// Puts the closure back. #[cfg(feature = "multi_template")] pub fn reset_closure(&mut self, closure: Option>) { self.stack.last_mut().unwrap().closure = closure; } /// Looks up a variable in the context. pub fn load(&self, env: &Environment, key: &str) -> Option { for frame in self.stack.iter().rev() { // look at locals first if let Some(value) = frame.locals.get(key) { return Some(value.clone()); } // if we are a loop, check if we are looking up the special loop var. if let Some(ref l) = frame.current_loop { if l.with_loop_var && key == "loop" { return Some(Value::from(l.object.clone())); } } // perform a fast lookup. This one will not produce errors if the // context is undefined or of the wrong type. if let Some(rv) = frame.ctx.get_attr_fast(key) { return Some(rv); } } env.get_global(key) } /// Pushes a new layer. pub fn push_frame(&mut self, layer: Frame<'env>) -> Result<(), Error> { ok!(self.check_depth()); self.stack.push(layer); Ok(()) } /// Pops the topmost layer. #[track_caller] pub fn pop_frame(&mut self) -> Frame { self.stack.pop().unwrap() } /// Returns the root locals (exports) #[track_caller] pub fn exports(&self) -> &Locals<'env> { &self.stack.first().unwrap().locals } /// Returns the current locals mutably. #[track_caller] #[cfg(feature = "multi_template")] pub fn current_locals_mut(&mut self) -> &mut Locals<'env> { &mut self.stack.last_mut().unwrap().locals } /// Returns the current innermost loop. pub fn current_loop(&mut self) -> Option<&mut LoopState> { self.stack .iter_mut() .rev() .filter_map(|x| x.current_loop.as_mut()) .next() } /// The real depth of the context. pub fn depth(&self) -> usize { self.outer_stack_depth + self.stack.len() } /// Increase the stack depth. #[allow(unused)] pub fn incr_depth(&mut self, delta: usize) -> Result<(), Error> { self.outer_stack_depth += delta; ok!(self.check_depth()); Ok(()) } /// Decrease the stack depth. #[allow(unused)] pub fn decr_depth(&mut self, delta: usize) { self.outer_stack_depth -= delta; } fn check_depth(&self) -> Result<(), Error> { if self.depth() > MAX_RECURSION { return Err(Error::new( ErrorKind::InvalidOperation, "recursion limit exceeded", )); } Ok(()) } } minijinja-1.0.3/src/vm/fuel.rs000064400000000000000000000042741046102023000143160ustar 00000000000000use crate::compiler::instructions::Instruction; use crate::error::{Error, ErrorKind}; use std::sync::atomic::{AtomicIsize, Ordering}; use std::sync::Arc; /// Helper for tracking fuel consumption pub struct FuelTracker { // The initial fuel level. initial: u64, // This should be an AtomicI64 but sadly 32bit targets do not necessarily have // AtomicI64 available. remaining: AtomicIsize, } impl FuelTracker { /// Creates a new fuel tracker. /// /// The fuel tracker is always wrapped in an `Arc` so that it can be /// shared across nested invocations of the template evaluation. pub fn new(fuel: u64) -> Arc { Arc::new(FuelTracker { initial: fuel, remaining: AtomicIsize::new(fuel as isize), }) } /// Tracks an instruction. If it runs out of fuel an error is returned. pub fn track(&self, instr: &Instruction) -> Result<(), Error> { let fuel_to_consume = fuel_for_instruction(instr); if fuel_to_consume != 0 { let old_fuel = self.remaining.fetch_sub(fuel_to_consume, Ordering::Relaxed); if old_fuel - fuel_to_consume <= 0 { return Err(Error::from(ErrorKind::OutOfFuel)); } } Ok(()) } /// Returns the remaining fuel. pub fn remaining(&self) -> u64 { self.remaining.load(Ordering::Relaxed) as _ } /// Returns the consumed fuel. pub fn consumed(&self) -> u64 { self.initial.saturating_sub(self.remaining()) } } /// How much fuel does an instruction consume? fn fuel_for_instruction(instruction: &Instruction) -> isize { match instruction { Instruction::BeginCapture(_) | Instruction::PushLoop(_) | Instruction::PushDidNotIterate | Instruction::PushWith | Instruction::PopFrame | Instruction::DupTop | Instruction::DiscardTop | Instruction::PushAutoEscape | Instruction::PopAutoEscape => 0, #[cfg(feature = "multi_template")] Instruction::ExportLocals => 0, #[cfg(feature = "macros")] Instruction::LoadBlocks | Instruction::BuildMacro(..) | Instruction::Return => 0, _ => 1, } } minijinja-1.0.3/src/vm/loop_object.rs000064400000000000000000000105211046102023000156520ustar 00000000000000use std::fmt; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; use crate::error::{Error, ErrorKind}; use crate::value::{Object, ObjectKind, StructObject, Value}; use crate::vm::state::State; pub(crate) struct Loop { pub len: usize, pub idx: AtomicUsize, pub depth: usize, #[cfg(feature = "adjacent_loop_items")] pub value_triple: Mutex<(Option, Option, Option)>, pub last_changed_value: Mutex>>, } impl fmt::Debug for Loop { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("Loop"); for attr in self.static_fields().unwrap() { s.field(attr, &self.get_field(attr).unwrap()); } s.finish() } } impl Object for Loop { fn kind(&self) -> ObjectKind<'_> { ObjectKind::Struct(self) } fn call(&self, _state: &State, _args: &[Value]) -> Result { Err(Error::new( ErrorKind::InvalidOperation, "loop cannot be called if reassigned to different variable", )) } fn call_method(&self, _state: &State, name: &str, args: &[Value]) -> Result { if name == "changed" { let mut last_changed_value = self.last_changed_value.lock().unwrap(); let value = args.to_owned(); let changed = last_changed_value.as_ref() != Some(&value); if changed { *last_changed_value = Some(value); Ok(Value::from(true)) } else { Ok(Value::from(false)) } } else if name == "cycle" { let idx = self.idx.load(Ordering::Relaxed); match args.get(idx % args.len()) { Some(arg) => Ok(arg.clone()), None => Ok(Value::UNDEFINED), } } else { Err(Error::new( ErrorKind::UnknownMethod, format!("loop object has no method named {name}"), )) } } } impl StructObject for Loop { fn static_fields(&self) -> Option<&'static [&'static str]> { Some( &[ "index0", "index", "length", "revindex", "revindex0", "first", "last", "depth", "depth0", #[cfg(feature = "adjacent_loop_items")] "previtem", #[cfg(feature = "adjacent_loop_items")] "nextitem", ][..], ) } fn get_field(&self, name: &str) -> Option { let idx = self.idx.load(Ordering::Relaxed) as u64; // if we never iterated, then all attributes are undefined. // this can happen in some rare circumstances where the engine // did not manage to iterate if idx == !0 { return Some(Value::UNDEFINED); } let len = self.len as u64; match name { "index0" => Some(Value::from(idx)), "index" => Some(Value::from(idx + 1)), "length" => Some(Value::from(len)), "revindex" => Some(Value::from(len.saturating_sub(idx))), "revindex0" => Some(Value::from(len.saturating_sub(idx).saturating_sub(1))), "first" => Some(Value::from(idx == 0)), "last" => Some(Value::from(len == 0 || idx == len - 1)), "depth" => Some(Value::from(self.depth + 1)), "depth0" => Some(Value::from(self.depth)), #[cfg(feature = "adjacent_loop_items")] "previtem" => Some( self.value_triple .lock() .unwrap() .0 .clone() .unwrap_or(Value::UNDEFINED), ), #[cfg(feature = "adjacent_loop_items")] "nextitem" => Some( self.value_triple .lock() .unwrap() .2 .clone() .unwrap_or(Value::UNDEFINED), ), _ => None, } } } impl fmt::Display for Loop { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "", self.idx.load(Ordering::Relaxed), self.len ) } } minijinja-1.0.3/src/vm/macro_object.rs000064400000000000000000000126501046102023000160070ustar 00000000000000use std::collections::BTreeSet; use std::fmt; use std::sync::Arc; use crate::error::{Error, ErrorKind}; use crate::output::Output; use crate::utils::AutoEscape; use crate::value::{ KeyRef, MapType, Object, ObjectKind, StringType, StructObject, Value, ValueRepr, }; use crate::vm::state::State; use crate::vm::Vm; pub(crate) struct MacroData { pub name: Arc, pub arg_spec: Vec>, // because values need to be 'static, we can't hold a reference to the // instructions that declared the macro. Instead of that we place the // reference to the macro instruction (and the jump offset) in the // state under `state.macros`. pub macro_ref_id: usize, pub closure: Value, pub caller_reference: bool, } pub(crate) struct Macro { // the extra level of Arc here is necessary for recursive calls only. // For more information have a look at the call() method. pub data: Arc, } impl fmt::Debug for Macro { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") } } impl fmt::Display for Macro { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "", self.data.name) } } impl Object for Macro { fn kind(&self) -> ObjectKind<'_> { ObjectKind::Struct(self) } fn call(&self, state: &State, args: &[Value]) -> Result { let (args, kwargs) = match args.last() { Some(Value(ValueRepr::Map(kwargs, MapType::Kwargs))) => { (&args[..args.len() - 1], Some(kwargs)) } _ => (args, None), }; if args.len() > self.data.arg_spec.len() { return Err(Error::from(ErrorKind::TooManyArguments)); } let mut kwargs_used = BTreeSet::new(); let mut arg_values = Vec::with_capacity(self.data.arg_spec.len()); for (idx, name) in self.data.arg_spec.iter().enumerate() { let kwarg = match kwargs { Some(kwargs) => kwargs.get(&KeyRef::Str(name)), _ => None, }; arg_values.push(match (args.get(idx), kwarg) { (Some(_), Some(_)) => { return Err(Error::new( ErrorKind::TooManyArguments, format!("duplicate argument `{name}`"), )) } (Some(arg), None) => arg.clone(), (None, Some(kwarg)) => { kwargs_used.insert(name as &str); kwarg.clone() } (None, None) => Value::UNDEFINED, }); } let caller = if self.data.caller_reference { kwargs_used.insert("caller"); Some( kwargs .and_then(|x| x.get(&KeyRef::Str("caller")).cloned()) .unwrap_or(Value::UNDEFINED), ) } else { None }; if let Some(kwargs) = kwargs { for key in kwargs.keys().filter_map(|x| x.as_str()) { if !kwargs_used.contains(key) { return Err(Error::new( ErrorKind::TooManyArguments, format!("unknown keyword argument `{key}`"), )); } } } let (instructions, offset) = &state.macros[self.data.macro_ref_id]; let vm = Vm::new(state.env()); let mut rv = String::new(); let mut out = Output::with_string(&mut rv); // If a macro is self referential we need to put a reference to ourselves // there. Unfortunately because we only have a &self reference here, we // cannot bump our own refcount. Instead we need to wrap the macro data // into an extra level of Arc to avoid unnecessary clones. let closure = self.data.closure.clone(); // This requires some explanation here. Because we get the state as &State and // not &mut State we are required to create a new state here. This is unfortunate // but makes the calling interface more convenient for the rest of the system. // Because macros cannot return anything other than strings (most importantly they) // can't return other macros this is however not an issue, as modifications in the // macro cannot leak out. ok!(vm.eval_macro( instructions, *offset, closure, caller, &mut out, state, arg_values )); Ok(if !matches!(state.auto_escape(), AutoEscape::None) { Value::from_safe_string(rv) } else { Value::from(rv) }) } } impl StructObject for Macro { fn static_fields(&self) -> Option<&'static [&'static str]> { Some(&["name", "arguments", "caller"][..]) } fn get_field(&self, name: &str) -> Option { match name { "name" => Some(Value(ValueRepr::String( self.data.name.clone(), StringType::Normal, ))), "arguments" => Some(Value::from( self.data .arg_spec .iter() .map(|x| Value(ValueRepr::String(x.clone(), StringType::Normal))) .collect::>(), )), "caller" => Some(Value::from(self.data.caller_reference)), _ => None, } } } minijinja-1.0.3/src/vm/mod.rs000064400000000000000000001125151046102023000141400ustar 00000000000000use std::collections::BTreeMap; use std::mem; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use crate::compiler::instructions::{ Instruction, Instructions, LOOP_FLAG_RECURSIVE, LOOP_FLAG_WITH_LOOP_VAR, MAX_LOCALS, }; use crate::environment::Environment; use crate::error::{Error, ErrorKind}; use crate::output::{CaptureMode, Output}; use crate::utils::{untrusted_size_hint, AutoEscape, UndefinedBehavior}; use crate::value::{ ops, value_map_with_capacity, value_optimization, KeyRef, MapType, Value, ValueRepr, }; use crate::vm::context::{Context, Frame, LoopState, Stack}; use crate::vm::loop_object::Loop; use crate::vm::state::BlockStack; #[cfg(feature = "macros")] use crate::vm::macro_object::{Macro, MacroData}; pub use crate::vm::state::State; mod closure_object; mod context; #[cfg(feature = "fuel")] mod fuel; mod loop_object; #[cfg(feature = "macros")] mod macro_object; mod state; // the cost of a single include against the stack limit. #[cfg(feature = "multi_template")] const INCLUDE_RECURSION_COST: usize = 10; // the cost of a single macro call against the stack limit. #[cfg(feature = "macros")] const MACRO_RECURSION_COST: usize = 5; /// Helps to evaluate something. #[cfg_attr(feature = "internal_debug", derive(Debug))] pub struct Vm<'env> { env: &'env Environment<'env>, } pub(crate) fn prepare_blocks<'env, 'template>( blocks: &'template BTreeMap<&'env str, Instructions<'env>>, ) -> BTreeMap<&'env str, BlockStack<'template, 'env>> { blocks .iter() .map(|(name, instr)| (*name, BlockStack::new(instr))) .collect() } fn get_or_lookup_local(vec: &mut [Option], local_id: u8, f: F) -> Option where T: Copy, F: FnOnce() -> Option, { if local_id == !0 { f() } else if let Some(Some(rv)) = vec.get(local_id as usize) { Some(*rv) } else { let val = some!(f()); vec[local_id as usize] = Some(val); Some(val) } } impl<'env> Vm<'env> { /// Creates a new VM. pub fn new(env: &'env Environment<'env>) -> Vm<'env> { Vm { env } } /// Evaluates the given inputs. /// /// It returns both the last value left on the stack as well as the state /// at the end of the evaluation. pub fn eval<'template>( &self, instructions: &'template Instructions<'env>, root: Value, blocks: &'template BTreeMap<&'env str, Instructions<'env>>, out: &mut Output, auto_escape: AutoEscape, ) -> Result<(Option, State<'template, 'env>), Error> { let _guard = value_optimization(); let mut state = State::new( self.env, Context::new(ok!(Frame::new_checked(root))), auto_escape, instructions, prepare_blocks(blocks), ); self.eval_state(&mut state, out).map(|x| (x, state)) } /// Evaluate a macro in a state. #[cfg(feature = "macros")] #[allow(clippy::too_many_arguments)] pub fn eval_macro( &self, instructions: &Instructions<'env>, pc: usize, closure: Value, caller: Option, out: &mut Output, state: &State, args: Vec, ) -> Result, Error> { let mut ctx = Context::new(Frame::new(closure)); if let Some(caller) = caller { ctx.store("caller", caller); } ok!(ctx.incr_depth(state.ctx.depth() + MACRO_RECURSION_COST)); self.eval_impl( &mut State { env: self.env, ctx, current_block: None, auto_escape: state.auto_escape(), instructions, blocks: BTreeMap::default(), loaded_templates: Default::default(), #[cfg(feature = "macros")] macros: state.macros.clone(), #[cfg(feature = "fuel")] fuel_tracker: state.fuel_tracker.clone(), }, out, Stack::from(args), pc, ) } /// This is the actual evaluation loop that works with a specific context. #[inline(always)] fn eval_state( &self, state: &mut State<'_, 'env>, out: &mut Output, ) -> Result, Error> { self.eval_impl(state, out, Stack::default(), 0) } fn eval_impl( &self, state: &mut State<'_, 'env>, out: &mut Output, mut stack: Stack, mut pc: usize, ) -> Result, Error> { let initial_auto_escape = state.auto_escape; let undefined_behavior = state.undefined_behavior(); let mut auto_escape_stack = vec![]; let mut next_loop_recursion_jump = None; let mut loaded_filters = [None; MAX_LOCALS]; let mut loaded_tests = [None; MAX_LOCALS]; // If we are extending we are holding the instructions of the target parent // template here. This is used to detect multiple extends and the evaluation // uses these instructions when it makes it to the end of the instructions. #[cfg(feature = "multi_template")] let mut parent_instructions = None; macro_rules! recurse_loop { ($capture:expr) => {{ let jump_target = ctx_ok!(self.prepare_loop_recursion(state)); // the way this works is that we remember the next instruction // as loop exit jump target. Whenever a loop is pushed, it // memorizes the value in `next_loop_iteration_jump` to jump // to. next_loop_recursion_jump = Some((pc + 1, $capture)); if $capture { out.begin_capture(CaptureMode::Capture); } pc = jump_target; continue; }}; } loop { let instr = match state.instructions.get(pc) { Some(instr) => instr, #[cfg(not(feature = "multi_template"))] None => break, #[cfg(feature = "multi_template")] None => { // when an extends statement appears in a template, when we hit the // last instruction we need to check if parent instructions were // stashed away (which means we found an extends tag which invoked // `LoadBlocks`). If we do find instructions, we reset back to 0 // from the new instructions. state.instructions = match parent_instructions.take() { Some(instr) => instr, None => break, }; out.end_capture(AutoEscape::None); pc = 0; continue; } }; // if we only have two arguments that we pull from the stack, we // can assign them to a and b. This slightly reduces the amount of // code bloat generated here. Do the same for a potential error // that needs processing. let a; let b; let mut err; macro_rules! func_binop { ($method:ident) => {{ b = stack.pop(); a = stack.pop(); stack.push(ctx_ok!(ops::$method(&a, &b))); }}; } macro_rules! op_binop { ($op:tt) => {{ b = stack.pop(); a = stack.pop(); stack.push(Value::from(a $op b)); }}; } macro_rules! bail { ($err:expr) => {{ err = $err; process_err(&mut err, pc, state); return Err(err); }}; } macro_rules! ctx_ok { ($expr:expr) => { match $expr { Ok(rv) => rv, Err(err) => bail!(err), } }; } macro_rules! assert_valid { ($expr:expr) => {{ let val = $expr; if let ValueRepr::Invalid(ref err) = val.0 { bail!(Error::new(ErrorKind::BadSerialization, err.to_string())); } val }}; } // if the fuel consumption feature is enabled, track the fuel // consumption here. #[cfg(feature = "fuel")] if let Some(ref tracker) = state.fuel_tracker { ctx_ok!(tracker.track(instr)); } match instr { Instruction::EmitRaw(val) => { // this only produces a format error, no need to attach // location information. ok!(out.write_str(val).map_err(Error::from)); } Instruction::Emit => { ctx_ok!(self.env.format(&stack.pop(), state, out)); } Instruction::StoreLocal(name) => { state.ctx.store(name, stack.pop()); } Instruction::Lookup(name) => { stack.push(assert_valid!(state .lookup(name) .unwrap_or(Value::UNDEFINED))); } Instruction::GetAttr(name) => { a = stack.pop(); // This is a common enough operation that it's interesting to consider a fast // path here. This is slightly faster than the regular attr lookup because we // do not need to pass down the error object for the more common success case. // Only when we cannot look up something, we start to consider the undefined // special case. stack.push(match a.get_attr_fast(name) { Some(value) => assert_valid!(value), None => ctx_ok!(undefined_behavior.handle_undefined(a.is_undefined())), }); } Instruction::GetItem => { a = stack.pop(); b = stack.pop(); stack.push(match b.get_item_opt(&a) { Some(value) => assert_valid!(value), None => ctx_ok!(undefined_behavior.handle_undefined(b.is_undefined())), }); } Instruction::Slice => { let step = stack.pop(); let stop = stack.pop(); b = stack.pop(); a = stack.pop(); if a.is_undefined() && matches!(undefined_behavior, UndefinedBehavior::Strict) { bail!(Error::from(ErrorKind::UndefinedError)); } stack.push(ctx_ok!(ops::slice(a, b, stop, step))); } Instruction::LoadConst(value) => { stack.push(value.clone()); } Instruction::BuildMap(pair_count) => { let mut map = value_map_with_capacity(*pair_count); for _ in 0..*pair_count { let value = stack.pop(); let key = stack.pop(); map.insert(KeyRef::Value(key), value); } stack.push(Value(ValueRepr::Map(map.into(), MapType::Normal))) } Instruction::BuildKwargs(pair_count) => { let mut map = value_map_with_capacity(*pair_count); for _ in 0..*pair_count { let value = stack.pop(); let key = stack.pop(); map.insert(KeyRef::Value(key), value); } stack.push(Value(ValueRepr::Map(map.into(), MapType::Kwargs))) } Instruction::BuildList(count) => { let mut v = Vec::with_capacity(untrusted_size_hint(*count)); for _ in 0..*count { v.push(stack.pop()); } v.reverse(); stack.push(Value(ValueRepr::Seq(Arc::new(v)))); } Instruction::UnpackList(count) => { ctx_ok!(self.unpack_list(&mut stack, count)); } Instruction::ListAppend => { a = stack.pop(); // this intentionally only works with actual sequences if let ValueRepr::Seq(mut v) = stack.pop().0 { Arc::make_mut(&mut v).push(a); stack.push(Value(ValueRepr::Seq(v))) } else { bail!(Error::new( ErrorKind::InvalidOperation, "cannot append to non-list" )); } } Instruction::Add => func_binop!(add), Instruction::Sub => func_binop!(sub), Instruction::Mul => func_binop!(mul), Instruction::Div => func_binop!(div), Instruction::IntDiv => func_binop!(int_div), Instruction::Rem => func_binop!(rem), Instruction::Pow => func_binop!(pow), Instruction::Eq => op_binop!(==), Instruction::Ne => op_binop!(!=), Instruction::Gt => op_binop!(>), Instruction::Gte => op_binop!(>=), Instruction::Lt => op_binop!(<), Instruction::Lte => op_binop!(<=), Instruction::Not => { a = stack.pop(); stack.push(Value::from(!a.is_true())); } Instruction::StringConcat => { a = stack.pop(); b = stack.pop(); stack.push(ops::string_concat(b, &a)); } Instruction::In => { a = stack.pop(); b = stack.pop(); // the in-operator can fail if the value is undefined and // we are in strict mode. ctx_ok!(state.undefined_behavior().assert_iterable(&a)); stack.push(ctx_ok!(ops::contains(&a, &b))); } Instruction::Neg => { a = stack.pop(); stack.push(ctx_ok!(ops::neg(&a))); } Instruction::PushWith => { ctx_ok!(state.ctx.push_frame(Frame::default())); } Instruction::PopFrame => { if let Some(mut loop_ctx) = state.ctx.pop_frame().current_loop { if let Some((target, end_capture)) = loop_ctx.current_recursion_jump.take() { pc = target; if end_capture { stack.push(out.end_capture(state.auto_escape)); } continue; } } } #[cfg(feature = "macros")] Instruction::IsUndefined => { a = stack.pop(); stack.push(Value::from(a.is_undefined())); } Instruction::PushLoop(flags) => { a = stack.pop(); ctx_ok!(self.push_loop(state, a, *flags, pc, next_loop_recursion_jump.take())); } Instruction::Iterate(jump_target) => { let l = state.ctx.current_loop().unwrap(); l.object.idx.fetch_add(1, Ordering::Relaxed); let next = { #[cfg(feature = "adjacent_loop_items")] { let mut triple = l.object.value_triple.lock().unwrap(); triple.0 = triple.1.take(); triple.1 = triple.2.take(); triple.2 = l.iterator.next(); triple.1.clone() } #[cfg(not(feature = "adjacent_loop_items"))] { l.iterator.next() } }; match next { Some(item) => stack.push(assert_valid!(item)), None => { pc = *jump_target; continue; } }; } Instruction::PushDidNotIterate => { let l = state.ctx.current_loop().unwrap(); stack.push(Value::from(l.object.idx.load(Ordering::Relaxed) == 0)); } Instruction::Jump(jump_target) => { pc = *jump_target; continue; } Instruction::JumpIfFalse(jump_target) => { a = stack.pop(); if !a.is_true() { pc = *jump_target; continue; } } Instruction::JumpIfFalseOrPop(jump_target) => { if !stack.peek().is_true() { pc = *jump_target; continue; } else { stack.pop(); } } Instruction::JumpIfTrueOrPop(jump_target) => { if stack.peek().is_true() { pc = *jump_target; continue; } else { stack.pop(); } } #[cfg(feature = "multi_template")] Instruction::CallBlock(name) => { if parent_instructions.is_none() && !out.is_discarding() { self.call_block(name, state, out)?; } } Instruction::PushAutoEscape => { a = stack.pop(); auto_escape_stack.push(state.auto_escape); state.auto_escape = ctx_ok!(self.derive_auto_escape(a, initial_auto_escape)); } Instruction::PopAutoEscape => { state.auto_escape = auto_escape_stack.pop().unwrap(); } Instruction::BeginCapture(mode) => { out.begin_capture(*mode); } Instruction::EndCapture => { stack.push(out.end_capture(state.auto_escape)); } Instruction::ApplyFilter(name, arg_count, local_id) => { let filter = ctx_ok!(get_or_lookup_local(&mut loaded_filters, *local_id, || { state.env.get_filter(name) }) .ok_or_else(|| { Error::new( ErrorKind::UnknownFilter, format!("filter {name} is unknown"), ) })); let args = stack.slice_top(*arg_count); a = ctx_ok!(filter.apply_to(state, args)); stack.drop_top(*arg_count); stack.push(a); } Instruction::PerformTest(name, arg_count, local_id) => { let test = ctx_ok!(get_or_lookup_local(&mut loaded_tests, *local_id, || { state.env.get_test(name) }) .ok_or_else(|| { Error::new(ErrorKind::UnknownTest, format!("test {name} is unknown")) })); let args = stack.slice_top(*arg_count); let rv = ctx_ok!(test.perform(state, args)); stack.drop_top(*arg_count); stack.push(Value::from(rv)); } Instruction::CallFunction(name, arg_count) => { // super is a special function reserved for super-ing into blocks. if *name == "super" { if *arg_count != 0 { bail!(Error::new( ErrorKind::InvalidOperation, "super() takes no arguments", )); } stack.push(ctx_ok!(self.perform_super(state, out, true))); // loop is a special name which when called recurses the current loop. } else if *name == "loop" { if *arg_count != 1 { bail!(Error::new( ErrorKind::InvalidOperation, format!("loop() takes one argument, got {}", *arg_count) )); } // leave the one argument on the stack for the recursion recurse_loop!(true); } else if let Some(func) = state.lookup(name) { let args = stack.slice_top(*arg_count); a = ctx_ok!(func.call(state, args)); stack.drop_top(*arg_count); stack.push(a); } else { bail!(Error::new( ErrorKind::UnknownFunction, format!("{name} is unknown"), )); } } Instruction::CallMethod(name, arg_count) => { let args = stack.slice_top(*arg_count); a = ctx_ok!(args[0].call_method(state, name, &args[1..])); stack.drop_top(*arg_count); stack.push(a); } Instruction::CallObject(arg_count) => { let args = stack.slice_top(*arg_count); a = ctx_ok!(args[0].call(state, &args[1..])); stack.drop_top(*arg_count); stack.push(a); } Instruction::DupTop => { stack.push(stack.peek().clone()); } Instruction::DiscardTop => { stack.pop(); } Instruction::FastSuper => { ctx_ok!(self.perform_super(state, out, false)); } Instruction::FastRecurse => { recurse_loop!(false); } // Explanation on the behavior of `LoadBlocks` and rendering of // inherited templates: // // MiniJinja inherits the behavior from Jinja2 where extending // loads the blocks (`LoadBlocks`) and the rest of the template // keeps executing but with output disabled, only at the end the // parent template is then invoked. This has the effect that // you can still set variables or declare macros and that they // become visible in the blocks. // // This behavior has a few downsides. First of all what happens // in the parent template overrides what happens in the child. // For instance if you declare a macro named `foo` after `{% // extends %}` and then a variable with that named is also set // in the parent template, then you won't be able to call that // macro in the body. // // The reason for this is that blocks unlike macros do not have // closures in Jinja2/MiniJinja. // // However for the common case this is convenient because it // lets you put some imports there and for as long as you do not // create name clashes this works fine. #[cfg(feature = "multi_template")] Instruction::LoadBlocks => { a = stack.pop(); if parent_instructions.is_some() { bail!(Error::new( ErrorKind::InvalidOperation, "tried to extend a second time in a template" )); } parent_instructions = Some(ctx_ok!(self.load_blocks(a, state))); out.begin_capture(CaptureMode::Discard); } #[cfg(feature = "multi_template")] Instruction::Include(ignore_missing) => { a = stack.pop(); ctx_ok!(self.perform_include(a, state, out, *ignore_missing)); } #[cfg(feature = "multi_template")] Instruction::ExportLocals => { let locals = state.ctx.current_locals_mut(); let mut module = value_map_with_capacity(locals.len()); for (key, value) in locals.iter() { module.insert(KeyRef::Value(Value::from(*key)), value.clone()); } stack.push(Value(ValueRepr::Map(module.into(), MapType::Normal))); } #[cfg(feature = "macros")] Instruction::BuildMacro(name, offset, flags) => { self.build_macro(&mut stack, state, *offset, name, *flags); } #[cfg(feature = "macros")] Instruction::Return => break, #[cfg(feature = "macros")] Instruction::Enclose(name) => { state.ctx.enclose(state.env, name); } #[cfg(feature = "macros")] Instruction::GetClosure => { stack.push(Value::from(state.ctx.closure())); } } pc += 1; } Ok(stack.try_pop()) } #[cfg(feature = "multi_template")] fn perform_include( &self, name: Value, state: &mut State<'_, 'env>, out: &mut Output, ignore_missing: bool, ) -> Result<(), Error> { use crate::value::SeqObject; let single_name_slice = std::slice::from_ref(&name); let choices = name .as_seq() .unwrap_or(&single_name_slice as &dyn SeqObject); let mut templates_tried = vec![]; for choice in choices.iter() { let name = ok!(choice.as_str().ok_or_else(|| { Error::new( ErrorKind::InvalidOperation, "template name was not a string", ) })); let tmpl = match self.env.get_template(name) { Ok(tmpl) => tmpl, Err(err) => { if err.kind() == ErrorKind::TemplateNotFound { templates_tried.push(choice); } else { return Err(err); } continue; } }; let (new_instructions, new_blocks) = ok!(tmpl.instructions_and_blocks()); let old_escape = mem::replace(&mut state.auto_escape, tmpl.initial_auto_escape()); let old_instructions = mem::replace(&mut state.instructions, new_instructions); let old_blocks = mem::replace(&mut state.blocks, prepare_blocks(new_blocks)); let old_closure = state.ctx.take_closure(); ok!(state.ctx.incr_depth(INCLUDE_RECURSION_COST)); let rv = self.eval_state(state, out); state.ctx.reset_closure(old_closure); state.ctx.decr_depth(INCLUDE_RECURSION_COST); state.auto_escape = old_escape; state.instructions = old_instructions; state.blocks = old_blocks; ok!(rv.map_err(|err| { Error::new( ErrorKind::BadInclude, format!("error in \"{}\"", tmpl.name()), ) .with_source(err) })); return Ok(()); } if !templates_tried.is_empty() && !ignore_missing { Err(Error::new( ErrorKind::TemplateNotFound, if templates_tried.len() == 1 { format!( "tried to include non-existing template {:?}", templates_tried[0] ) } else { format!( "tried to include one of multiple templates, none of which existed {}", Value::from(templates_tried) ) }, )) } else { Ok(()) } } fn perform_super( &self, state: &mut State<'_, 'env>, out: &mut Output, capture: bool, ) -> Result { let name = ok!(state.current_block.ok_or_else(|| { Error::new(ErrorKind::InvalidOperation, "cannot super outside of block") })); let block_stack = state.blocks.get_mut(name).unwrap(); if !block_stack.push() { return Err(Error::new( ErrorKind::InvalidOperation, "no parent block exists", )); } if capture { out.begin_capture(CaptureMode::Capture); } let old_instructions = mem::replace(&mut state.instructions, block_stack.instructions()); ok!(state.ctx.push_frame(Frame::default())); let rv = self.eval_state(state, out); state.ctx.pop_frame(); state.instructions = old_instructions; state.blocks.get_mut(name).unwrap().pop(); ok!(rv.map_err(|err| { Error::new(ErrorKind::EvalBlock, "error in super block").with_source(err) })); if capture { Ok(out.end_capture(state.auto_escape)) } else { Ok(Value::UNDEFINED) } } fn prepare_loop_recursion(&self, state: &mut State) -> Result { if let Some(loop_ctx) = state.ctx.current_loop() { if let Some(recurse_jump_target) = loop_ctx.recurse_jump_target { Ok(recurse_jump_target) } else { Err(Error::new( ErrorKind::InvalidOperation, "cannot recurse outside of recursive loop", )) } } else { Err(Error::new( ErrorKind::InvalidOperation, "cannot recurse outside of loop", )) } } #[cfg(feature = "multi_template")] fn load_blocks( &self, name: Value, state: &mut State<'_, 'env>, ) -> Result<&'env Instructions<'env>, Error> { let name = match name.as_str() { Some(name) => name, None => { return Err(Error::new( ErrorKind::InvalidOperation, "template name was not a string", )) } }; if state.loaded_templates.contains(&name) { return Err(Error::new( ErrorKind::InvalidOperation, format!("cycle in template inheritance. {name:?} was referenced more than once"), )); } let tmpl = ok!(self.env.get_template(name)); let (new_instructions, new_blocks) = ok!(tmpl.instructions_and_blocks()); state.loaded_templates.insert(new_instructions.name()); for (name, instr) in new_blocks.iter() { state .blocks .entry(name) .or_insert_with(BlockStack::default) .append_instructions(instr); } Ok(new_instructions) } #[cfg(feature = "multi_template")] pub(crate) fn call_block( &self, name: &str, state: &mut State<'_, 'env>, out: &mut Output, ) -> Result, Error> { if let Some((name, block_stack)) = state.blocks.get_key_value(name) { let old_block = mem::replace(&mut state.current_block, Some(name)); let old_instructions = mem::replace(&mut state.instructions, block_stack.instructions()); state.ctx.push_frame(Frame::default())?; let rv = self.eval_state(state, out); state.ctx.pop_frame(); state.instructions = old_instructions; state.current_block = old_block; rv } else { Err(Error::new( ErrorKind::UnknownBlock, format!("block '{}' not found", name), )) } } fn derive_auto_escape( &self, value: Value, initial_auto_escape: AutoEscape, ) -> Result { match (value.as_str(), value == Value::from(true)) { (Some("html"), _) => Ok(AutoEscape::Html), #[cfg(feature = "json")] (Some("json"), _) => Ok(AutoEscape::Json), (Some("none"), _) | (None, false) => Ok(AutoEscape::None), (None, true) => Ok(if matches!(initial_auto_escape, AutoEscape::None) { AutoEscape::Html } else { initial_auto_escape }), _ => Err(Error::new( ErrorKind::InvalidOperation, "invalid value to autoescape tag", )), } } fn push_loop( &self, state: &mut State<'_, 'env>, iterable: Value, flags: u8, pc: usize, current_recursion_jump: Option<(usize, bool)>, ) -> Result<(), Error> { #[allow(unused_mut)] let mut iterator = ok!(state.undefined_behavior().try_iter(iterable)); let len = iterator.len(); let depth = state .ctx .current_loop() .filter(|x| x.recurse_jump_target.is_some()) .map_or(0, |x| x.object.depth + 1); let recursive = flags & LOOP_FLAG_RECURSIVE != 0; let with_loop_var = flags & LOOP_FLAG_WITH_LOOP_VAR != 0; ok!(state.ctx.push_frame(Frame { current_loop: Some(LoopState { with_loop_var, recurse_jump_target: if recursive { Some(pc) } else { None }, current_recursion_jump, object: Arc::new(Loop { idx: AtomicUsize::new(!0usize), len, depth, #[cfg(feature = "adjacent_loop_items")] value_triple: Mutex::new((None, None, iterator.next())), last_changed_value: Mutex::default(), }), iterator, }), ..Frame::default() })); Ok(()) } fn unpack_list(&self, stack: &mut Stack, count: &usize) -> Result<(), Error> { let top = stack.pop(); let seq = ok!(top .as_seq() .ok_or_else(|| Error::new(ErrorKind::CannotUnpack, "not a sequence"))); if seq.item_count() != *count { return Err(Error::new( ErrorKind::CannotUnpack, format!( "sequence of wrong length (expected {}, got {})", *count, seq.item_count() ), )); } for item in seq.iter().rev() { stack.push(item); } Ok(()) } #[cfg(feature = "macros")] fn build_macro( &self, stack: &mut Stack, state: &mut State, offset: usize, name: &str, flags: u8, ) { use crate::compiler::instructions::MACRO_CALLER; let arg_spec = match stack.pop().0 { ValueRepr::Seq(args) => args .iter() .map(|value| match &value.0 { ValueRepr::String(arg, _) => arg.clone(), _ => unreachable!(), }) .collect(), _ => unreachable!(), }; let closure = stack.pop(); let macro_ref_id = state.macros.len(); Arc::make_mut(&mut state.macros).push((state.instructions, offset)); stack.push(Value::from_object(Macro { data: Arc::new(MacroData { name: Arc::from(name.to_string()), arg_spec, macro_ref_id, closure, caller_reference: (flags & MACRO_CALLER) != 0, }), })); } } #[inline(never)] #[cold] fn process_err(err: &mut Error, pc: usize, state: &State) { // only attach line information if the error does not have line info yet. if err.line().is_none() { if let Some(span) = state.instructions.get_span(pc) { err.set_filename_and_span(state.instructions.name(), span); } else if let Some(lineno) = state.instructions.get_line(pc) { err.set_filename_and_line(state.instructions.name(), lineno); } } // only attach debug info if we don't have one yet and we are in debug mode. #[cfg(feature = "debug")] { if state.env.debug() && err.debug_info().is_none() { err.attach_debug_info(state.make_debug_info(pc, state.instructions)); } } } minijinja-1.0.3/src/vm/state.rs000064400000000000000000000273461046102023000145100ustar 00000000000000use std::collections::{BTreeMap, BTreeSet}; use std::fmt; use crate::compiler::instructions::Instructions; use crate::environment::Environment; use crate::error::{Error, ErrorKind}; use crate::output::Output; use crate::utils::{AutoEscape, UndefinedBehavior}; use crate::value::{ArgType, Value}; use crate::vm::context::Context; #[cfg(feature = "fuel")] use crate::vm::fuel::FuelTracker; /// Provides access to the current execution state of the engine. /// /// A read only reference is passed to filter functions and similar objects to /// allow limited interfacing with the engine. The state is useful to look up /// information about the engine in filter, test or global functions. It not /// only provides access to the template environment but also the context /// variables of the engine, the current auto escaping behavior as well as the /// auto escape flag. /// /// In some testing scenarios or more advanced use cases you might need to get /// a [`State`]. The state is managed as part of the template execution but the /// initial state can be retrieved via [`Template::new_state`](crate::Template::new_state). /// The most common way to get hold of the state however is via functions of filters. /// /// **Notes on lifetimes:** the state object exposes some of the internal /// lifetimes through the type. You should always elide these lifetimes /// as there might be lifetimes added or removed between releases. pub struct State<'template, 'env> { pub(crate) env: &'env Environment<'env>, pub(crate) ctx: Context<'env>, pub(crate) current_block: Option<&'env str>, pub(crate) auto_escape: AutoEscape, pub(crate) instructions: &'template Instructions<'env>, pub(crate) blocks: BTreeMap<&'env str, BlockStack<'template, 'env>>, #[allow(unused)] pub(crate) loaded_templates: BTreeSet<&'env str>, #[cfg(feature = "macros")] pub(crate) macros: std::sync::Arc, usize)>>, #[cfg(feature = "fuel")] pub(crate) fuel_tracker: Option>, } impl<'template, 'env> fmt::Debug for State<'template, 'env> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ds = f.debug_struct("State"); ds.field("name", &self.instructions.name()); ds.field("current_block", &self.current_block); ds.field("auto_escape", &self.auto_escape); ds.field("ctx", &self.ctx); ds.field("env", &self.env); ds.finish() } } impl<'template, 'env> State<'template, 'env> { /// Creates a new state. pub(crate) fn new( env: &'env Environment, ctx: Context<'env>, auto_escape: AutoEscape, instructions: &'template Instructions<'env>, blocks: BTreeMap<&'env str, BlockStack<'template, 'env>>, ) -> State<'template, 'env> { State { env, ctx, current_block: None, auto_escape, instructions, blocks, loaded_templates: BTreeSet::new(), #[cfg(feature = "macros")] macros: Default::default(), #[cfg(feature = "fuel")] fuel_tracker: env.fuel().map(FuelTracker::new), } } /// Creates an empty state for an environment. pub(crate) fn new_for_env(env: &'env Environment) -> State<'env, 'env> { State::new( env, Context::default(), AutoEscape::None, &crate::compiler::instructions::EMPTY_INSTRUCTIONS, BTreeMap::new(), ) } /// Returns a reference to the current environment. #[inline(always)] pub fn env(&self) -> &Environment<'_> { self.env } /// Returns the name of the current template. pub fn name(&self) -> &str { self.instructions.name() } /// Returns the current value of the auto escape flag. #[inline(always)] pub fn auto_escape(&self) -> AutoEscape { self.auto_escape } /// Returns the current undefined behavior. #[inline(always)] pub fn undefined_behavior(&self) -> UndefinedBehavior { self.env.undefined_behavior() } /// Returns the name of the innermost block. #[inline(always)] pub fn current_block(&self) -> Option<&str> { self.current_block } /// Looks up a variable by name in the context. #[inline(always)] pub fn lookup(&self, name: &str) -> Option { self.ctx.load(self.env, name) } /// Looks up a global macro and calls it. /// /// This looks up a value as [`lookup`](Self::lookup) does and calls it /// with the passed args. #[cfg(feature = "macros")] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] pub fn call_macro(&self, name: &str, args: &[Value]) -> Result { let f = ok!(self.lookup(name).ok_or_else(|| Error::new( crate::error::ErrorKind::UnknownFunction, "macro not found" ))); f.call(self, args).map(Into::into) } /// Renders a block with the given name into a string. /// /// This method works like [`Template::render`](crate::Template::render) but /// it only renders a specific block in the template. The first argument is /// the name of the block. /// /// This renders only the block `hi` in the template: /// /// ``` /// # use minijinja::{Environment, context}; /// # fn test() -> Result<(), minijinja::Error> { /// # let mut env = Environment::new(); /// # env.add_template("hello", "{% block hi %}Hello {{ name }}!{% endblock %}")?; /// let tmpl = env.get_template("hello")?; /// let rv = tmpl /// .eval_to_state(context!(name => "John"))? /// .render_block("hi")?; /// println!("{}", rv); /// # Ok(()) } /// ``` /// /// Note that rendering a block is a stateful operation. If an error /// is returned the module has to be re-created as the internal state /// can end up corrupted. This also means you can only render blocks /// if you have a mutable reference to the state which is not possible /// from within filters or similar. #[cfg(feature = "multi_template")] #[cfg_attr(docsrs, doc(cfg(feature = "multi_template")))] pub fn render_block(&mut self, block: &str) -> Result { let mut buf = String::new(); crate::vm::Vm::new(self.env) .call_block(block, self, &mut Output::with_string(&mut buf)) .map(|_| buf) } /// Renders a block with the given name into an [`io::Write`](std::io::Write). /// /// For details see [`render_block`](Self::render_block). #[cfg(feature = "multi_template")] #[cfg_attr(docsrs, doc(cfg(feature = "multi_template")))] pub fn render_block_to_write(&mut self, block: &str, w: W) -> Result<(), Error> where W: std::io::Write, { let mut wrapper = crate::output::WriteWrapper { w, err: None }; crate::vm::Vm::new(self.env) .call_block(block, self, &mut Output::with_write(&mut wrapper)) .map(|_| ()) .map_err(|err| wrapper.take_err(err)) } /// Returns a list of the names of all exports (top-level variables). pub fn exports(&self) -> Vec<&str> { self.ctx.exports().keys().copied().collect() } /// Invokes a filter with some arguments. /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// # let tmpl = env.template_from_str("").unwrap(); /// # let state = tmpl.new_state(); /// let rv = state.apply_filter("upper", &["hello world".into()]).unwrap(); /// assert_eq!(rv.as_str(), Some("HELLO WORLD")); /// ``` pub fn apply_filter(&self, filter: &str, args: &[Value]) -> Result { match self.env.get_filter(filter) { Some(filter) => filter.apply_to(self, args), None => Err(Error::from(ErrorKind::UnknownFilter)), } } /// Invokes a test function on a value. /// /// ``` /// # use minijinja::Environment; /// # let mut env = Environment::new(); /// # let tmpl = env.template_from_str("").unwrap(); /// # let state = tmpl.new_state(); /// let rv = state.perform_test("even", &[42i32.into()]).unwrap(); /// assert!(rv); /// ``` pub fn perform_test(&self, test: &str, args: &[Value]) -> Result { match self.env.get_test(test) { Some(test) => test.perform(self, args), None => Err(Error::from(ErrorKind::UnknownTest)), } } /// Formats a value to a string using the formatter on the environment. /// /// ``` /// # use minijinja::{value::Value, Environment}; /// # let mut env = Environment::new(); /// # let tmpl = env.template_from_str("").unwrap(); /// # let state = tmpl.new_state(); /// let rv = state.format(Value::from(42)).unwrap(); /// assert_eq!(rv, "42"); /// ``` pub fn format(&self, value: Value) -> Result { let mut rv = String::new(); let mut out = Output::with_string(&mut rv); self.env.format(&value, self, &mut out).map(|_| rv) } /// Returns the fuel levels. /// /// When the fuel feature is enabled, during evaluation the template will keep /// track of how much fuel it has consumed. If the fuel tracker is turned on /// the returned value will be `Some((consumed, remaining))`. If fuel tracking /// is not enabled, `None` is returned instead. #[cfg(feature = "fuel")] #[cfg_attr(docsrs, doc(cfg(feature = "fuel")))] pub fn fuel_levels(&self) -> Option<(u64, u64)> { self.fuel_tracker .as_ref() .map(|x| (x.consumed(), x.remaining())) } #[cfg(feature = "debug")] pub(crate) fn make_debug_info( &self, pc: usize, instructions: &Instructions<'_>, ) -> crate::debug::DebugInfo { crate::debug::DebugInfo { template_source: Some(instructions.source().to_string()), referenced_locals: instructions .get_referenced_names(pc) .into_iter() .filter_map(|n| Some((n.to_string(), some!(self.lookup(n))))) .collect(), } } } impl<'a> ArgType<'a> for &State<'_, '_> { type Output = &'a State<'a, 'a>; fn from_value(_value: Option<&'a Value>) -> Result { Err(Error::new( ErrorKind::InvalidOperation, "cannot use state type in this position", )) } fn from_state_and_value( state: Option<&'a State>, _value: Option<&'a Value>, ) -> Result<(Self::Output, usize), Error> { match state { None => Err(Error::new(ErrorKind::InvalidOperation, "state unavailable")), Some(state) => Ok((state, 0)), } } } /// Tracks a block and it's parents for super. #[derive(Default)] pub(crate) struct BlockStack<'template, 'env> { instructions: Vec<&'template Instructions<'env>>, depth: usize, } impl<'template, 'env> BlockStack<'template, 'env> { pub fn new(instructions: &'template Instructions<'env>) -> BlockStack<'template, 'env> { BlockStack { instructions: vec![instructions], depth: 0, } } pub fn instructions(&self) -> &'template Instructions<'env> { self.instructions.get(self.depth).copied().unwrap() } pub fn push(&mut self) -> bool { if self.depth + 1 < self.instructions.len() { self.depth += 1; true } else { false } } #[track_caller] pub fn pop(&mut self) { self.depth = self.depth.checked_sub(1).unwrap() } #[cfg(feature = "multi_template")] pub fn append_instructions(&mut self, instructions: &'template Instructions<'env>) { self.instructions.push(instructions); } }