-
Notifications
You must be signed in to change notification settings - Fork 293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tera v2 wishlist/changes #637
Comments
I'm not yet a tera user, but I gave it a very serious look, because I want runtime templates (using askama for now, which uses compile-time templates). I'm templating latex files, which use brackets (in particular, the curly ones) all over the place, so what I'd need is different delimiters than |
The current parser library doesn't allow passing variables to the lexer and neither does the future lexer library :/ |
Unfortunately, latex also uses |
Does it use [[, [%, [# though? Single |
No, |
Tera only cares about |
Huh, that would be awesome. You mention jinja2 in that issue, but I'm pretty sure it did trip over the brackets in the default settings (but jinja2 allows changing the delimiters, so I got it to work). Nevertheless, I'm taking tera for a test-drive tonight, thanks for your time, and thanks for tera! |
After using tera for some time (works nicely, thank you for it!), what I am missing is more fine-grained errors, in particular around rendering a template. When the template has a variable that is missing from the context, the error seems to be a generic |
So the next parser has spanned expression pointing to the exact span in the template. |
Yep, you have to use |
Hi! We're starting to use Tera in rustdoc. I like it a lot so far. One thing we're really interested in is performance. Rustdoc generates a lot of HTML in a single run, and there's often a developer waiting to look at the output, so we care a lot about speed. Also, the rustdoc team has put in a lot of effort speeding it up, so we want to make sure the move to templates doesn't slow things down again. Right now I'm investigating a perf regression after adding a second template. Some themes that come up:
The |
:o nice! Performance is the main goal for v2 as well, I'm guessing a lot of allocations you see are from the JSON serialization, which is the bottleneck for Zola as well (minus syntax highlighting). I'm guessing rustdoc is the same (lots of text) that need to be essentially cloned when moving to I have some big hopes for |
Has it stalled? Looks like they're planning an initial release as of 8 days ago. |
I am looking at tokio-rs/valuable#59 which would be required for Tera |
One thing I'm wishing for right now was a way to get a template from tera to inspect it. In my case, I can probably just read the file from disc again, but depending on the structure of the program it might be worthwhile to provide something like that (never mind that it might just be faster than reading it again). Maybe a use case would be modifying those on the fly? Or check for certain content? The latter is what I want to do btw. |
You can access a template AST, it's just not visible in the documentation since the AST is not stable. |
It would be nice to have context local functions, it was pulled from v1 due to some breaking changes but it's on the table for v2 |
I would like to have something like Components from Vue.js which in Tera would probably be macros 2.0. {% macro user_list(users, user_icon="👤") %}
<ul>
{% for user in users %}
<li>
{% block icon(user_icon, user) %}
{% if user_icon %}<i class="icon">{{ user_icon }}</i>{% endif %}
{% endblock %}
{% block name(user) %}{{ user.name }}{% endblock %}
{% block default(user) %}<a href="/users/edit/{{user.id}}">Edit</a>{% endblock %}
</li>
{% endfor %}
</ul>
{% endmacro %}
{# this uses the default content of all blocks #}
<user-list users="{{users}}"/>
{# rendered #}
<ul><li><i class="icon">👤</i>Aron<a href="/users/edit/1">Edit</a></li></ul>
{# children will be assigned to the default block #}
<user-list users="{{users}}" icon="🧍">
<span class="disabled">Edit</span>
</user-list>
{# rendered #}
<ul><li><i class="icon">🧍</i>Aron<span class="disabled">Edit</span></li></ul>
{# blocks can be overridden and receive the arguments that are passed #}
<user-list users="{{users}}">
{% block icon(user_icon, user) %}<i class="icon rounded {{user.color}}">{{user_icon}}</i>{% endblock %}
</user-list>
{# rendered #}
<ul><li><i class="icon rounded red">👤</i>Aron<a href="/users/edit/1">Edit</a></li></ul>
{# when the default block needs arguments it can be called via its name #}
<user-list users="{{users}}">
{% block default(user) %}<a href="/users/delete/{{user.id}}">Delete</a>{% endblock %}
</user-list>
{# rendered #}
<ul><li><i class="icon">👤</i>Aron<a href="/users/delete/1">Delete</a></li></ul>
{# it would be possible to allow assigning the default block arguments directly but would cause confusion #}
<user-list users="{{users}}" {{ default(user) }}>
{{ user.name }} {# this works #}
{% block icon(user_icon) %} {# notice that only the first argument was retrieved #}
{{ user.name }} {# this doesn't work since user is not defined #}
{% endblock %}
</user-list>
{# and explicitly calling the default block would still be needed to empty it #}
<user-list users="{{users}}">
{% block default() %}{% endblock %}
</user-list>
{# rendered #}
<ul><li><i class="icon">👤</i><a href="/users/edit/1">Edit</a></li></ul>
{# shorthand : to avoid {{ }} like in Vue.js and other templating languages might be nice #}
<user-list :users="users"> Using HTML like syntax to call macros might be controversial but I think the result is really neat and leaves the heavy syntax I think this would be a very nice addition to Tera and would allow for seamless use of e.g. Tailwindcss edit: naming them |
That's extremely unlikely to happen. Tera is not only used for rendering HTML so it can't just change its syntax for one usecase. Something like https://jinja.palletsprojects.com/en/3.0.x/templates/#call is likely to be added though. |
Ah of course then that syntax won't fly. Call is already halfway there but would be improved with the ability to define multiple callers/yield points and to have default content if it's not provided at the call site. {# the original definition syntax would work but here is an alternate proposal that's closer to call #}
{% macro user_list(users, user_icon="👤") %}
<ul>
{% for user in users %}
<li>
{# a named caller can be defined like this #}
{{ caller prepend(user) }}
{# if caller is in {% %} it's a block with default content #}
{% caller icon(user_icon, user) %}
{% if user_icon %}<i class="icon">{{ user_icon }}</i>{% endif %}
{% endcaller %}
{% caller name(user) %}{{ user.name }}{% endcaller %}
{% caller(user) %}<a href="/users/edit/{{user.id}}">Edit</a>{% endcaller %}
</li>
{% endfor %}
</ul>
{% endmacro %}
{% call(user) user_list(users) %}
<a href="/users/delete/{{ user.id }}">Delete</a>
{% icon(user_icon, user) %}
<i class="icon rounded {{ user.color }}">{{ user_icon }}</i>
{% endcall %}
{# rendered #}
<ul><li><i class="icon rounded red">👤</i>Aron<a href="/users/delete/1">Delete</a></li></ul> To have a library of reusable |
A variant of |
Isn't that |
Indeed. Thanks, I haven't thought of it. |
it will be amazing if tera is able to access context. i'm working on KaTeX SSR integration for zola and I really need this feature! |
Yep, accessing the context from filters/functions is definitely something I want to add, partly for Zola as well (so we don't do |
is it possible to add a |
A note comment about new hand-written parser isn't clear. @Keats are you going to kick off the pest grammar generated parser? |
I've added indent/group_by/filter and made an issue to discuss filters specifically: Keats/tera2#37 |
https://github.com/Keats/tera2 now has filters/tests/functions implemented and should be mostly working and is ready for some beta testing. Next steps are:
And probably some more things like the global macros/macro types/rendering a macro specifically along the way. The macro types can be added later on so it will probably be worked last unless someone wants to implement it. |
Is this still the position (pun not intended) on positional arguments? Feels frustrating every time I have a macro/function taking one arg and I have to come up with a name for it. I liked the thought of the first arg or single arg functions being/having positionals? |
Yep still the case |
One thing that would be nice is the ability to either:
Use case: Building an API in Rust (Axum and tera) where users can get an html-document from a template by specifying the template in the path and field values using headers. the feature would be useful regardless of performance cost since you could just adapt by either:
|
I wrote a function for that in |
How do you handle branches? Eg if/else? |
Not sure if I handle them. I just walk over the AST recursively and fetch anything that looks like a variable. |
Yes it does, is there any reason for a method for Tera with similar functionality to not be implemented that anyone could think of? I feel like it would be useful in many circumstances. |
Yeah, I think that'd be pretty nice to have as default. |
You can only know if the output is correct in your own context. Eg:
Should it report |
Reporting |
Yes, might be better to just add a method to Tera for getting all variables and leaving Tera.render() unchanged. Of course there are cases where not all variables are necessary but I think the scope should, at least initially, be limited to getting all variables of a template and consider changing error behaviour and/or being able to check what is required out of scope |
This may have been brought up already, but, I can't think of the right search term. I'm using Tera from within Zola, and for me it really slows things down always needing to use the {% %} / {{ }} tags to delimit any "code". As well as making it much harder to read. In jinja, I've previously found it very useful to define my own filters in Python, where I have a more usual programming environment, so it usually makes it easier to implement logic there and expose a new filter than to try and write it within a template. I expect this wouldn't make much sense for Tera, given that it's in Rust, so then some other embedded language would need to be bundled in, and it gets messy. What would be really nice however, is to be able to write macros in a plain Tera file, without using all the tags, so that it just reads more naturally like other languages. I think that would make working with macros much faster and more pleasant. I expect that might affect parsing, hence mentioning it here and not within Zola, but I may be wrong on that, or maybe it's currently possible an I'm just not using he right keywords in Google. |
I would rather have lua/wasm or similar filters for Zola that users can create than different syntax (how does that even work in practice)? |
Lua sounds sensible to me, am sure it would bring up a discussion where different people would prefer a different language, but as it's designed to be embedded and presumably there are easy ways to create rust bindings then sounds like a good option to have. Either way, it'd still be nice when creating macros to be able to
instead of
or
If we're not actually rendering a template right now, I just find it a lot of extra typing and visual noise that makes it harder for my eye to scan and for my mind to read the code. I have no idea how lisp users survive because I just cannot scan more than three closing brackets of the same type in one go! |
Your example is a very narrow version of a macro. How would you have a macro used to template some Python code without delimiters? Like something that actually has text like return, if, else etc. I mean without even that part, I find the first snippet much worse to read than the 3rd one |
Well, possibly not the best use-case for that kind of macro. I'm trying to build a simple site, the macros are there to implement logic and then maybe they have one line rendering the actual content, so the chances of needing to render a keyword or something that looks like code are minimal. I'd expect someone wouldn't want to use the idea if they were. I keep the macros separate from the templates if I can, and if they could return values back to the template rather than render, then personally I'd find that preferable. Having used a number of templating tools I find that's usually where the friction comes in. One is injecting logic into content and the other is injecting content into logic. The way they expand, the scoping rules, include paths, etc. all makes sense in the context of content but are different from their equivalents in most programming languages. I've found it best if there's a way to keep those boundaries separated and the interface as thin as possible. E.g. if I wanted to promote some blog posts to the top of the page based on some tags and how popular they are, it's best to run through manipulating a list of hashmaps to get them in the order, and then let the template decide what to do with those. So I definitely wouldn't complain to having an embedded lua interpreter, doing away with most of the macros and a number of the filters altogether and instead just working in a programming language to achieve things and sending that back up to the template to render. But either way, I lost a good 20 minutes yesterday wondering what had gone wrong because after needing to copy and paste several times (this was the workaround for sorting, so it was quite a bit longer than the above), my eye simply didn't scan the difference between |
Maybe I don't understand something, but why read the raw text of the templates? There are also a bunch of extensions with syntax highlighting Jinja2. For example, VS Code with the extension Better Jinja or Sublime Text is able to highlight the syntax well. Why break your eyes? |
Any thoughts on Keats/tera2#51 ? |
Syntax highlighting helps, it doesn't always fire up or work properly in my IDE. But the need to type the braces is still an inconvenience and clutters things for me, whatever colour they might be. I think components make sense, as more of a frontend thing, and then if logic can be somewhat separated then that seems a logical way of approaching it to me. I guess up to now Tera may be emulating the behaviour/ethos of other templating engines that largely work in the same way, but moving away significantly from the way they do things could lead to something better. As mentioned - personally would rather implement e.g. complex sorting programmatically and have that separate from the HTML/UI elements/whatever format is being templated that decide how to display each data item. If embedded Lua is on the table, then it could even be taken as something like each Component is a class and move away from a more text-based expansion approach. Its constructor takes an optional body and some typed arguments and perhaps then the macro call instead also becomes e.g.
|
FYI I've been working on a Tera-specific Tree-sitter grammar for Neovim/Helix editors if that helps: https://github.com/uncenter/tree-sitter-tera. Planning to make a TextMate version (VS Code and Sublime Text) version at some point in the future once I finish up documenting the Tree-sitter version. |
Thanks! If it works in VS Code I will likely take a look some time in the future. |
As I said originally it only works with Neovim/Helix (and Zed too actually) as it is a Tree-sitter grammar, which VSCode doesn't support. Working on a new TextMate grammar for VS Code right now but it isn't complete or public yet. I can let you know once I finish it, hopefully later this week :) |
Yep, that's what I meant. I'll keep an eye out, if you end up creating one :D |
Now public at https://github.com/uncenter/vscode-tera. Please create issues if you encounter any weird behavior, I'd be happy to fix. Still haven't added embedded HTML support, on my todo though. |
On top of https://github.com/Keats/tera/issues?q=is%3Aopen+is%3Aissue+label%3A%22For+next+major+version%22
Parser
Operator precedence and expressions ✅
Parentheses should work everywhere and precedence should make sense. This is mostly already implemented in a new parser (private for now).
Should something like
{{ (get_page(path=“some-page”)).title }}
work? Right now it requires going throughset
which is probably fine imo.Better error messages ✅
The new parser is hand-written so we can provide detailed error for each type of error. I’m also thinking of spanning all expressions in the AST for better rendering errors but not 100% convinced on that yet, will need to try.
Whitespace management
Do
trim_blocks
andlstrip_blocks
(https://jinja.palletsprojects.com/en/3.0.x/templates/#whitespace-control) by default that can be switched on/off as one thing and not two.Indexing changes ❔
Right now you can access array indices by doing
my_value.0
ormy_value[0]
. This is in line with Jinja2/Django but it feels a bit weird.How about allowing only
my_value[0]
so array index is the same as dict index and more like all programming languages (except tuple access)? The dot syntax doesn’t work when the index is a variable anyway which is probably the main usage of indexing. It would be a big departure from Django/Jinja2 though.Features
call
https://ttl255.com/jinja2-tutorial-part-5-macros/#call-block is a good example of that feature.
I do find the naming in Jinja2 very confusing though so it would probably be called something different
Investigating valuable ❔
https://tokio.rs/blog/2021-05-valuable
This should avoid an awful lot of cloning and would improve performances a lot. Still not clear whether it will work though.
Improving filters/tests/functions
They should be able to get values from the context implicitely the same way expression do so we don’t have to repeat things like
lang=lang
in Zola for example.Maybe a first argument which is something like
get_ctx: |key: &str| -> Option<Value>
?It would be nice if there was a way to define the arguments in a better way than the current macros too.
Related issue: #543
Also remove some weird things added to match Jinja2 like #650 (comment)
--
Any feedback / other items you want to change?
The text was updated successfully, but these errors were encountered: