Skip to content
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

Tracking issue for unsupported Jinja features #427

Open
3 of 19 tasks
vallentin opened this issue Jan 6, 2021 · 6 comments
Open
3 of 19 tasks

Tracking issue for unsupported Jinja features #427

vallentin opened this issue Jan 6, 2021 · 6 comments
Assignees

Comments

@vallentin
Copy link
Collaborator

vallentin commented Jan 6, 2021

This is a tracking issue of an excerpt of unsupported Jinja features, as specified in Jinja's Template Designer Documentation.
The goal is not to list all unsupported features, but mainly focus on features that are useful or otherwise fit in Askama.

Missing filters are not included, as these are already tracked in issue #18.

The included features, are those that seem feasible to implement in Rust.


  • loop.cycle("odd", "even") (Implement {{loop.cycle(…)}} similar to Jinja #517)
    • Which can currently be expressed with {% if loop.index0 % 2 == 0 %}odd{% else %}even{% endif %}
    • This could allow any amount of items to cycle, e.g. loop.cycle("foo", "bar", "baz", "qux"), which would make it less trivial to express with {% if %}
  • loop.length, loop.revindex, loop.revindex0
    • Could be implemented using len() from ExactSizeIterator, that way they are guaranteed to be exact
    • Support for any generic iterator, can remain, as long as ExactSizeIterator is only required, if loop.length, etc is used
  • loop.previtem, loop.nextitem
    • Could be implemented similarly to the previous, by instead requiring Clone
    • Jinja defines e.g. loop.previtem as undefined on the first iteration, so either it could become Option<T> or T if it implicitly unwrap()ed
  • loop.changed, "True if previously called with a different value (or not called at all)."
    • e.g. {% if loop.changed(entry.category) %}
    • Could be useful when splitting sections based on some change
  • {% for user in users if not user.hidden %} ( Implement for-else  #518)
    • Support for if in for could simplify template code
    • Could potentially also improve performance, by using filter() on the iterator
  • {% for ... %}{% else %}{% endfor %} ( Implement for-else  #518)
    • Support for else in for would simplify handling for loops with no iterations

  • {% if foo is defined %}
    • This is already mention in Tests support like: {% if foo is defined %} {{ foo }} {% endif %} #257. However, that proposes an is_some() check, along with an unwrap(), i.e. if let Some
    • Instead, to be more on par with Tera and Jinja, I instead propose that foo is defined is checked in the generator, and checks if the foo field exists in the template struct.
      • This could be useful for included and extended templates, where the same field isn't defined in all template structs
      • This would be limited to template struct fields, as it wouldn't be possible to inspect anything outside the template
  • Alternatively Tests support like: {% if foo is defined %} {{ foo }} {% endif %} #257, could be done with a some test instead, i.e. {% if foo is some %} would then translate into if let Some(foo) = &foo
    • Handling foo.bar is some might be more convoluted, as it would need to translate into if let Some(bar) = &foo.bar, while any subsequent use of foo.bar would need to be additionally translated to bar

  • Macros with default arguments, e.g. {% macro input(name, value="", type="text", size=20) %}
  • {% call ... %}{% endcall %} and caller() and inside macros

  • Filter sections, e.g. {% filter upper %}This text becomes uppercase{% endfilter %}
  • Block assignments, e.g. {% set foo %}Capture the contents of a block{% endset %}

  • Concat operator (~), e.g. {{ "foo " ~ 2 }}, which would translate to format!("{}{}", "foo ", 2)
    • The concat operator could be useful in the context of calls, e.g. {{ self::foo("bar " ~ 2) }}, which otherwise wouldn't be feasible to do, this case might come up, if someone has a url_for function, with a path created dynamically from fields

  • Line Statements
    # for item in seq
        <li>{{ item }}</li>  ## this is comment
    # endfor
@vallentin vallentin self-assigned this Jan 6, 2021
@vallentin vallentin pinned this issue Jan 6, 2021
@djc djc unpinned this issue Jan 7, 2021
@djc
Copy link
Owner

djc commented Jan 7, 2021

I unpinned both of these issues again. In my opinion, matching Jinja feature-for-feature is a non-goal. I'd prefer to build something smaller and only add features if there are requests for features with a clear use case: making something much easier to express than it would otherwise be, in a way that can't be done without adding a feature to the core parser/codegen.

Additionally, I value that the transformation from template to Rust code is easy to understand. While Python actually has an is keyword, Rust does not. Neither does Rust support keyword arguments. While we could plausibly make this work inside templates, I'm not really inclined to accept changes in this direction.

@vallentin
Copy link
Collaborator Author

I pinned both, as then it would be quicker to find them for me. Finding the filters issue, requires browsing through the issues.

Overall I agree with the sentiment. I've removed keyword arguments from the issue. However, I do see an exception with is defined. Having an is defined evaluate based on whether a field exists. This would make it easier to include and extend templates. For instance you could have a base template with e.g. {% if css is defined %}. Then all templates can still reuse the base template regardless of whether they have a css field or not.

With that said, I'm not saying all tests necessarily have to be implemented, as e.g. {% if foo.is_some() %} is more trivial. However, specifically is defined I do see as useful.


Personally, I do have uses for loop.cycle("odd", "even") and loop.changed(entry.category).

Ultimately you decide. I feel it would be easier if you edited the issue, and just removed the ones you don't want. Then I know which I should and shouldn't spent time implementing. 😅

@djc
Copy link
Owner

djc commented Jan 7, 2021

My issue with pinning these is that it could convey a signal to others browsing the issues that matching Jinja feature for feature is a goal, which I'd prefer to discourage.

I'm fine with having line statements, is defined, loop.cycle() and loop.changed().

@vallentin
Copy link
Collaborator Author

Ah. From a perspective that someone might only read the title, then yeah, that's understandable.

Alright, I'll look into implementing these when I have time.

@teenjuna
Copy link

teenjuna commented Sep 3, 2023

Hey there. Thanks for you work! Are there any plans on implementing this?

Macros with default arguments, e.g. {% macro input(name, value="", type="text", size=20) %}

@djc
Copy link
Owner

djc commented Sep 3, 2023

Not that I'm aware of. I'd be happy to guide you if you want to take a stab at it, though. Right now I don't remember much context about how macros work, but I think macro definitions are stored in the Context: https://github.com/djc/askama/blob/main/askama_derive/src/heritage.rs#L43 so you should be able to look at storing the defaults in the Macro type (https://github.com/djc/askama/blob/main/askama_parser/src/node.rs#L443 -- maybe make args a Vec<(&'a str, &'a str)>?) and, for calls, insert the defaults from there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants