Oxiplate: Template engine for Rust
This project is experimental and features described in this book may not yet be implemented, or may be implemented in a different way.
Oxiplate is a template engine that uses a derive macro to compile templates into Rust code for the best performance. It focuses on straightforward escaping and powerful whitespace control.
Getting started
Include Oxiplate in your project:
cargo add oxiplate
Create a couple templates in the /template
directory:
<!-- /template/layout.html.oxip -->
<!DOCTYPE html>
<html>
<head>
<title>{{ title }} - {{ site_name }}</title>
</head>
<body>
<header>
<h1>{{ site_name }}</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
<!-- /template/index.html.oxip -->
{% extends "layout.html.oxip" %}
{% block content %}
<h1>{{ title }}</h1>
<p>{{ message }}</p>
{% endblock %}
Build the template and output it:
// /src/main.rs
use oxiplate::Oxiplate;
#[derive(Oxiplate)]
#[oxiplate = "index.html.oxip"]
struct Homepage {
site_name: &'static str,
title: &'static str,
message: &'static str,
}
fn main() {
let template = Homepage {
site_name: "Oxiplate Documentation"
title: "Oxiplate Example",
message: "Hello world!",
};
print!("{}", template);
}
Which should output something like:
<!DOCTYPE html>
<html>
<head>
<title>Oxiplate Example - Oxiplate Documentation</title>
</head>
<body>
<header>
<h1>Oxiplate Documentation</h1>
</header>
<main>
<h1>Oxiplate Example</h1>
<p>Hello world!</p>
</main>
</body>
</html>
Template introduction
The syntax for Oxiplate templates is similar to many other systems, but the terminology may be slightly different. Over the next several chapters, you'll be introduced to the terminology Oxiplate uses and what functionality templates built with it supports.
Templates are made up of two types of data: static text and tags.
Tags
Tags start with {
and end with }
with one or more characters between to define the type of tag and any contained logic.
Writs
Writs are expressions wrapped with {{
and }}
that will be evaluated and output into the template:
Hello {{ name }}!
Hello Luna!
Statements
Statements are wrapped with {%
and %}
and include variable assignments and control structures:
{% if user.is_some() %}<a href="/account/">Account</a>{% endif %}
<a href="/login/">Log In</a>
Comments
Comments are text wrapped with {#
and #}
that won't appear in the final template:
Hello world.{# Comments are ignored. #}
Hello world.
Whitespace control
All tags support the following whitespace control characters:
-
(U+002D HYPHEN-MINUS
) will remove all matched whitespace_
(U+005F LOW LINE
) will replace all matched whitespace with a single space (U+0020 SPACE
)
To adjust whitespace before the tag, the whitespace control character must be added immediately following the opening {{
, {%
, or {#
.
To adjust whitespace after the tag, the whitespace control character must be added immediately before the closing }}
, %}
, or #}
.
If no whitespace control character is present, the matched whitespace will be left as-is.
For example, a {{- "b" _}} c
would become ab c
.
Short tags
There are also a couple short tags available for controlling whitespace elsewhere in templates:
{-}
will remove all surrounding whitespace{_}
will replace all surrounding whitespace with a single space (U+0020 SPACE
)
For example:
<p>{-}
Hello {_}
world! {-}
</p>
will become:
<p>Hello world!</p>
Writs
A writ is an expression wrapped with {{
and }}
that will be evaluated and output into the template.
Hello {{ name }}!
Hello Luna!
But writs support any expression:
{{ a }} + {{ b }} = {{ a + b }}
1 + 2 = 3
No matter how complicated:
{{ (user.name() | upper) ~ " (" ~ (user.company() | lower) ~ ")" }}
CASPER (sloths and stuff, inc.)
Escaping
Eventually you'll likely want to escape user-provided text for safe usage within a markup language. Set a default escaper group and manually specify the escaper anywhere the default escaper for the group won't work:
escaper_groups.html.escaper = "::oxiplate::escapers::HtmlEscaper"
<a href="/{{ attr: user.username }}" title="Visit {{ attr: user.name }}'s profile">
{{ user.name }}
</a>
<a href="/toya_the_sequoia" title="Visit Isabelle "Cat & Mouse" Toya's profile">
Isabelle "Cat & Mouse" Toya
</a>
Read more about escaping in the next chapter.
Escaping
HTML escaping is on by default, so if a user provides this as their name in the example above:
<script>alert('oh no');</script>
It would be safely escaped (even if it may look pretty strange):
Hello <script>alert('oh no');</script>!
You can use a different escape method whenever you want, like for HTML attributes:
<a href="/{{ attr: handle }}" title="{{ attr: name }}">{{ name }}</a>
If you need to skip escaping, you can do that:
<aside>{{ raw: your_html }}</aside>
And if you want to be explicit, {{ name }}
and {{ text: name }}
are equivalent.
Escaping for other formats
Using Oxiplate to build TOML, JSON, XML, RTF, or [insert format here] files?
You can switch the default escaper for all of your files:
# /oxiplate.toml
default_escaper_group = "html"
Or switch it just for the document you're in:
unimplemented!("Syntax not yet implemented and subject to change!")
{% default_escaper_group json %}
{
"name": "{{ name }}",
"age": {{ number: age }},
}
Statements
Extends statements extend a template with the option to prepend, replace, append, or surround any block
from the parent template.
If statements add branching to templates with if
, else if
, and else
.
For statements bring iteration to templates with for
and else
.
Extending templates with extends
and block
Start with a template that contains one or more blocks:
{# layout.html.oxip -#}
<!DOCTYPE html>
<main>
{%- block content %}
<p>Parent content.</p>
{% endblock -%}
</main>
Then create a template to extend it:
{# your-content.html.oxip -#}
{% extends "layout.html.oxip" %}
This is essentially the same as using layout.html.oxip
directly:
<!DOCTYPE html>
<main>
<p>Parent content.</p>
</main>
Replace parent contents
You can choose to replace the contents of the parent block with a block with the same name:
{# your-content.html.oxip -#}
{% extends "layout.html.oxip" %}
+ {% block content %}
+ <p>Replaced content.</p>
+ {% endblock %}
<!DOCTYPE html>
<main>
- <p>Parent content.</p>
+ <p>Replaced content.</p>
</main>
For the same effect, you can be explicit with extends(replace)
:
{# your-content.html.oxip -#}
- {% extends "layout.html.oxip" %}
+ {% extends(replace) "layout.html.oxip" %}
{% block content %}
<p>Replaced content.</p>
{% endblock %}
Prefix parent contents
To prefix the contents of the parent, you can use extends(prefix)
:
{# your-content.html.oxip -#}
- {% extends "layout.html.oxip" %}
+ {% extends(prefix) "layout.html.oxip" %}
+
+ {% block content %}
+ <p>Prefix.</p>
+ {% endblock %}
<!DOCTYPE html>
<main>
+ <p>Prefix.</p>
<p>Parent content.</p>
</main>
Suffix parent contents
To suffix the contents of the parent, you can use extends(suffix)
:
{# your-content.html.oxip -#}
- {% extends "layout.html.oxip" %}
+ {% extends(suffix) "layout.html.oxip" %}
+
+ {% block content %}
+ <p>Suffix.</p>
+ {% endblock %}
<!DOCTYPE html>
<main>
<p>Parent content.</p>
+ <p>Suffix.</p>
</main>
Surround parent contents
To surround the contents of the parent, you can use extends(surround)
and {% parent %}
:
{# your-content.html.oxip -#}
- {% extends "layout.html.oxip" %}
+ {% extends(surround) "layout.html.oxip" %}
+
+ {% block content %}
+ <p>Prefix.</p>
+ {% parent %}
+ <p>Suffix.</p>
+ {% endblock %}
<!DOCTYPE html>
<main>
+ <p>Prefix.</p>
<p>Parent content.</p>
+ <p>Suffix.</p>
</main>
Branching with if
, else if
, and else
#[derive(Oxiplate)]
#[oxiplate = "template.html.oxip"]
struct YourStruct {
count: i64,
}
print!("{}", YourStruct {
count: 19,
});
<p>
{%- if count < 0 -%}
{{ count }} is negative
{%- elseif count > 0 -%}
{{ count }} is positive
{%- else -%}
{{ count }} is zero
{%- endif -%}
</p>
<p>19 is positive</p>
Iterating with for
and else
<ul>
{% for name in names %}
<li>{{ name }}
{% else %}
<li><em>No names found</em>
{% endfor %}
</ul>
Could produce something like:
<ul>
<li>Jasmine
<li>Malachi
<li>Imogen
</ul>
Or if names
was empty:
<ul>
<li><em>No names found</em>
</ul>
Expressions
TODO