http.sh/docs/template.md
2025-05-18 14:14:18 +02:00

7.1 KiB

HTTP.sh: template engine

We have a basic template engine! It's somewhat limited in capabilities compared to engines you might have previously used, but we're working on making it better :3

Note: the templates subdirectory in the HTTPsh repo is entirely unrelated to the template engine, and it will be removed in a future release. Please ignore it.

For practical examples, see the template examples page.

Tag schema

  • Tags always start with {{ and end with }}.
  • Tags can't include whitespace, outside of special iter/boolean tags defined below
  • Tag identifiers can contain letters, numbers, dashes and underscores ([a-zA-Z0-9_-]). Other characters may work but are NOT RECOMMENDED.
  • Tag identifiers are always prefixed by the tag type. This is also reflected in the code, outside of simple replaces which MUST skip the dot in the array assignment.
  • Identifiers are represented by <name> later in this document.

API

render <assoc_array> <file> [recurse]

The first param points to an associative array containing the replacement data. Second one points to a file containing the template itself (see section below for special usage). Third is optional, and controls whether render will recurse or not (this is mostly used internally, you likely won't ever need to set it).

File paths

Starting with HTTP.sh 0.97.1 (2025-05-18), paths in calls to render and include tags are relative to the namespace directory (usually app/). This behavior can be changed by defining an array called template_relative_paths:

template_relative_paths=(
	"${cfg[namespace]}/templates/neue_theme/"
	"${cfg[namespace]}/templates/default/"
)

The templating engine will check all the files in order and pick the first one that exists. This can be used to implement basic inheritance.

Before 0.97.1, paths were relative to the main HTTP.sh directory. Take care when upgrading, either fix the paths, or set template_relative_paths="./" to emulate the previous behavior.

Inline templates

For some purposes, it may be beneficial to store the template within the script file itself. This can be done either through passing /dev/stdin as a file name, or through inlining a file substitution, such as <(echo ...).

Simple replace

In the template {{.<name>}}
In the code array[<name>]="<value>"
Notes For your convenience, code representation skips the dot.

Important: to simplify your life (and protect your application), simple replaces ALWAYS use html_encode behind the scenes. This means that you're safe to assign any value to them without prior sanitization.

Raw replace

In the template {{@<name>}}
In the code array[@<name>]="<value>"

Same as a simple replace, but doesn't do html_encode. Useful if you want to guarantee unmangled output (for filling out hidden form values, etc.)

Template includes

In the template {{#<path>}}
In the code n/a

Template includes are special, in that you don't have to define them in the array. They get processed first to "glue together" one singular template.

The path starts at the root of your namespace (usually app/). This behavior can be changed, see section "File paths" above.

Warning: No recursion is supported within included templates; This means that you can't have an "include chain". Furthermore, some interactions between included templates and loops/ifs are a bit wonky; This will get ironed out at some point (sorry!)

Boolean if statements

In the template {{start ?<name>}} ... {{end ?<name>}}
In the template (alt.) {{start ?<name>}} ... {{else ?<name>}} ... {{end ?<name>}}
In the code array[?<name>]=_
Notes Can be used both inline and not. See examples page for more details.

Important: Currently, you can't have two checks for the same variable. If needed, set a second variable in the code and check for that. Fix TBD.

This is a boolean operator. The only supported mode of operation is checking whether a variable is set or not.

Loops

In the template {{start _<name>}} ... {{end _<name>}}
In the code array[_name]="<reference>"

Each loop extracts the area between start/end markers, and executes another render internally. You have to provide it with an "array of arrays", essentially an intermediate holding references. This is usually done through nested_declare <array> and nested_add <array> <temporary>.

Essentially, this boils down to:

nested_declare list # "array of arrays"
declare -A elem # temporary element
for i in {1..32}; do
	elem[item]="$i" # assign $i to the temporary element
	nested_add list elem # add elem to list; this creates a copy you can't modify
done
# once we have a full list of elements, assign it to the array passed to render
str[_list]=list

A more detailed usage description is available on the template examples page.

Leaky temporary array

You should excercise caution when handling the temporary arrays; Calling unset elem on the end of each loop may be a good idea if you can't guarantee that all of your elements will always have values. Otherwise, values from previous iterations may leak to the current one, potentially causing confusion.

Loop indexes

In the template {{-index}}
In the code n/a
Notes Doesn't resolve at all outside loops. Counter starts at 0 and gets incremented with every element.

Date pretty-printing

In the template {{+<name>}}
In the code array[+<name>]="<timestamp>"

This saves you from a few messy calls to date. Input is a UNIX timestamp.

The date format can be overriden by changing a config variable. Default is cfg[template_date_format]='%Y-%m-%d %H:%M:%S'.

URI slices

In the template {{-uri-<level>}}
In the code n/a
Notes Level must be a number. URI is always terminated with a slash, even if your last object is a file.

Takes the current URI path, and slices it using / as a delimeter, going from the left.

Given an URL http://localhost:1337/hello/world/asdf...

  • {{-uri-0}} -> /
  • {{-uri-1}} -> /hello/
  • {{-uri-2}} -> /hello/world/
  • {{-uri-3}} -> /hello/world/asdf/
  • {{-uri-4}} -> none (higher values are always empty)

This is very useful when creating menus; Instead of relying on hardcoded values, if the page is always on the same URI level, one can create links such as <a href="{{-uri-2}}meow">(...)</a>, which will always resolve to the same file; This eliminates a whole class of bugs where trailing slashes would break some poorly-written relative URLs.

Set statement

In the template {{-set-<name>}}
In the code n/a
Notes Very simple, processed out of order, nesting in conditional statements will not work.

If {{-set-<name>}} exists anywhere within your processed template (including the included templates), array[?<name>] will get set internally. This can be used to conditionally enable parts of another template based on what other templates are loaded.