Coder Social home page Coder Social logo

mitsuhiko / minijinja Goto Github PK

View Code? Open in Web Editor NEW
1.3K 12.0 77.0 2.71 MB

MiniJinja is a powerful but minimal dependency template engine for Rust compatible with Jinja/Jinja2

Home Page: https://docs.rs/minijinja/

License: Apache License 2.0

Rust 96.68% HTML 0.62% Makefile 0.47% Shell 0.15% Python 2.06% Jinja 0.01% Qt Script 0.01%
jinja2 templates rust jinja

minijinja's Introduction

MiniJinja: a powerful template engine for Rust with minimal dependencies

Build Status License Crates.io rustc 1.61.0 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 required 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.

$ cargo tree
minimal v0.1.0 (examples/minimal)
└── minijinja v2.0.0-alpha.0 (minijinja)
    └── serde v1.0.144

You can play with MiniJinja online in the browser playground powered by a WASM build of MiniJinja.

Goals:

Example Template

{% extends "layout.html" %}
{% block body %}
  <p>Hello {{ name }}!</p>
{% endblock %}

API

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.

Related Crates

Similar Projects

These are related template engines for Rust:

  • Askama: Jinja inspired, type-safe, requires template precompilation. Has significant divergence from Jinja syntax in parts.
  • Tera: Jinja inspired, dynamic, has divergences from Jinja.
  • TinyTemplate: minimal footprint template engine with syntax that takes lose inspiration from Jinja and handlebars.
  • 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.

License and Links

minijinja's People

Contributors

cclauss avatar digitalresistor avatar douganderson444 avatar epilys avatar folyd avatar guillaumegomez avatar hnakamur avatar inducer avatar jankatins avatar jcgruenhage avatar joshuataylor avatar jplatte avatar keirua avatar lucky avatar malyn avatar messense avatar mitsuhiko avatar morenol avatar mystborn avatar nokome avatar rdbo avatar remram44 avatar robjtede avatar sergiobenitez avatar striezel avatar turbo87 avatar voultapher avatar waywardmonkeys avatar wrapperup avatar youguanxinqing avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

minijinja's Issues

Add If Clauses for For Loops

Currently the engine does not yet support if clauses for {% for %} loops. I find them quite useful in Jinja2 as they make the special loop variable respect them.

Example:

{% for user in users if user.is_active %}
   ...
{% endfor %}

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.

Support `{% set %}` statements

Allowing set statements like jinja would help clean up some of the templates I have (e.g., conditionally building up a string which is used multiple times later in the template).

with statements can mostly be used as a workaround, however I've found this can get quite verbose in some cases.

Add Tuple Syntax and Tuple Unpacking

It's currently not possible to either construct tuples (foo, bar) or to unpack into tuples in loops. The latter is also the reason there is currently no |items filter as something like {% for key, value in foo|items %} cannot be expressed.

Python binding?

Given that Rust implementation should be faster, would it be possible to have a Python binding for the Rust library?

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)

Validate Safety of minijinja-stack-ref

Now that #148 landed the practical limitations (other than excessive uses of Arc all the way down) are really lifetimes. I tried to use the functionality and the main way to find this enjoyable really requires borrowing from data that is already there (eg: on the stack).

I created an API around this which hopefully provides a safe interface to do so in minijinja-stack-ref but it's unclear if this is safe. So I need to check the create for that.

Motivating example

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: #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?

Add ternary if Expression

In Jinja2 it is possible to write things like {{ foo if expr else other }}. This is a useful feature that is currently not available in MIniJinja.

Add "in" Operator

It is currently not possible to do {{ foo in bar }} to check if foo is contained in the sequence/map bar. This is a relatively commonly used feature in Jinja2 to avoid writing complex or-expressions.

Jinja compability: Functions with a trailing comma

Description

The following should be supported 😢 😬 :

        {{basic_function('hello', 'world') }}
        {{basic_function('hello',
        'world') }}
        {{basic_function(
        'hello',
        'world') }}
        {{basic_function('hello',  'world',) }}
        {{basic_function('hello', 'world') }}
        {{basic_function('hello',
        'world') }}
        {{basic_function(
        'hello',
        'world') }}
        {{basic_function('hello',  'world',) }}"#,

Test Replication:

fn basic_function(_state: &State, arg_one: String, arg_two: String) -> Result<Value, Error> {
    Ok(Value::from(format!("{} {}", arg_one, arg_two)))
}

#[test]
fn test_functions() {
    let mut env = Environment::new();
    env.add_function("basic_function", basic_function);
    env.add_template(
        "demo.html",
        r#"
        {{basic_function('hello', 'world') }}
        {{basic_function('hello',
        'world') }}
        {{basic_function(
        'hello',
        'world') }}
        {{basic_function('hello',  'world',) }}"#,
    )
        .unwrap();

    let tmpl = env.get_template("demo.html").unwrap();
    insta::assert_debug_snapshot!(tmpl.render(&()).unwrap());
}

Right now it throws this:

thread 'test_functions' panicked at 'called `Result::unwrap()` on an `Err` value: Error { kind: SyntaxError, detail: Some("unexpected `)`"), name: Some("demo.html"), lineno: 8, source: None }

Not sure if it should be supported for backwards compatibility or not..

`and`, `or`, `not`, `is`, and `in` are not reserved words.

Description

It seems to be pretty trivial to trick the parser into mistaking keywords for function names.

Reproduction steps

  1. Test string: r#" '{{ a }}' == 'a' and ( '{{ a }}' != 'b' or '{{ a }}' != 'c' ) "#
  2. Basic context!( a => "a" ) and eval with it.
  3. Get the error Error { kind: ImpossibleOperation, details: Some("unknown function and"), name: Some("<expression>"), lineno: 1, source: None }

Give it some context!( a => "a" ) and eval it.

Additional helpful information:

  • Version of minijinja: 0.71.0
  • Version of rustic: 1.62.0
  • Operating system and version: Windows 10

What did you expect

and should be treated like a keyword, per the docs so the parser should understand that it is not just a normal identifier, and shouldn't first identify it as an ident, then try to convert it into a function.

There likely should be different Infix/Suffix/Prefix handling within the parser logic. I also see the project is implementing its own LRParser, I would recommend => lrpar project as it fully re-implements yacc in rust.

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?

support using anyhow::Error

The Error type in minijinja is !Send and !Sync and that makes needlessly annoying to use it with anyhow. Is it possible to bound source to be Send and Sync?

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?

Make &state to filters optional

Entirely unclear how this can be done without GATs at moment or awful hacks I don't feel confident about. In theory something like this would work:

pub trait FunctionArgs<'a>: Sized {
    /// Converts to function arguments from a slice of values.
    fn from_values(state: &'a State<'a, 'a>, values: &'a [Value]) -> Result<Self, Error>;
}

