djc / askama

Type-safe, compiled Jinja-like templates for Rust
Apache License 2.0
3.36k stars 217 forks source link

Support closures in templates #284

Open agraven opened 4 years ago

agraven commented 4 years ago

Currently working with Options can be rather frustrating and clumsy because of the lack of support for closures inside templates. Adding support for closures would significantly improve the library's usability.

djc commented 4 years ago

Full closure support would mean arbitrary Rust code in templates, which is at odds with my design direction. What could work is figuring out how filters might interact with Option's combinator methods.

Can you give a bit more context on your use cases?

agraven commented 4 years ago

The main use case is accessing a field of a struct inside an Option, so something like session_opt.map(|session| session.user)

djc commented 4 years ago

I'm having a hard time thinking of alternative syntaxes that could cover this use case well. What might work is having highly restricted support for closures, for example by only allow single expression bodies. I'm a bit worried about creating an uncanny valley effect where it's hard to know up front what is supported and what's not, but to some extent that probably can't be prevented.

agraven commented 4 years ago

Maybe something filter-like along the lines of value|option_field(field_name)? But single-expression closures does seems like a reasonable compromise that could be incredibly useful.

djc commented 4 years ago

Okay, I'm open to having single-expression closures. However, I probably won't be able to implement them anytime soon. If anyone else is interested, I'm happy to provided guidance and/or mentoring!

agraven commented 4 years ago

I would be interested in helping implement this

djc commented 4 years ago

Cool! Okay, so probably just start with the parser, implement a new expression variant Closure(&str, Box<Expression>) and the parser for it. Then, you'll need to add code generation, along the lines of a visit_closure() method (the compiler will point out where it needs to be wired up). I think the code generation itself will be straightforward, since you can mostly rely on the existing support for the nested expression, and the closure syntax itself is also simple.

Let me know if you have questions!

agraven commented 4 years ago

Sweet, sounds pretty approachable. I'll take a stab at it.

DJDuque commented 1 month ago

I think this is the issue I am having. I want to do

{% if my_vector.iter().filter(|c| condition(c)).count() > 1 %}

But this fails to compile. Is there a simple workaround? Or should I just implement this logic myself without using the closure?

djc commented 1 month ago

The simplest workaround would be to make a method on your context type that takes care of this logic, I think.