MiniJinja is a powerful but minimal dependency template engine for Rust

Overview

MiniJinja: a powerful template engine for Rust with minimal dependencies

Build Status License Crates.io Documentation

MiniJinja is a powerful but minimal dependency template engine for Rust which is based on the syntax and behavior of the Jinja2 template engine for Python.

It's implemented on top of serde and only has it as a single dependency. It supports a range of features from Jinja2 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.

Example

use minijinja::Environment;
use serde::Serialize;

#[derive(Serialize)]
pub struct Context {
    name: String,
}

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".into()
    }).unwrap());
}

License and Links

Comments
  • Cow based interface for StructObject::fields

    Cow based interface for StructObject::fields

    The current fields interface only allows borrowing a str out of self. While attempting to make a safe interface for stack borrowing I realized that there are cases where you really need to create an owned string there (#156).

    There are two options here:

    • fn fields(&self) -> Box<dyn Iterator<Item = Cow<'_, str>> + '_> — which has the advantage that you can borrow in a lot of cases, but it means that code which cannot hold on to the lifetime needs to allocate a string in all cases
    • fn fields(&self) -> Box<dyn Iterator<Item = Cow<'static, str>> + '_> — would require more dynamic allocations in case the field name is not fully known, but has the advantage that we could internally start having a ValueRepr::StaticStr which might avoid unnecessary allocations in a lot of cases.
    opened by mitsuhiko 11
  • Implemented Revamped Object Model

    Implemented Revamped Object Model

    This changes the object interface to support both structs and sequences with the possibility to add extra behavior (eg: arbitrary key maps) at a future point. There are some challenges with this design however.

    Issues:

    These are probably something I need to accept:

    • impl<'a, T: ArgType<'a, Output = T>> ArgType<'a> for Vec<T> cannot be implemented to support these new dynamic sequences because the lifetime constraints cannot be enabled. Restrict?

    Things to resolve:

    This is stuff that can be fixed:

    • [x] Similarly the slice syntax x[:5] first converts the entire value before slicing down.
    • [x] Decide on the names.
    • [x] The Value::as_slice API cannot return borrowed values for dynamic slices. Internally a as_cow_slice has been added. Potential solution is to retire as_slice entirely for SeqObject.
    • [x] Iteration over dynamic slices first makes a real slice out of it. This is mostly done because of lifetime issues that are annoying to work around.
    • [x] Consider adding SeqObject::iter

    Refs #147

    opened by mitsuhiko 10
  • Suggestion: support {% macro %}

    Suggestion: support {% macro %}

    My main use case for this is displaying recursive data. I think currently the only way to display recursive data with minijinja is by flattening it in rust code. Recursive include doesn't work either.

    Would this feature still be in scope of minijinja? I'm not sure if the mini is because it doesn't support all features of jinja or because it doesn't plan to.

    opened by emi2k01 9
  • Resolving include files

    Resolving include files

    Would it be possible to add some sort of hook to allow my code to resolve the include files when they are needed, instead of adding them in ahead of time?

    Right now, as far as I can tell, I have to know the names of all the files that are needed before-hand and add them to the environment, which requires my users to have to keep track of these and pass a list to me. I do see the Source struct which allows loading all templates in a path, but then the code would load all sorts of files that it doesn't need to load.

    opened by user4815162342 8
  • Support for Dict Methods (keys, values, items)

    Support for Dict Methods (keys, values, items)

    I am using this in a small tool to render configuration files, and want to be able to more easily translate existing knowledge of Jinja2 from Python to minijinja.

    I am looking to do something like, which does not work:

    {% for (k, v) in env.items() %}
    {{ k }}: {{ v }}
    {%- endfor %}
    
    Error { kind: ImpossibleOperation, detail: Some("object has no method named items")
    

    This does work:

    {% for k in env %}
    {{ k }}: {{ env[k] }}
    {%- endfor %}
    

    So I have a work-around for now.

    I did see this closed issue: https://github.com/mitsuhiko/minijinja/issues/9 however there is no items filter.

    Is there a way to add some of the more useful functions, such as keys(), items(), or values() on maps?

    opened by bertjwregeer 8
  • Add indent filter

    Add indent filter

    I missed a feature where i can ident a multiline Value. This PR should fix it.

    Please be patient with me, this is my second PR and I'm new to Rust. I will do my best if something is missing.

    opened by Spiegie 7
  • Breaking change of API between 1.19.1 and 1.20

    Breaking change of API between 1.19.1 and 1.20

    Description

    The 'filters::Filter' changed its API between these versions, you should increment the mayor.

    Reproduction steps

    This code works in 1.19.1

    struct Test {}
    
    impl minijinja::filters::Filter<String, String, ()> for Test
    {
        fn apply_to(&self, _state: &minijinja::State, _value: String, _args: ()) -> Result<String, minijinja::Error>
        {
            Ok("".to_string())
        }
    }
    

    But fails in 1.20

    error[E0053]: method `apply_to` has an incompatible type for trait
      --> src/filters.rs:24:81
       |
    24 |     fn apply_to(&self, _state: &minijinja::State, _value: String, _args: ()) -> Result<String, minijinja::Error>
       |                                                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |                                                                                 |
       |                                                                                 expected struct `std::string::String`, found enum `Result`
       |                                                                                 help: change the output type to match the trait: `std::string::String`
       |
       = note: expected fn pointer `fn(&filters::Test, &minijinja::State<'_, '_>, std::string::String, ()) -> std::string::String`
                  found fn pointer `fn(&filters::Test, &minijinja::State<'_, '_>, std::string::String, ()) -> Result<std::string::String, minijinja::Error>`
    
    error: aborting due to 2 previous errors
    

    Additional helpful information:

    • Version of rustc: 1.63.0 (4b91a6ea7 2022-08-08)
    • Operating system and version: Ubuntu 20.04

    What did you expect

    Should work because the mayor is the same (SemVer).

    opened by chilabot 7
  • Consider supporting Expression Statements

    Consider supporting Expression Statements

    In Jinja, there is an extension called "expression statements":

    https://jinja.palletsprojects.com/en/3.1.x/extensions/#expression-statement

    The “do” aka expression-statement extension adds a simple do tag to the template engine that works like a variable expression but ignores the return value.
    

    These are super handy, you can do things like this:

    https://jinja.palletsprojects.com/en/3.1.x/templates/#expression-statement

    Thoughts? (Happy to submit this)

    opened by joshuataylor 7
  • mutable filter state

    mutable filter state

    I have a use-case where I'd like to keep track of a limited amount of global state across invocations of the same filter, for example, keeping track of the number of bound parameters to a sql query. Currently we can't get a mutable handle to the env, only &Environment. Initially I thought I could do this by passing a reference to a HashMap or some other shared data structure around into the filter, but it seems like the filter functions need to remain pure due to the automatic type conversion that is happening (which is great, btw).

    jinjasql, the library which I'm trying to emulate handles this via thread local variables, is there an idiomatic way to share filter state across invocations?

    enhancement 
    opened by wseaton 7
  • `Object` but for sequences

    `Object` but for sequences

    Using Object cut down the running time of my application by 25% by allowing me to remove clones in favor of using proxy objects. From profiling, I can see that a similar possibility for optimization exists for sequences.

    At present, if I have an existing sequence of non-minijinja-values, I have no recourse but to clone the entire sequence, converting each value into a native minijinja::Value in the process:

    array.iter()
        .map(value_to_minijinja_value)
        .collect()
    

    This is expensive, especially when the sequence is large. If instead there was a trait like Object for sequences, I could wrap the array in a proxy object like I do maps and save the cost:

    Value::from_sequence_object(ProxySequence(array))
    

    One option is to introduce another trait and ValueRepr kind, say Sequence, which looks a bit like Object:

    pub trait Sequence: Display + Debug + Any + Sync + Send {
        fn get(&self, index: usize) -> Option<Value> { ... }
    
        /// This is a range so that slices are cheaply representable.
        fn range(&self) -> Range<usize> { ... }
    }
    

    Alternatively, Object can be generalized to be useful for sequence-like objects as well. One approach for this option is to add the same get() and range() methods above to Object. Another is to make the existing get_attr() and attributes() methods more versatile, allowing the former to take a usize and the latter to emit a range().

    opened by SergioBenitez 6
  • Macro in child template isn't found inside of `block`

    Macro in child template isn't found inside of `block`

    Consider the following (also here):

    {% extends template %}
    {% macro foo() %}inside foo{% endmacro %}
    {% block title %}{{ foo() }}{% endblock %}
    {% block body %}new body{% endblock %}
    

    This gives the following error:

    Error {
        kind: UnknownFunction,
        detail: "foo is unknown",
        name: "macro-extends.txt",
        line: 3,
    }
    
    unknown function: foo is unknown (in macro-extends.txt:3)
    ------------------------------ macro-extends.txt ------------------------------
       1 | {% extends template %}
       2 | {% macro foo() %}inside foo{% endmacro %}
       3 > {% block title %}{{ foo() }}{% endblock %}
         i                     ^^^^^ unknown function
       4 | {% block body %}new body{% endblock %}
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    No referenced variables
    -------------------------------------------------------------------------------
    

    Removing the extends results in the template compiling without error.

    I considered whether this was intended behavior, but the syntax docs make no mention of blocks having some newly defined scope. And if they do, then how would I go about invoking foo(), defined in a child template, inside of a block in said child?

    bug 
    opened by SergioBenitez 5
  • Lazy Objects and Lookups

    Lazy Objects and Lookups

    Isolated from #157:

    In the latter case, I have imagined an API by which the engine tracks object path accesses at compile-time and then asks once (say via an Object trait) for a terminal value that isn't compound, so anything but a map or sequence. For example, for foo.bar[0].baz, the engine would ask the foo object (assuming it is one) for bar[0].baz. The object can then perform an efficient lookup without any clones and return the terminal baz. Similarly, if we have:

    {% set bar = foo.bar[0] %}
    {{ bar.baz }}
    

    The engine would again ask foo for bar[0].baz. This of course requires some thought and implementation grease, but, aside from allowing full borrows of anything, seems like the most sensible approach.

    opened by mitsuhiko 9
  • Remove 'source lifetime

    Remove 'source lifetime

    Today the engine holds &'source str all over the place in the instructions and by extension in the templates. This is a design pillar that I originally inherited from the initial API that was inspired by tinytemplate. It's a pretty clever setup but it has the disadvantage that the API cannot deal with dynamic templates well.

    The solution to this problem so far has been the source feature which introduces a Source type which lets the engine lie about lifetimes through the use of self-cell and memo-map.

    There is a alternative that could be evaluated where these borrows to the source string are not taking place through the references but string handles. That would likely require significant changes in the lexer and potentially the parser as well, but the changes to the instructions are probably quite limited.

    The way this could work is that the instructions that currently hold an &'source str would instead hold something like a StrHandle which looks something like this:

    struct StrHandle {
        offset: u32,
        len: u32,
        #[cfg(debug_assertions)]
        instructions_id: u64,
    }
    
    impl StrHandle {
        pub fn as_str(&self, instr: &Instructions) -> &str {
            debug_assert_eq!(instr.id, self.instructions_id);
            &instr.source[self.offset as usize..self.offset as usize + self.len as usize]
        }
    }
    

    Obviously one complexity with this approach is that at no point the handles must be separated from the instructions they are contained in. It also comes at least through the safe interface with additional cost as Rust will require a lot more bounds checks. unsafe would work here, but actually making a safe interface where someone does not change the source string underneath the instructions accidentally could be quite tricky.

    The benefit of doing this would be that the engine ends up with less dependencies, overall less unsafe code and a nicer API.

    enhancement 
    opened by mitsuhiko 3
  • Add Support for Valuable

    Add Support for Valuable

    There is quite a bit of ecosystem being built on top of valuable nowadays thanks to the tracing ecosystem. It might be worth exploring as an alternative/addition to serde.

    enhancement 
    opened by mitsuhiko 0
  • Support using custom delimiters

    Support using custom delimiters

    Hi ! First of all thanks for this awesome lib !

    Do you have any plans on implementing a way to provide custom delimiters ? Either at compile time or at runtime.

    This feature does not exist in Tera, and Askama’s approach makes it impossi ble to have runtime provided templates. I am writing a CLI to parse and render jinja2 templates using [[ … ]], [% … %] and [# … #] to avoid conflicting with other templating languages used in the files I am processing.

    Thanks in advance !

    opened by NikolaLohinski 4
  • Consider implementing loop.previtem and loop.nextitem

    Consider implementing loop.previtem and loop.nextitem

    These two special loop variables are currently not supported but both very useful. loop.previtem points to the last value of the iteration, loop.nextitem to the next one.

    enhancement 
    opened by mitsuhiko 1
Owner
Armin Ronacher
Software developer and Open Source nut. Creator of the Flask framework. Engineering at @getsentry. Other things of interest: @pallets and @rust-lang
Armin Ronacher
A template engine for Rust based on Jinja2/Django

Tera Tera is a template engine inspired by Jinja2 and the Django template language. <title>{% block title %}{% endblock title %}</title> <ul> {% for u

Vincent Prouillet 2.5k Jan 1, 2023
A flexible template engine for Rust

Rustache Rustache is a Rust implementation of the Mustache spec. Documentation The different Mustache tags are documented at the mustache(5) man page.

rustache 208 May 10, 2022
finch - a super fast and efficient template rendering engine for node.js

finch A super fast and efficient template rendering engine for node.js, inspired by Handlebars. Usage Finch is very simple to use: Register a template

null 1 Nov 2, 2021
Balsa is a delightfully simple HTML template engine

?? balsa Balsa is a delightfully simple HTML template engine. It is designed to be used in user interfaces such as a CMS where a user needs to be able

Tyler Lafayette 1 Jan 19, 2022
A minimalist Rust WebAssembly project template

MiniWASM - A minimalist Rust WebAssembly project template This is a minimal Rust-powered WebAssembly application template. It was designed to showcase

Emil Loer 160 Jul 26, 2022
A template for a Rust-powered static-page Try Online interface

rust-tio-template A template for a Rust-powered static-page Try Online interface What is included This is an example setup that enables all of the fol

null 2 Dec 13, 2021
A "Hello, world!" template of a Rust binary crate for the ESP-IDF framework.

Rust on ESP-IDF "Hello, World" template A "Hello, world!" template of a Rust binary crate for the ESP-IDF framework. This is the crate you get when ru

Ivan Markov 140 Jan 4, 2023
A template for creating services in Rust using Axum and Prisma.

A template for creating services in Rust using Axum and Prisma. This uses the super cool Prisma Rust Client.

Aaron Leopold 6 Oct 19, 2022
✨ A perfect template for a binary rust project.

Rust Template A project template for Rust, helping to structure your projects blazingly fast ⚡ . Features ?? Code-ready for binary projects. Add amazi

bwtecode 3 Aug 21, 2022
Compiler for Jade-like template language to cito.js-based virtual dom

Marafet A very experimental DSL for creating (mostly) single page applications in HTML. It's mostly a Jade-like (or Haml-like) templating language wit

Paul Colomiets 11 Jun 25, 2020
A nice template for NEAR repos

Template for a NEAR project If you're looking for a no-std version of this template, go here. Contains: a setup script Cargo.toml setup with simulatio

Thor 12 Dec 28, 2021
Yew Tauri Desktop App Template

Yew Tauri Desktop App Template This is Template App for Desktop App. Run Develop $ cargo tauri dev Build App $ cargo tauri build Setup yew setup Using

usa 16 Oct 16, 2022
Template for Cargo based SysY compiler projects.

基于 Cargo 的 SysY 编译器项目模板 该仓库中存放了一个基于 Cargo 的 SysY 编译器项目的模板, 你可以在该模板的基础上进行进一步的开发. 该仓库中的 Rust 代码实现仅作为演示, 不代表你的编译器必须以此方式实现. 如你需要使用该模板, 建议你删掉所有 Rust 源文件, 仅

PKU Compiler Course 1 Nov 1, 2021
Bevy GitHub CI Template

Bevy GitHub CI Template This repo show how to setup CI on a github project for Bevy. It creates two workflows: CI Release CI Definition: .github/workf

MiniaczQ 2 Feb 7, 2022
This is a template repo for eframe, a framework for writing apps using egui.

eframe template This is a template repo for eframe, a framework for writing apps using egui. The goal is for this to be the simplest way to get starte

Viet Dinh 0 Feb 5, 2022
Rust templating with Handlebars

handlebars-rust Handlebars templating language implemented in Rust and for Rust. Handlebars-rust is the template engine that renders the official Rust

Ning Sun 922 Dec 27, 2022
:pencil: Compile-time HTML templates for Rust

maud Documentation (source) • API reference • Change log Maud is an HTML template engine for Rust. It's implemented as a macro, html!, which compiles

Chris Wong 1.4k Jan 1, 2023
A macro-based html builder for rust

Horrorshow A macro-based html templating library, compatible with stable rust (currently requires rust >= 1.37). Features This crate will degrade grac

Steven Allen 267 Dec 11, 2022
Rust Compiled Templates with static-file handling

Rust Compiled Templates — ructe This is my attempt at writing a HTML template system for Rust. Some inspiration comes from the scala template system u

Rasmus Kaj 337 Jan 8, 2023