And then implement FunctionArgs for (A, B, C, D) as well as (&State, A, B, C, D). That trait can actually be implemented today with Rust, but the challenge comes when implementing the Filter trait. There GATs are required to express the borrow for &State (See also #97 which runs against the same limitation).

Remove meta API

The meta API from #55 is quite useless in practice and I think the original feature request is better addressed with the new source support anyways.

Add dict invocation syntax

It would be nice if foo(a=1, b=2) was an alias to foo({"a": 1, "b": 2}). That would align the behavior largely with what exists in Python in terms of what can be expressed.

Change filters/functions/tests to borrow arguments

It's currently quite wasteful that a filter like lower always needs to convert into a String first. It would be a better interface if &Value or similar is passed, then a borrow to a &str out of that should be possible for the interface.

Ability to add filters, functions, and tests with dynamic names

At present, the Environment::{add_filter, add_function, add_test} methods make it impossible to add filter/function/tests with dynamic (String) names as they expect an &'source str. In my application, filters, functions, and tests are loaded dynamically, so I cannot possibly generate an &str for the names unless I go through some unsafety and keep self-references around. Alternatively, I can simply leak the String, but this is far from ideal.

Ideally, I could simply pass the String to the appropriate method and have that be it. It's not evident to me what's being saved here as a result of using an &str solely, so my proposal is to allow N: Into<Cow<'source, str>> instead, and store the name as a Cow<'source, str>.

'attempt to subtract with overflow' panic on invalid expression

Description

The following code panics:

use minijinja::Environment;

fn main() {
    let env = Environment::new();
    let expr = env.compile_expression("\\{").unwrap();
}

with:

thread 'main' panicked at 'attempt to subtract with overflow', minijinja/src/error.rs:282:23

Relevant part of the stacktrace:

  16:        0x10306a21c - minijinja::error::debug_info::render_debug_info::hcd5508172021c006
                               at /Users/jer/code/rust/minijinja/minijinja/src/error.rs:282:23
  17:        0x10305a028 - <minijinja::error::Error as core::fmt::Debug>::fmt::h8cd2dc17739d34ea
                               at /Users/jer/code/rust/minijinja/minijinja/src/error.rs:53:17

If no expression name is given, it uses <expression> and sets a lineno.
That happens in parse_expr.
However current_span().start_line is 0 there, leading to the undeflow later.

Reproduction steps

  1. Compile and run the code
  2. Observe the panic

What did you expect

It shouldn't panic, but fail to parse the expression.

Macro cannot call itself recursively

A macro is currently unable to call itself. The reason for this is that the variables available in the macro are pulled into a closure when the macro is defined. At that point the macro itself does not exist yet, and as such the value in the macro with the name of the function points to what was in the context instead.

Escaping doesn't work as expected

Description

When using {{ '\'' }}, the following error is thrown:

called `Result::unwrap()` on an `Err` value: Error { kind: BadEscape, detail: None, name: Some("concat.txt"), lineno: 2, source: None }

(Ignore the test file, I initially thought it might be a concat issue, it's not.

Reproduction steps

    let mut env = Environment::new();
    env.add_template("escape", "{{ '\'' }}").unwrap();

Additional helpful information:

  • Version of minijinja: Latest git (310a12b)
  • Version of rustc: rustc 1.65.0-nightly (bc4b39c27 2022-08-29)
  • Operating system and version: ArchLinux btw 5.19

What did you expect

It escaped.

It's handy to do things like this:

{% set result = run_query('select add_months(\''~ start_month ~ '-01\'::date, 11)::date') %}

Though you should use " ;).

{% set result = run_query("select add_months('"~ start_month ~ "'-01'::date, 11)::date") %}

I'm looking into this, just thought I should raise it 👍

Make Template Features Optional

I would like to make some of the internal tags optional now that they add so much compile time overhead:

  • macros
    • {% macro %}
  • multi-template
    • {% import %}
    • {% from %}
    • {% block %}
    • {% extends %}
    • {% include %}

This should make it much easier to use minijinja in build scripts where the latest additions like macros increased build times significantly.

Tuple unpacking in {% with %} tag.

In my data, I have a list of tuples.
I can use them in a for loop:

{% for (v1, v2) in mylist %}
{{ v1 }}: {{ v2 }}
{% endfor %}

However, I only need the first one in the list. I'd like to do:

{% with (v1, v2) = mylist|first() %}
This is the {{ v2 }}
{% endwith %}

(also, I would normally use {% set .... %} in jinja2, but it is missing. Not really a problem if {% with %} can do what I need.)

Iterability of Strings

In Jinja2 you can iterate over a string which yields the characters in it. In MiniJinja that is only possible if you call |list. That is somewhat intentional as I always found this implicit character iteration of strings in Python odd, but it's a divergence in behavior that might need fixing.

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 !

Remove non-sync mode

Currently it's possible to compile minijinja with Rc instead of Arc by removing the sync feature. This adds a lot of internal complexity for very little benefit. I think it's a better idea to enforce Arc on the value type and instead maybe find ways to avoid excessive refcounting. Except for some cases where engine objects are put back into the context, much of the evaluation could get away with borrowing.

Refs #97

{{1-1}} for simple math throws unexpected integer, expected end of variable block

Description

Trying to do {{1-1}} throws the following error:

called `Result::unwrap()` on an `Err` value: Error { kind: SyntaxError, detail: Some("unexpected integer, expected end of variable block"), name: Some("ranges"), lineno: 1, source: None }

This is the smallest reproduction I can create, the main reason for this is for ranges:

{% for i in range(1, 5) %}
{{i-1}}
{% endfor %}

Reproduction steps

{{1-1}}


Additional helpful information:

## What did you expect
Working math :)

Unsafe Iterators

The library generally holds on to values by using Arc<T> internally. The challenge with this is that iteration over values (in the VM) depends on the iterator being on the context stack which does not work with lifetimes. As a result the current implementation creates a value iterator without lifetimes that unsafely holds a reference to the rust collection iterators of the underlying value types. This works because the refcount is also kept alive but it's an improper use of unsafe.

There might be better ways to do this.

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.

Decide on i128/u128 in Value Type

With #142 the size of the Value type increased as i128/u128 are now inlined rather than in an Arc. This also increases the size of an Instruction from 32 bytes to 48 bytes on mac. For some reason the sizes appear to still be as expected on Linux (according to GHA at least).

The changes from #142 entirely results in noticeable performance differences for parse and compile (regression) but and render (improvements):

cmp_compile/minijinja   time:   [6.6548 µs 6.6674 µs 6.6817 µs]
                        change: [+1.2169% +1.5215% +1.8229%] (p = 0.00 < 0.05)
                        Performance has regressed.

cmp_render/minijinja    time:   [6.0559 µs 6.0681 µs 6.0793 µs]
                        change: [-3.8201% -3.5456% -3.2949%] (p = 0.00 < 0.05)
                        Performance has improved.

parse                   time:   [8.1028 µs 8.1185 µs 8.1328 µs]
                        change: [+1.1411% +1.3896% +1.6345%] (p = 0.00 < 0.05)
                        Performance has regressed.

compile                 time:   [12.986 µs 13.019 µs 13.054 µs]
                        change: [+1.3967% +1.6597% +1.9237%] (p = 0.00 < 0.05)
                        Performance has regressed.

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.

Optimize {{ super() }}

Currently a call to bare {{ super() }} without any other modification still causes intermediate buffering. This is needed because {{ super()|upper }} for instance needs to be supported. However for the common case of just calling super without any modification it should be possible to directly emit to the resulting string without indirection.

One way in which this could be accomplished is a new SUPER instruction that gets emitted from the compiler for a bare super() call.

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

Add "else" clause to For Loops

MiniJinja currently does not support the {% else %} block on for loops which is executed if the loop did not iterate.

Multiline strings in calls and sets don't work

Description

{{ 'hello
world' }}

Should output:

hello
world

And

{% set foo = 'hello
world' %}
{{ foo }}

Should output:


hello
world

[What happened]

called `Result::unwrap()` on an `Err` value: Error { kind: SyntaxError, detail: Some("unexpected end of string"), name: Some("set.txt"), lineno: 22, source: None }

---------------------------- Template Source -----------------------------
  19 | {% if true %}{% set foo = "was true" %}{% endif %}
  20 | {{ foo }}
  21 | 
  22 > {{"hello
  23 | world"}}
--------------------------------------------------------------------------

thread 'test_vm' panicked at 'called `Result::unwrap()` on an `Err` value: Error { kind: SyntaxError, detail: Some("unexpected end of string"), name: Some("set.txt"), lineno: 22, source: None }

---------------------------- Template Source -----------------------------
  19 | {% if true %}{% set foo = "was true" %}{% endif %}
  20 | {{ foo }}
  21 | 
  22 > {{"hello
  23 | world"}}
--------------------------------------------------------------------------
', minijinja/tests/test_templates.rs:37:58
stack backtrace:

Additional helpful information:

What did you expect

It would not error :)

I believe this is due to https://github.com/mitsuhiko/minijinja/blob/main/minijinja/src/lexer.rs#L127 , I'm unsure if it'll break anything by removing it?

Dynamic Source

To address #54 one potential way would be to have a callback based dynamic source:

use minijinja::Source;

let mut source = Source::new();
source.set_loader(|name| -> Result<Option<String>, Error> {
    if name == "foo" {
        Ok(Some("new template".into()))
    } else {
        Ok(None)
    }
});

This however currently does not work with the borrow rules but a restricted API and some unsafe might make this possible.

`Object::attributes()` appears to be impossible to implement in most cases

Hello again!

One of the most appealing aspects of minijinja, and that reason I'm exploring using it now, is the existence of the Object trait. In my application which currently uses tera, the lack of something like it accounts for 25% of the entire application's running time. In particular, there are two such instances where I'd like to implement Object:

  1. Around an existing serde_json::Value or toml::Value, avoiding a second serialization.
  2. Around an arbitrary structure which contains a HashMap, exposing its direct fields, and then the keys/value of the HashMap, in a cascading fashion.

I've been somewhat successful in implementing this with the caveat that I cannot implement attributes() without resorting to unsafe and Pin. Without implement attributes(), the object ceases to behave like a "regular" object, which I feel will cause confusion in my application. The why is hopefully clear: in either case, the attributes of the object are dynamic in nature. I can collect into a Vec<String>, but then I can't get an &[&str] from that. I wonder if there's a better approach here that makes this possible while keeping Object an object-safe trait.

One idea is to change the signature as follows:

- fn attributes(&self) -> &[&str];
+ fn attribute(&self) -> Cow<'_, [Cow<'_, str>];

This is ugly, but covers static and dynamic cases alike, with no allocations in the former case.

Another idea is to return an iterator:

fn attribute(&self) -> Box<dyn Iterator<Item = &str> + '_>;

This likely works for most interesting cases, including mine, without the ugly signature, at the expense of a Box every time it's called. Perhaps the compiler will optimize-out the allocation in simple/common cases, however.

Finally, perhaps switching to/adding support for Valuable as in #77 obviates the need for this, though I can't be quite certain. I would suggest, however, that these cases be considered if/when implementing #77.

Support for the boolean() filter

As I am going through and trying to use this, it would be great if there was a standard boolean filter that takes in text and returns true for values like:

  • yes
  • true
  • True

and then false for string values like this:

  • false
  • False
  • no

Although I am not sure if that is what the Jinja2 filter does, or if I am thinking of the ansible bool filter instead...

Environment::add_and_get_template functon

When "report" is not often used function there is no sense
to cache Environment between calls, so code to generate
reports looks like this:

env.add_template("report1", report1_html)?;
let html_tmpl = env.get_template("report1")?;

it would be nice to have function like add_and_get_template:

let html_tmpl = env.add_and_get_template("report1", report1_html)?;

is there a way to iterate over key, values in a map

For a custom function, that accepts a minijinja::value::Value which holds a map, I would like to access the key, values of the inner map and even mutate the values or add new key, value pairs.

As it seems right one can only check for specific keys, via .get_attr(&str) but not iterate over all contents.

Maybe I'm missing something, in that case I would appreciate some input.

`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().

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.

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.