keats / tera Goto Github PK
View Code? Open in Web Editor NEWA template engine for Rust based on Jinja2/Django
Home Page: http://keats.github.io/tera/
License: MIT License
A template engine for Rust based on Jinja2/Django
Home Page: http://keats.github.io/tera/
License: MIT License
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
Don't put everything in the README.md but have a static site (way later done with Tera?)
So we can get jinja2 syntax highlighting in editors
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. 😉
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
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.
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.
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
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.
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.
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
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
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]
.
A few people mentioned that using something like nom would be a better approach but I don't know anything about it.
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".
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:
call
syntaxJinja2 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
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.
To match what the filters are doing in Tera, all args to a macros should be kwargs.
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
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
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
Simple math (only 1 operation) should be enough for most of the cases
There is only one basic right now
in html, I need to display a table, such as:
白拿活动申请结果表
序号 蜥客名 电话 艺术品id 艺术品名称 时间
1
when no data, It display one line yet.
PS: I used loop.index
as first column.
this is very easy to check.
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.
Left to add:
All testers are in src/testers.rs
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
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
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:
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?
There are some templetes that they only include sub templetes. So, how to render they neatly?
Basic ones: capitalize, uppercase, lowercase, length, first, last, slugify, striptags, truncatechars, truncatewords, raw
Harder:
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
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
.
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
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.
Right now it's 32
Should be done on the v0.5 branch
But do so after custom templatetag are in, might change a lot of things
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 the functionality depicted here: http://jinja.pocoo.org/docs/dev/templates/#whitespace-control
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
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
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.
Those should be in filters/mod.rs
or in a filters/common.rs
file.
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
that makes it so the content inside is just text
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.
I don't think comments are enough if you want to see the bigger picture of how all of macros/inheritance work together
The crate can be compiled but the tests can't be ran as tests/lib.rs
uses serde macro for struct serialisation which is only available on nightly
Since we render the parent template ast, we don't see the macro imports in child templates
As an experiment for now to see if that cleans up a bit the code
Looking at templates/included.html
we need to do weird things to keep indentation. Have a look and see if it's easily fixable
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.