Templating system
Sistine's main job is to take each page from the content directory and render it into a full HTML page using a template. Sistine's page templates are HTML files with extra template directives modeled after Ink's std.format
function. The rest of this page details this templating system, and how Sistine finds these templates.
Templating language and directives
Ink's templating language uses double curly braces like {{ these }}
to denote special instructions for the templating engine. If you must include double curly braces in your template to be displayed, escape the second brace, like {\{
, or use the HTML entity {
to denote a curly brace.
Sistine provides the following functions in a template.
Display a variable or property
{{ foo.bar }}
resolves to the value of bar
in the object foo
in the current parameter dictionary. For example, if the page parameters looked like this
{
title: 'Hello, World!'
foo: {
bar: [10, 20, 30]
baz: {
quux: 'Goodbye!'
}
}
}
The following are all valid.
{{ title }}
{{ foo.bar }}
(though this will print the raw list object){{ foo.bar.2 }}
(prints30
){{ foo.baz.quux }}
Accessing an undefined or null value will not error -- it will simply render an empty string. This behavior is nice for dealing with optional values, like page.draft
which may be usually false.
Conditional if/else expressions
{{ if foo }} X {{ else }} Y {{ end }}
renders X if value foo
is truthy, Y
otherwise. In determining truthiness, the following values and their string forms are considered false, and any other value is considered true:
0
''
()
{}
false
An idiomatic trick is to check {{ if page.some_list }}...{{ end }}
to check whether a list is empty.
Loops through a list or object values
The loop directive is a bit more complex. The full form looks like the following, where the parts in square brackets are optional.
{{ each foo [by bar] [asc|desc] [limit] }}
X
{{ else }}
Y
{{ end }}
If foo
is not empty, this directive loops through every value in the list or object foo
ordered by each item's property bar
and renders X for each value; if the list is empty, this renders Y. The asc|desc
declaration determines whether the sort is in ascending or descending order, and limit
is the optional, maximum number of items to be looped through, like a limit clause in SQL. They are optional, but a limit must follow an asc/desc declaration. For example, a common format for a reverse-chronological blog post listing page may include
{{ each page.pages by date desc }}
{{ -- post-listing -- }}
{{ else }}
<p>No posts yet.</p>
{{ end }}
Besides the properties that are normally a part of each value in the list, within each {{ each }}
section, a template has access to three special variables:
i
is the index in the loop, starting at 0*
is the template parameter immediately outside the loop, useful for accessing out-of-scope variables from within the loop like{{ *.site.name }}
_
is the parent value, i.e. the thing being iterated over
Escaping for HTML
{{ escape foo }}
escapes the value of variable foo
for HTML. This escapes at least <
and &
for safe display of HTML code.
Partial template embeds
Partial templates are defined by placing an HTML file into ./tpl/parts
. They are referred to by their base filename in other templates. Partial templates can refer to other partial templates, but normal templates cannot refer to other normal templates by their name. For example, to share a common header part across all templates, we may place a header.html
into the partial templates folder, then write
{{ -- header -- }}
This will invoke Sistine to search for this partial template in ./tpl/parts/header.html
. If one is not found, this directive will be ignored, but you'll see an error message from Sistine in the output.
Page template variables
All page templates are passed a dictionary with values for:
site
, containing site-wide metadata, imported from./config.json
page
, containing data about that specific page, including URLS, file paths, the rendered Markdown content, child and parent pages, and any other parameters defined for the page in the page's front matter
In general, a template begins rendering with these variables, plus any user-defined ones.
site {
name
origin
description
}
page {
path // URL of the page
publicPath // path to file in ./public
contentPath // path to file in ./content
content // compiled Markdown content
index? // true if is an index page, else false
pages: { name -> page } // for index pages, map of page names -> pages
roots: page[] // parent pages, from the root (/) page down, like breadcrumbs
}
The rss.xml
template is passed something slightly different. It's passed the site
variable just like others, and then pages
, a flat list of all the pages in the static site.
Template resolution and rendering rules
In every directory in ./content
, there are
- Folders, which are compiled to directories in
./public
of the same name and descendant pages - Files, which if named
index.md
are compiled toindex.html
and if not, are compiled to{{ filename }}/index.html
Every Sistine page renders once to a single template that is resolved in the following order of decreasing specificity.
- A template at the same directory path and name as the content file, ignoring file extensions.
- If an
index.md
file, the template with the name of the directory for which it is the index. For example,./tpl/foo.md
for./content/foo/index.md
. Non-index files skip this step. index.html
in the same directory path as the content file.tpl/index.html
, the root page template.
If no appropriate option is found after looking in these four places for any given content page, Sistine will generate an error for that page in the CLI output.