Giter Club home page Giter Club logo

tera's Introduction

Tera

Actions Status Crates.io Docs Gitter

Tera is a template engine inspired by Jinja2 and the Django template language.

<title>{% block title %}{% endblock title %}</title>
<ul>
{% for user in users %}
  <li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>

It does not aim to be 100% compatible with them but has many of the Jinja2/Django filters and testers.

Documentation

API documentation is available on docs.rs.

Tera documentation is available on its site.

SemVer

This project follows SemVer only for the public API, public API here meaning functions appearing in the docs. Some features, like accessing the AST, are also available but breaking changes in them can happen in any versions.

tera's People

Contributors

0x1793d1 avatar alex-pk avatar andy128k avatar bootandy avatar eijebong avatar fx-kirin avatar giraffate avatar godofdream avatar harrisonkaiser avatar hoggetaylor avatar keats avatar lfalch avatar lucab avatar mathiaspius avatar moschroe avatar nulltier avatar opilar avatar orhanbalci avatar paulcmal avatar peternator7 avatar sergiobenitez avatar tailhook avatar tones111 avatar txuritan avatar upsuper avatar vdagonneau avatar vdagonneau-anssi avatar wdv4758h avatar xabufr avatar yakov-bakhmatov 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tera's Issues

New feature request:Templates optimization.

The title may not be suitable.
What I want to convey is:
if tera automaticly removes comment tags and all unnecessary blank chars, the data tansports to client will be reduced.

Better compiling & rendering errors

If we can get errors filename/line it will improve the debugging experience by quite a bit.

Also stop using panic! and propagate the errors across everything to make it easy to integrate with other tools so they don't need to use catch_unwind

Should be done on the v0.5 branch

Add a magical variable that dumps the context

Something like __tera_context.
Useful when you are not entirely what the context is, like when I'm writing templates with Hugo and I have no clue what is available.

It will need to be able to pretty print the JSON context

Regression between version 0.2 and 0.3

Nested 'if' in 'for' results in "internal error: entered unreachable code" at render.rs:105. The following code is a minimal example to reproduce the issue.

extern crate tera;
use tera::Tera;
use tera::Context;

fn main() {
    let mut tera = Tera::default();
    tera.add_template("nested", "{% for val in seq %} {% if val == sel %} on {% else %} off {% endif %} {% endfor %}");

    let mut context = Context::new();
    context.add("sel", &2u32);
    context.add("seq", &vec![1,2,3]);

    let page = tera.render("nested", context).unwrap();
    println!("{}", page);
}

I went back through the commits and the problem is introduced in commit 2154c3e "Add tests and remove using AST in tests fn".

Improve performance

I added some benches and performance is not that great when rendering both inheritance and macros or using lots of variable across 3 templates, a pretty common case:

test bench_rendering_basic_template            ... bench:      11,157 ns/iter (+/- 2,765)
test bench_rendering_inheritance_and_macros    ... bench:      28,078 ns/iter (+/- 13,440)

Note that my laptop is ~3y old so someone with a good desktop/server will beat that easily.
Results are in ns so it's still pretty fast but can easily be improved as I haven't cared about performance yet and there is lots of clone() around.

I only mention rendering performance as parsing performance is not critical since that's a one-time cost and is fast enough. But i'm obviously pro performance gains there as well

Context from Serde Serializable

It would be nice if instead of having to build out a context for the template... we could just pass in anything that serde sees as serializable.

like...

impl Render<T: Serializable> for Template {
   fn render(tmpl: &str, ctx : &T) { ... }
}

Would be very convienent indeed

Automatically escape rendered HTML.

At present, Tera emits passed-in text directly in templates. This means that unless the user escapes the text being rendered, he/she is open to XSS attacks. Other template engines, like Handlebars, automatically escape text, precluding this attack vector, unless the user explicitly asks for unescaped text. I propose that Tera do the same.

Specifically, text rendered with {{ }} should automatically escape the input text, while text rendered with {{{ }}} should emit the raw text.

Filters to implement

Intro

Follow the examples in filters/string.rs with tests.
Need to add all common and expected filters from Jinja2 and Django.
Feedback on the API wanted as well!
Each filter should have tests for all the possible options.
Tera filters can only be applied to identifers and their arguments have to be named.

Strings

Numbers

Arrays

Common

Those should be in filters/mod.rs or in a filters/common.rs file.

Dates

Date filters will wait until Tera has its own serialization instead of using JSON


Note: I left out some filters that I don't really the use for like ljust in Django, but happy to include more than the currently listed if they are needed. I also probably forgot some

