As someone who spent way too much time building Django inspired template engines (Jinja, Jinja2, Twig, etc.) I must say this is really cool and very close to what I wanted to build myself for Rust but did not have the time.
> Tera uses serde which means that in the example above
Very good. But serde needs to get stable :(
> beautiful html output out of the box (ie no need for the {{- tags)
This is a request that comes up often but I tested this so much and always came back to not doing magic. It breaks too many applications in unintended ways (particularly plain text output).
> able to register new tags easily like the {% url ... %} in Django
I would not do that again. Jinja has an extension interface and I regret adding it. I much rather have people just expose functions to the templates. That said, in Rust that might be not a good idea because there are no keyword arguments so make it makes sense there.
> no macros or other complex logic in the template […] include partial templates
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 etc.
> This is a request that comes up often but I tested this so much and always came back to not doing magic. It breaks too many applications in unintended ways (particularly plain text output).
That was even the first feature request I got. I don't think I'll go more into magic than what it currently is, seems to be okay-ish
> I would not do that again. Jinja has an extension interface and I regret adding it. I much rather have people just expose functions to the templates. That said, in Rust that might be not a good idea because there are no keyword arguments so make it makes sense there.
That was my first thought on a better way to do template tags but not sure how to do that in Rust.
If anyone has an idea, there's https://github.com/Keats/tera/issues/23 now
Good point about macros, maybe I can have a look later. Macros and include feel like they fill the same spot in my mind in terms of features so I'd rather not have both.
> Good point about macros, maybe I can have a look later. Macros and include feel like they fill the same spot in my mind in terms of features so I'd rather not have both.
You could always repurpose includes as imports, i.e. not generate any output from imports, but to allow their use as purely an organizational tool for importing macros.
> Jinja has an extension interface and I regret adding it. I much rather have people just expose functions to the templates.
So, like Ruby's ERB, where you're passing a lexical binding? Or is that going a bit too far? (It's always felt a bit "magic" to me, but—imagining a version of ERB that compiled its templates to modules—the magic would basically have to be discarded at compile-time and turned into regular hash-lookups in order to get something with a sane runtime API anyway. So it'd be the "funny macro" kind of magic rather than the "bunch of runtime introspection" kind of magic.)
For the record, that's how Play's Twirl templates work. Templates are compiled (at compile time) to plain Scala singletons with an entry point method that builds the output.
I've been expermenting with Mustache under the hood lately, so I'm interested in template implementations. It seems odd, you've chosen an odd middle-ground of "support arithmetic" and "no real heavy logic".
Having played with the subject a few times, I've come to the general feeling that it's best to choose a hard line on one side or the other - either the mustache "pure logicless" or just do straight string interpolation (assuming the language provides a good syntax for multi-line string interpolation - C# almost does except that you have to escape all your quotes. Dunno if other languages to better).
Any particular reason why you chose to support some logic instead of none or all?
C#6 string interpolation is glorious, but if you use the multiline
var myString = $@"
Hey, string interpolation is nifty.
Look ma, no { this.Hands.ToString().ToUpper() }
More text.
But this is a ""quote"". It's ""ugly"" isn't it?
";
As you can see, you need to double your double quotes to escape them. Almost perfect. You can even nest string interpolations to do next template stuff with functional-programming loops and whatnot.
Neat! With facilities like that, Id be tempted to skip templating libraries altogether and just have a normal interface for templates and use the regular string expressions... Unless I needed then internationalized, and that's where Mustache shines, IMHO.
After using many templating systems over the years, I'm sold on the opposite approach of embedding (a description of) the output language in the host language, rather than trying to embed logic in the output language. Think React or Scalatags. Logic becomes trivial, because it's the logic of the host language. And the output can be the output language, directly, or some other data structure (VDOM, in the case of React).
It's similar to the concept of the free monad [1].
> Edit: Why spread the ugliness of "endif" and "endblock"? Why not just "end"? Why have the unnatural "elif" as well?
Not the developer but I did not remove endif for twig or jinja either coming from Django. Reasons for it: it becomes a lot more readable in the templates and it avoids lots of debug headache due to accidentally deleting things. It's already hard enough to debug template code sometimes but this way at least it's very quickly clear what the problem is.
What would you use instead of elif? `{% else if %}` is in no way nicer than `{% elif %}`. I picked `elif` in Jinja because it's what Python uses and that's where Jinja is at home.
You have to indent anyway. Then "endblock" becomes redundant. Also, you can't have a blank "{% elif %}" - it needs to be followed by a condition. I think a more natural and English-like syntax beats having to memorize deviations.
Not affiliated with the project, but my take on this is either have a really simple block syntax, like Smalltalk "[ block ... ]" (ed: Or use only indentation, like python), or have semantically meaningful start/end. To take eg: html, I always find stuff like:
<div class="navigation">
<div class="dropdown" id="misc"> ... </div>
(... imagine a few hundred lines here)
</div> <!-- text body? navigation? -->
... tends to get messy after a lot of editing and re-editing. Now this is just an example, with html5 we take a step back towards SGML/XML, and can use <article> so that </article> means "end article" (but which one?) and not "end navigation".
Stuff like this easily gets out of hand with complicated meta-programming/inclusion/"transclusion". Say you import a footer, and then needs to debug the output. What is that "}" supposed to close? (This is true both for generating eg: java code, and for generating various templates/document types).
Just for the sake of readability. At a previous job we had pretty huge templates and I would forget the name of the block by the time I reached the end
> Tera uses serde which means that in the example above
Very good. But serde needs to get stable :(
> beautiful html output out of the box (ie no need for the {{- tags)
This is a request that comes up often but I tested this so much and always came back to not doing magic. It breaks too many applications in unintended ways (particularly plain text output).
> able to register new tags easily like the {% url ... %} in Django
I would not do that again. Jinja has an extension interface and I regret adding it. I much rather have people just expose functions to the templates. That said, in Rust that might be not a good idea because there are no keyword arguments so make it makes sense there.
> no macros or other complex logic in the template […] include partial templates
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 etc.
But awesome to see this happen for Rust!