--- 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) ```