---
title: "tera"
vignette: >
%\VignetteIndexEntry{tera}
%\VignetteEngine{quarto::html}
%\VignetteEncoding{UTF-8}
---
This vignette walks through some of the basics of rendering templates with Tera
in R. Users mainly interact with a Tera R6 object, which serves as a template
library with encapsulated methods for rendering templates with a given context.
A templating engine requires two things:
- a `template`, as you may have guessed, that includes variables and rendering
logic describing where and how to inject data, and
- a `context`, or a set of variables and values to be injected into the
template.
Templating syntax is described in the documentation for [Tera](https://keats.github.io/tera/).
```{r}
#| label: setup
library(tera)
```
## Usage
To get a feel for what `tera` can do, let's start with a simple "hello world"
example.
```{r}
#| label: hello-world
tera <- Tera$new()
tera$render_string(
'
Hello {{ x }}. This is {{ y }}.
',
x = "world",
y = "tera"
)
```
The syntax and API should look pretty familiar to anyone who has used `glue` to
do something like `glue::glue("Foo { x }", x = "bar")`. The big difference is
the object-oriented workflow.
## Initializing `Tera`
Everything in `tera` revolves around the `Tera` object, which serves as a
template library with encapsulated rendering methods. In the above example, we
initialize a `Tera` engine with an empty template library by calling `Tera$new()`
with no arguments.
If you have a complicated directory system with nested templates and inheritance
patterns - a common situation for web development, you may find it easier to
initialize an `tera` by specifying the directory with a glob pattern. Suppose,
for example, that you have a website directory that looks like this:
```{r}
#| label: directory
template_dir <- system.file("templates", package = "tera")
cat(
"website",
list.files(template_dir, recursive = TRUE),
sep = "\n- "
)
```
You can generate a new `Tera` around this directory like so
```{r}
#| label: glob
#| collapse: true
tera <- Tera$new(file.path(template_dir, "**/*.html"))
tera
```
## Adding templates
Templates can be added from file or string.
```{r}
#| label: add-templates
#| collapse: true
# add templates manually from file
tera <- Tera$new()
tera$add_file_templates(
"base.html" = file.path(template_dir, "base.html"),
"index.html" = file.path(template_dir, "index.html")
)
tera
# add templates manually from string
tera$add_string_templates(
img = '
'
)
tera
```
If you initialize a tera instance with a glob and then add more file templates
to the template directory, you can reload with the glob to catch the new
templates.
```{r}
#| label: reload
#| collapse: true
example_dir <- file.path(tempdir(), "tera-templates")
dir.create(example_dir)
file.copy(
from = file.path(template_dir, "base.html"),
to = file.path(example_dir, "base.html")
)
tera <- Tera$new(file.path(example_dir, "*.html"))
tera
file.copy(
from = file.path(template_dir, "index.html"),
to = file.path(example_dir, "index.html")
)
tera$reload()
tera
```
Reset to continue with built-in template examples.
```{r}
#| label: reset-tera-instance
tera <- Tera$new(file.path(template_dir, "**/*.html"))
```
## Rendering basics
Generally speaking, rendering a template involves supplying it with a `context`,
or a set of key-value pairs, with the keys being the variable names - surrounded
by `{{{ variable }}}` in the template - and their values being the content to
inject into the template. You can render a template to string or to a file.
Consider our hypothetical website's index template:
```{r}
#| label: index-template
template_dir |>
file.path("index.html") |>
readLines(warn = FALSE) |>
cat(sep = "\n")
```
This has three variables: `{{{ title }}}`, `{{{ p }}}`, and `{{{ owner }}}`. You
can render this template to a string by passing it a context, a set of values
for those variables. Here we render the template to a string.
```{r}
#| label: render-to-string
string <- tera$render_template(
"index.html",
title = "This is my blog",
p = "Welcome to my awesome homepage.",
owner = "Blake"
)
cat(string)
```
You can also render a template to file by specifying `outfile`.
```{r}
#| label: render-to-file
outfile <- file.path(tempdir(), "rendered-index.html")
tera$render_template(
"index.html",
title = "This is my blog",
p = "Welcome to my awesome homepage.",
owner = "Blake",
outfile = outfile
)
cat(readLines(outfile, warn = FALSE), sep = "\n")
```
And you can render a specific block in a template:
```{r}
#| label: render-block
string <- tera$render_template(
"index.html",
title = "This is my blog",
p = "Welcome to my awesome homepage.",
owner = "Blake",
block = "content"
)
cat(string)
```
You can also render individual components defined in one of your templates, for
example
```{r}
#| label: render-component
string <- tera$render_component(
"widget",
title = "foo",
body = "This is a widget!
"
)
cat(string)
```
And you can bypass library templates altogether and pass a template string
directly
```{r}
#| label: render-string-template
string <- tera$render_string(
'
',
img_src = "foo/bar.svg"
)
cat(string)
```
Two helper functions are also provided if you want to render a one-off template
without going throught he process of initializing a tera instance. The
`render_template()` function will render a template file, and `render_string()`
will render a string, same as the methods, but without the explicit tera
instance.
## Inspecting the library
There are some tools for inspecting the library. You can get a list of
templates and components in the library and a list of variables in a specific
template.
```{r}
#| label: inspection
#| collapse: true
tera$templates()
tera$components()
tera$variables("index.html")
```
There is also the print method, which provides some of this information.
```{r}
#| label: tera-print
#| collapse: true
tera$print()
```
## Rendering logic
The `Tera` templating engine offers a lot of additional functionality, like
control flow and data manipulation. For example, the blog post template shows
how to construct a for loop and apply built-in filters and functions.
```{r}
#| label: for-loop
template_dir |>
file.path("blog", "post.html") |>
readLines(warn = FALSE) |>
cat(sep = "\n")
```
In `{{{ now() | date(format="%Y-%m-%d") }}}`, `now()` is a function that returns
the current date and time. It's returned value is then piped to the `date()`
filter, which provides formatting options. The template also has the for-loop
construction `{$ for product in products %}` that allows for looping over the
elements of a product table or array. When passed a data.frame, we get this:
```{r}
#| label: render-logic
products <- data.frame(
name = c("apple", "banana", "orange"),
price = c(0.25, 0.4, 0.5)
)
string <- tera$render_template(
"blog/post.html",
title = "Fruit prices",
products = products
)
cat(string)
```
## Autoescape
Tera escapes HTML by default:
```{r}
#| label: autoescape
string <- tera$render_string(
"{{ html }}",
html = ""
)
cat(string)
```
For one-off rendering like the above, the function takes an autoescape argument.
```{r}
#| label: autoescape-arg
string <- tera$render_string(
"{{ html }}",
html = "",
autoescape = FALSE
)
cat(string)
```
For rendering templates and components in the library, you can turn off
autoescape globally using `$autoescape_off()` and turn it back on with
`$autoescape_on()` (it is on by default).
```{r}
#| label: autoescape-toggle
tera$autoescape_off()
tera$autoescape_on()
```
## Delimiters
Templates are marked up with `{% blocks %}`, `{{{ variables }}}`, and
`{# comments #}`. You can change these with `$set_delimiters()`, though only
on an engine with an empty template library, so it must be done before any
templates are added.
```{r}
#| label: set-delimiters
alt <- Tera$new()
alt$set_delimiters(
variable_start = "<<",
variable_end = ">>"
)
alt$render_string("<< greeting >>, world!", greeting = "Hello")
```
Each start delimiter must differ from the others, each delimiter must be exactly
two bytes long, and any delimiter left `NULL` is unchanged. Use `$delimiters()`
to see the current set.
```{r}
#| label: delimiters
alt$delimiters()
```
## Inheritance
Templates can inherit content from each other in one of two ways, either using
`include` or, for more complicated inheritance, `extends`.
```{r}
#| label: include
string <- tera$render_string(
'{%- include "index.html" -%}',
title = "This is my blog",
p = "Welcome to my awesome homepage.",
owner = "Blake"
)
cat(string)
```
The extension mechanism is a little more involved, requiring that you specify
content blocks where content from a child document should be injected. We have
actually been using this method in the examples already. Our current library has
`base.html`:
```{r}
#| label: base.html
template_dir |>
file.path("base.html") |>
readLines(warn = FALSE) |>
cat(sep = "\n")
```
Notice it has two `{% block ... %}`. This is where a child document inserts
content. And here is `index.html`:
```{r}
#| label: index.html
template_dir |>
file.path("index.html") |>
readLines(warn = FALSE) |>
cat(sep = "\n")
```
Notice it has `{% extends "base.html" %}`. This makes it a child document of
`base.html`.
```{r}
#| label: extends
tera <- Tera$new(file.path(template_dir, "**/*.html"))
string <- tera$render_template(
"index.html",
title = "This is my blog",
p = "Welcome to my awesome homepage.",
owner = "Blake"
)
cat(string)
```