Double for-loop can not be used?

main.rs

extern crate tera;
use std::env;
use std::path::Path;
use tera::{Tera, Context};

fn main() {
    let root_path = env::current_dir().unwrap();
    let template_dir = root_path.join(Path::new("templates/*"));
    let template_engine = Tera::new(template_dir.to_str().unwrap());
    let mut context = Context::new();

    let vectors = vec![vec![0, 3, 6], vec![1, 4, 7]];
    context.add("vectors", &vectors);
    println!("{}", template_engine.render("double-loop.tera", context).unwrap());
}

templates/double-loop.tera:

{% for vector in vectors %}
  {% for j in vector %}
    j={{ j }}
  {% endfor %}
{% endfor %}

output:

j=0
  j=3
  j=6
j=0
  j=3
  j=6

P.S.

tera is good library 😄
I have been using in the build script.

Thanks.

Fix include whitespace

Looking at templates/included.html we need to do weird things to keep indentation. Have a look and see if it's easily fixable

Tera hangs whenever you insert a free opening brace

This hangs tera in line 5:

{% block title %}Index{% endblock title %}
{% block head %}
    {{ super() }}
    <style type="text/css">
        .important { color: #336699; }
    </style>
{% endblock head %}
{% block content %}
    <h1>Index</h1>
    <p class="important">
      Welcome to my awesome homepage.
    </p>
{% endblock content %}

Particularly the opening brace ("{") in the ".important { color: #336699; }" CSS statement, breaks the parser.

This example came straight from your front page. 😉

Add support for variable tests

Hello!

Great project you have going on here!

One feature that I feel is sorely missing is support for tests. Have you considered implementing this? The case when I most want to use this is to check if a variable has been defined. For example, I'd like to be able to write:

{% if some_var is defined %}
  {{ some_var }}
{% endif %}

It would also be nice to make this pluggable to allow anyone to implement a test.

Add not to mean !

In condition nodes.

Currently there's no way to indicate negation in if/elif.

For booleans we can do:

{% if my_bool == false %}

but that's obviously not ideal and doesn't work for truthy values (truthiness defined there https://github.com/Keats/tera/blob/master/src/context.rs#L86-L99)

We want to be able to express negation like so:

{% if not my_array %} // empty array will result in that if body rendering
{% if not username %} // empty string or null values will result in that if body rendering
{% if user and not user.is_admin %} // render if user exists but is not an admin

The not should be a keyword only used in the condition node but should be reserved globally as a Tera keyword in case we want to expand its use

Support for inheritance

Jinja/Django approach

You put empty blocks like {% block title %}{% endblock %} in the parent template, {% extends "base.html" %} in the child template and then re-open the blocks but with actual content.
http://jinja.pocoo.org/docs/dev/templates/#template-inheritance
Jinja only uses 1 level of inheritance afaik, ie can't extend another template that extends

I've looked at other templating languages but couldn't find solutions that looked better (looked at erb, handlebars and twig).

Next to look at: clojure/haskell/etc templates

Use our own serialization

rather than JSON.
It would give us more types than JSON has, like date and datetimes out of the box.

Would require custom derive to be nice though so waiting for macros 1.1 to land for that to happen.
Or we could piggyback on Serde and write a serde_tera implementation

Add a single template render method

With signature Template::render(content, context) maybe?

Usecase: you allow users to enter a template in your frontend, eg for mails or something and want to render that to HTML. You don't want to add that user template to your (potential) Tera instance.

It will need to differentiate between parsing and rendering errors in order to provide meaningful errors that the dev can handle

Indexed array/tuple access does not seem to be working

If I have a context that includes a tuple or array, I should be able to access tuple or array elements using tuple or array syntax: tuple.0, or tuple[0]. Unfortunately, while the former is recognized, it does not appear to work. Tera instead issued an error saying that a field named '0' does not exist.

try_get_value! refers to a private module

As I mentioned in the reddit thread, the try_get_value! macro used in custom filters doesn't work outside the tera crate itself. This is due to this line in the macro expansion:

 return Err(::errors::TeraError::FilterIncorrectArgType(

but the errors module is private within tera. Here's a minimal example that fails to compile:

#[macro_use]
extern crate tera;
extern crate serde_json;

use std::collections::HashMap;
use tera::{TeraResult, Value};

pub fn dummy_filter(value: Value, _: HashMap<String, Value>) -> TeraResult<Value> {
    let _ = try_get_value!("dummy", "value", String, value);
    Ok(value)
}

fn main() {}

And the error is:

error[E0433]: failed to resolve. Maybe a missing `extern crate errors;`?
 --> src/tera.rs:9:13
  |
9 |     let _ = try_get_value!("dummy", "value", String, value);
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Maybe a missing `extern crate errors;`?
  |
  = note: this error originates in a macro outside of the current crate

Fix error handling in Render

This code sample, when placed in tests/templates/ (and added to the test suite)

<h1>{{object.nonexistent_field}}</h1>

causes the following error

thread 'test_templates' panicked at 'called 'Option::unwrap()' on a 'None' value', ../src/libcore\option.rs:330

It's pretty clear why -- render.rs is full of unsafe unwraps. @Keats you already know about this, but I thought I should file the first issue to track this.

Investigate passing functions instead of templatetags

Functions would be like filters that don't have a first arg?
Could also fake kwargs like {{ url_for(view_name="hello", user.id, company.id }} and have the kwargs be a hashmap but not sure if that is actually needed

Add `Tera` constructor from in-memory buffer

It is sometimes desirable not to load files at runtime, but to include them in the binary (with include_bytes! or include_str!).
Tera currently doesn't allow loading a template from such a buffer.

A potential constructor could take a map of strings (filenames) to &str or &[u8].

How to actually pass custom functions to Tera

If we create the Tera instance in lazy_static! we might not have the router etc there yet so for example url_for wouldn't be doable.

In short: how to pass functions to Tera that requires some kind of state.

Macros

Macros are a way to do what most templates engine do as include but with a known context as they look like functions.

See Jinja2 docs and some real life examples.

A few things to consider when porting them:

  • import
  • call syntax
  • args

Import

Jinja2 has 2 ways to load macros (http://jinja.pocoo.org/docs/dev/templates/#import):

{% import "macros.html" as macros %}
{% from "macros.html" import macro_1 , macro_2 %}

Since the equivalent of from x import * is not great in general, I'd be interested in the second form, that is {% from "macros.html" import macro_1 , macro_2 %} which makes it clear what you want to import.
That means Template need to have a pub macros: HashMap<String, Node> or something similar in order to resolve the macros easily.
Since we are namespacing macros by filename, several macros can have the same name which can cause an issue if we are trying to use 2 macros with the same name in the same file.
Jinja2 allows renaming imports {% from 'forms.html' import input as input_field, textarea %} and we could/should support that syntax, waiting for feedback on that point

Call syntax

I'm not sure call is that needed and it makes things a bit hard to understand imo. For example from the Jinja2 docs:

{% macro dump_users(users) -%}
    <ul>
    {%- for user in users %}
        <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
    {%- endfor %}
    </ul>
{%- endmacro %}

{% call(user) dump_users(list_of_user) %}
    <dl>
        <dl>Realname</dl>
        <dd>{{ user.realname|e }}</dd>
        <dl>Description</dl>
        <dd>{{ user.description }}</dd>
    </dl>
{% endcall %}

seems overly complicated to read for what should be simple.
Let's ignore call for the first pass of macros.

Args

To match what the filters are doing in Tera, all args to a macros should be kwargs.

Motivation for macros

From the creator of Jinja2:

Another thing i strongly disagree with but I can see the motivation. Macros in Jinja I prefer so much over includes because it becomes clear what values exist. Includes become really messy unless you are very organized. Macros (which are just functions) accept the parameters and it becomes clear what the thing does. Just look at that macro as an example: https://github.com/pallets/website/blob/master/templates/mac...
It's absolutely clear what it accepts as parameters. If that was a partial include the "caller" needs to set up the variables appropriately and there is no clear documentation on what the template wants et

Handle globs

so you can import from say /app/**/*.html

The tricky point is how to name the templates, ie if we have

app
|_ contacts
    |_ contact.html
|_ index.html

I guess contact.html would be called contacts/contact.html ? Ie name starts from the globbing ** if there is one or * if there isn't

replace filter results in panic

The use of a template consisting only of
{{ message|replace(from="\n", to="<br>") }}

results in
thread '<unnamed>' panicked at 'no pattern matched in _fn_arg_value; failed at [string, fn_arg, simple_ident, ...]', /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 stack backtrace: 1: 0x560f8723a45f - std::sys::backtrace::tracing::imp::write::h22f199c1dbb72ba2 2: 0x560f8723fc2d - std::panicking::default_hook::{{closure}}::h9a389c462b6a22dd 3: 0x560f8723ea12 - std::panicking::default_hook::h852b4223c1c00c59 4: 0x560f8723f008 - std::panicking::rust_panic_with_hook::hcd9d05f53fa0dafc 5: 0x560f8723eea2 - std::panicking::begin_panic::hf6c488cee66e7f17 6: 0x560f8723ede0 - std::panicking::begin_panic_fmt::hb0a7126ee57cdd27 7: 0x560f86b00339 - <tera::parser::Rdp<T>>::_fn_arg_value::h08850c1ed22ae8f7 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 8: 0x560f86b0135a - <tera::parser::Rdp<T>>::_fn_args::h5c0372594d2ddc62 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 9: 0x560f86b02f7a - <tera::parser::Rdp<T>>::_fn::h991da15aed3dddf7 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 10: 0x560f86b03e0d - <tera::parser::Rdp<T>>::_filters::he78ab38dd7a92213 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 11: 0x560f86b0d9a9 - <tera::parser::Rdp<T>>::_expression::hee58ca5339661318 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 12: 0x560f86af0a83 - <tera::parser::Rdp<T>>::_content::h42ee329bcedd9f9e at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 13: 0x560f86aee706 - <tera::parser::Rdp<T>>::_template::h5a92ce7d184c6b59 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 14: 0x560f86aee73b - <tera::parser::Rdp<T>>::_template::h5a92ce7d184c6b59 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 15: 0x560f86aee73b - <tera::parser::Rdp<T>>::_template::h5a92ce7d184c6b59 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 16: 0x560f86aee73b - <tera::parser::Rdp<T>>::_template::h5a92ce7d184c6b59 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 17: 0x560f86aee73b - <tera::parser::Rdp<T>>::_template::h5a92ce7d184c6b59 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 18: 0x560f86aecffd - <tera::parser::Rdp<T>>::main::h5fc000eb0502395c at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:51 19: 0x560f86abf3ff - tera::parser::parse::h5f7bb412bdbeaa4e at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/parser.rs:551 20: 0x560f86accdff - tera::template::Template::new::h6dd82516036273aa at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/template.rs:17 21: 0x560f86ace05f - tera::tera::Tera::new::h8ab17d08d28cbf49 at /home/alan/.cargo/registry/src/github.com-1ecc6299db9ec823/tera-0.3.1/src/tera.rs:47

There is no issue with
{{message}}

Sorry if this is something I am doing but I am at a loose end

Support for 'env_logger'

Would it be feasible to include env_logger into the dependencies? That way the library can be peppered with debug and trace statements, which could help a lot when debugging issues.

Add {% include... %} tag or macros

Go templates: {{ partial "header.html" . }} . means it passes the current context

Jinja2: {% include 'header.html' %} passes the current context automatically

Django: {% include "name_snippet.html" with person="Jane" greeting="Hello" %} By default it passes the current context automatically but can override with the with clause

Filters

Basic ones: capitalize, uppercase, lowercase, length, first, last, slugify, striptags, truncatechars, truncatewords, raw

Harder:

  • date/time formatting (use chrono?)
  • pprint to pretty print a variable, useful for debugging. Having a magic argument that prints the full context would be very nice too

Escaping

Rendered variables should be escaped by default.
Rather than having a separate tag for raw (like {{{ my_html }}}) we could have{{ my_html | raw }}` eventually to get the non-escaped version.
To investigate

Improve documentation

Don't put everything in the README.md but have a static site (way later done with Tera?)

Not able to pull in tera as dependency

Hi,

I got the following error from cargo build --verbose:

 $ cargo build --verbose
    Updating registry `https://github.com/rust-lang/crates.io-index`
failed to parse registry's information for: serde_json

Caused by:
  the given version requirement is invalid

I have tera = "0.1.3" in my Cargo.toml.

Custom templatetags

https://github.com/cobalt-org/liquid-rust implementation seems quite neat

Defining Tera tags using the same interface as the custom tags would be a good way to dogfood the system

There's this trait that all tags need to implement:

pub trait Renderable {
    fn render(&self, context: &mut Context) -> Result<Option<String>>;
}

Example template:

{% for user in users %}
  <h1>Awards</h1>
  {% if user.awards %}
    {{ user.awards | length }} awards(s):
    <ul>
    {% for award in user.awards %}
      {% include "award.html" %}
    {% endfor %}
    </ul>
  {% else %}
      Nothing
  {% endif %}
{% endfor %}

I guess the parsing could do something like:

  • see forloop -> create forloop node
  • see if -> parse if and append it to forloop children
  • see endfor -> push forloop to ast and continue

The difference would be in the rendering, the Renderable trait should get the Node of the tag as well and we could pass an updated context to child nodre like the if or the include.
Not entirely sure how for example a blocktrans tag would work if you have an if inside?

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.