wire
import wire
Wire, an HTML template engine.
A Wire template is an HTML document. Not a document with a templating
language sprinkled through it, and not a string that happens to end up
looking like HTML: the source is parsed by the same
WHATWG parser the html
module uses, and every valid HTML5 document is already a valid Wire
template. What Wire adds are attributes on ordinary elements and values
written between {{ and }}.
Parsing rather than substituting is what makes the rest possible. Wire
knows that one interpolation sits in a paragraph, another in an href,
another in a <script>, and it escapes each of them for where it
actually is. A value can never turn into structure.
import wire
echo wire.render_string(
'<p>Hello {{ name }}</p>',
{ name: '<b>Ada</b>' }
)
# <p>Hello <b>Ada</b></p>
Templates from files
render_string() is convenient for a one-off and for reading
documentation. Real templates live in files under a root directory,
which is templates beside the working directory unless you say
otherwise:
import wire
var view = wire.wire()
view.set_root('./views')
echo view.render('pages/home', { user })
The .html extension is added when the path as written names no file,
so 'pages/home' finds pages/home.html. set_extension() changes
that.
Every path resolves inside the root and nowhere else. One that
climbs out with .., or names an absolute path elsewhere, is refused.
That matters the moment a path is built from anything a request
supplied.
Values
Anything between {{ and }} is an expression. The full language is in
wire.expression; in practice it reads the way you would guess:
<h1>{{ post.title }}</h1>
<p>{{ post.author.name|upper }}</p>
<p>{{ post.tags|join(', ') }}</p>
<p>{{ post.views > 1000 ? 'popular' : 'quiet' }}</p>
<p>{{ user.nickname ?? user.name }}</p>
A name that was never supplied is nil, and so is a key read off it, so
{{ user.address.city }} on a request with no user renders as nothing
rather than failing. Ask with x-if when the difference matters.
Writing {{ literally is %{{.
Filters
A value passed through | goes through a filter. They chain, and they
take arguments:
{{ name|trim|title }}
{{ total|number_format(2) }}
{{ summary|truncate(80, '…') }}
wire.filters lists the forty-odd that ship with Wire, and
register_filter() adds your own.
Escaping, and why there is no way to turn it off
Every interpolation is escaped for the place it lands in:
| Where it is | What happens |
|---|---|
| text between tags | &, < and > become references |
| an attribute value | & and " become references |
href, src, action | the URL’s scheme is checked as well |
<script>, onclick | the value is encoded as JSON |
<style> | anything outside a CSS-safe set is dropped |
A javascript: URL in an href becomes about:blank.
set_url_schemes() changes the allowlist when an application really
does need another scheme.
Inside a <script> the value carries its own quotes, so do not add any:
<script>
var user = {{ name }}; // right, renders as "Ada"
var user = '{{ name }}'; // wrong, renders as '"Ada"'
</script>
Markup you built yourself and want rendered as markup goes through the
raw filter, or arrives already wrapped in wire.safe():
<div class="body">{{ article.rendered_html|raw }}</div>
raw is a promise that the value is safe where it lands. Applying it to
anything a user supplied is how a template engine becomes a cross-site
scripting hole.
Directives
Wire’s own attributes all begin with x-. Wire owns that prefix
outright: an x- attribute it does not recognize is an error rather
than something that quietly does nothing, so x-fi is caught the first
time the template compiles.
A directive can go on any element, and the element it goes on is part of
what it controls. Put it on <template> when you want the control
without an element in the output; <template> is the one element HTML
lets through untouched anywhere, including inside <head>, a table and
a <select>, and Wire removes it.
Choosing
<p x-if="user.is_admin">Admin</p>
<p x-elif="user.is_staff">Staff</p>
<p x-else>Member</p>
x-elif and x-else attach to the element sibling in front of them,
with only whitespace and comments allowed in between. x-not is the
inverse of x-if and takes no chain.
Repeating
<li x-for="items" x-value="item" x-key="index">
{{ index }}: {{ item.name }}
</li>
The element repeats, not just its children. Lists, dictionaries, strings
and ranges all iterate; a list binds x-key to the position and a
dictionary to the key.
Every iteration also publishes a loop variable:
| Field | Is |
|---|---|
loop.index | the position counting from 1 |
loop.index0 | the position counting from 0 |
loop.first | true on the first pass |
loop.last | true on the last |
loop.length | how many entries there are |
loop.even, .odd | whether the position is even or odd |
loop.key, .value | this entry, whether or not it is bound |
loop.parent | the enclosing loop’s own loop |
x-loop="row" renames it, which is how two nested loops can both be
read without going through parent.
An x-if on a repeated element is asked once per iteration, so it can
read the loop’s variables. It cannot be continued with x-elif there.
Content
<p x-text="summary"></p>
<div x-html="article.body|raw"></div>
<a x-attr="{ href: post.url, rel: post.external ? 'nofollow' : nil }">…</a>
x-text and x-html replace an element’s children. x-attr spreads a
dictionary onto it, where true gives a valueless attribute and false
or nil leaves the attribute off.
Composition
Including
<include path="partials/header.html" />
<template x-include="partials/header.html"></template>
Those two are the same thing. <include>, <extend>, <declare>,
<define> and <super> are sugar, rewritten into the directive form
while the source is being tokenized, which is why they work inside a
<head> or a table where an unknown element would be moved or dropped.
A path is read as text, not as an expression, so it can be written plainly and computed where it needs to be:
<include path="themes/{{ theme }}/header.html" />
An include sees the variables around it. x-with adds to them and
x-only withholds everything else, which is what turns a partial into a
component with a real interface:
<include path="components/badge" x-with="{ label: 'New', tone: 'green' }" only />
Anything written inside an include is handed to it as the region called
content, so a partial can wrap what it was given:
<!-- components/card.html -->
<div class="card">
<h3>{{ title }}</h3>
<declare name="content"></declare>
</div>
<!-- the page -->
<include path="components/card" x-with="{ title: 'Totals' }">
<p>{{ orders|length }} orders</p>
</include>
Inheriting
A base template marks the regions a page may replace:
<!-- layouts/page.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<declare name="head"></declare>
</head>
<body>
<main>
<declare name="content"></declare>
</main>
<footer>
<declare name="footer">
<p>© {{ year }}</p>
</declare>
</footer>
</body>
</html>
A page fills them in:
<!-- pages/home.html -->
<extend base="layouts/page.html">
<define name="content">
<h1>Hello {{ user.name }}</h1>
</define>
<define name="footer">
<super />
<p>All rights reserved.</p>
</define>
</extend>
A region nobody defines renders whatever the base put inside it. <super />
renders the definition this one replaces, so a page can add to a footer
instead of restating it. Inheritance nests as deep as you like, and a
middle template can declare regions of its own.
One template extends one base, and everything in an extending template
has to be inside a <define>, because the base is what decides the
structure. Defining the same region twice needs override said out
loud:
<define name="content" override>…</define>
Extending Wire from Zuri
import wire
var view = wire.wire()
# A filter: the value comes first, then whatever the template passed.
view.register_filter('excerpt', @(value, words) {
var count = words == nil ? 25 : words
return ' '.join(value.split('/\\s+/').take(count)) + '…'
})
# A value every template can see without being handed it.
view.register_global('site_name', 'Example')
# A function a template calls.
view.register_global('route', @(name) {
return '/' + name
})
Used as:
<p>{{ post.body|excerpt(40) }}</p>
<a href="{{ route('about') }}">{{ site_name }}</a>
register_element() claims a tag name outright and hands every one of
them to a function, which is the escape hatch for anything the
directives cannot express.
Compiling and caching
A template is parsed once, not once per render. render() compiles on
first use and keeps the result, checking the file’s modification time
and size before reusing it.
That check is one stat per template per render, which is what you want
while writing templates and not what you want under load.
set_auto_reload(false) turns it off, and clear_cache() is how a
long-running process picks up a deployment.
Comments
HTML comments are server-side notes and do not reach the page:
<!-- This never ships, and neither does {{ secret }}. -->
Nothing inside one is evaluated. set_comments(true) keeps them when a
comment is genuinely meant for the browser, as a conditional comment or
a build marker is.
When something is wrong
Everything Wire raises is a WireError and carries the template and the
line and column of the tag at fault. TemplateSyntaxError comes from
compiling, RenderError from rendering, and TemplateNotFoundError
from a path that goes nowhere.
catch {
echo view.render('pages/home', { user })
} as e {
if instance_of(e, wire.WireError) {
echo 'template ${e.location()}: ${e.reason}'
}
}
The module is callable. wire(...) is the same call as
wire.wire(...), documented below.
The wire API
Every public name in wire, wherever it is declared. Each links to the
page that documents it.
| Name | Kind | Summary |
|---|---|---|
wire.RenderError | class | Raised while rendering, for anything that depends on the values a template was given: iterating something… |
wire.Safe | class | A string that is already markup and must be written out as it is. |
wire.TemplateNotFoundError | class | Raised when a template file cannot be found, or when it resolves to somewhere outside the root directory. |
wire.TemplateSyntaxError | class | Raised while compiling a template, for anything Wire can tell is wrong without rendering: an unknown… |
wire.Wire | class | A template engine: a root directory, a set of filters, and a cache of everything compiled so far. |
wire.WireError | class | The base class of every error Wire raises. |
wire.compile.Attribute | class | One attribute of an element. |
wire.compile.Branch | class | One arm of a conditional chain. |
wire.compile.Comment | class | An HTML comment that survived into the output. |
wire.compile.Compiler | class | Compiles one template’s source. |
wire.compile.Conditional | class | A chain of x-if, x-elif and x-else, or a lone x-if. |
wire.compile.Element | class | An element, with everything about it already worked out. |
wire.compile.Group | class | A run of instructions with nothing of its own, which is what a <template> carrying a directive leaves… |
wire.compile.Include | class | Another template rendered in this instruction’s place. |
wire.compile.Instruction | class | The base of every instruction, carrying the kind the renderer switches on and the place in the source it came… |
wire.compile.Loop | class | A repetition, from x-for. |
wire.compile.Raw | class | Characters written out exactly as they are, used for the doctype. |
wire.compile.Segment | class | One piece of a run of text or of an attribute value: either literal characters or an expression to evaluate. |
wire.compile.Slot | class | A region an extending template may replace, from x-slot. |
wire.compile.Super | class | The definition this one replaces, from x-super. |
wire.compile.Template | class | A compiled template, ready to render as many times as you like. |
wire.compile.Text | class | A run of text, possibly with interpolations in it. |
wire.compile.compile | function | Compiles source into a Template. |
wire.constants.ATTR_ATTR | constant | Spreads a dictionary of name/value pairs onto the element as attributes. |
wire.constants.CALL_CLOSE | constant | Closes a template function call. |
wire.constants.CALL_OPEN | constant | Opens a template function call. |
wire.constants.CARRIER_TAG | constant | The element Wire uses to carry a directive without emitting anything of its own. |
wire.constants.DEFAULT_EXTENSION | constant | The extension render() appends when the path it was given does not name an existing file and carries no… |
wire.constants.DEFAULT_LOOP_NAME | constant | The variable an x-for publishes its loop metadata under. |
wire.constants.DEFAULT_ROOT | constant | The directory render() resolves template paths against until set_root() says otherwise, which is a… |
wire.constants.DEFINE_ATTR | constant | Replaces the base template’s region of the same name. |
wire.constants.DIRECTIVES | constant | Every directive attribute Wire understands. |
wire.constants.DIRECTIVE_PREFIX | constant | The prefix every Wire directive attribute carries. |
wire.constants.ELIF_ATTR | constant | Continues an x-if chain on the next element sibling. |
wire.constants.ELSE_ATTR | constant | Closes an x-if chain on the next element sibling. |
wire.constants.ESCAPE_CHAR | constant | The character that escapes an interpolation, making Wire emit the braces literally instead of reading what is… |
wire.constants.EXPRESSION_DIRECTIVES | constant | The directives whose value is a Wire expression. |
wire.constants.EXTEND_ATTR | constant | Marks this template as extending the base template named by the value. |
wire.constants.FLAG_DIRECTIVES | constant | The directives that take no value at all. |
wire.constants.FOR_ATTR | constant | Repeats the element once per entry of the expression’s value. |
wire.constants.HTML_ATTR | constant | Replaces the element’s children with the expression’s value as markup, without escaping it. |
wire.constants.IF_ATTR | constant | Renders the element only when the expression is truthy. |
wire.constants.INCLUDE_ATTR | constant | Renders another template in this element’s place. |
wire.constants.KEY_ATTR | constant | Names the variable each x-for iteration binds its key or index to. |
wire.constants.LOOP_ATTR | constant | Renames the loop metadata variable an x-for publishes, which is called loop unless this says otherwise. |
wire.constants.MAX_DEPTH | constant | How deep x-include and x-extend may nest before Wire calls it a cycle. |
wire.constants.NAME_DIRECTIVES | constant | The directives whose value names a variable or a region, and is read as a bare identifier rather than… |
wire.constants.NOT_ATTR | constant | Renders the element only when the expression is falsy, the inverse of x-if. |
wire.constants.ONLY_ATTR | constant | Withholds the surrounding scope from an x-include or a component, leaving it only what x-with passes. |
wire.constants.OVERRIDE_ATTR | constant | Permits an x-define to replace a definition of the same name made earlier in the same template. |
wire.constants.PATH_DIRECTIVES | constant | The directives whose value is a template path, read as literal text with {{ }} interpolation allowed inside… |
wire.constants.PSEUDO_ELEMENTS | constant | The pseudo elements Wire accepts as sugar, each mapped to the directive it becomes and the attribute that… |
wire.constants.PSEUDO_FLAGS | constant | Attributes a pseudo element may carry that become valueless directives rather than the directive its name… |
wire.constants.SLOT_ATTR | constant | Declares a region an extending template may replace. |
wire.constants.SUPER_ATTR | constant | Renders the definition this one replaces. |
wire.constants.TEXT_ATTR | constant | Replaces the element’s children with the expression’s value as text. |
wire.constants.VALUE_ATTR | constant | Names the variable each x-for iteration binds its value to. |
wire.constants.VAR_CLOSE | constant | Closes an interpolation. |
wire.constants.VAR_OPEN | constant | Opens an interpolation. |
wire.constants.WITH_ATTR | constant | Supplies the variables an x-include or a component is rendered with. |
wire.escape.BLOCKED_URL | constant | What a blocked URL is replaced with. |
wire.escape.CONTEXT_ATTRIBUTE | constant | An ordinary attribute value. |
wire.escape.CONTEXT_SCRIPT | constant | A place the browser reads as JavaScript: the body of a <script>, or the value of an on* handler attribute. |
wire.escape.CONTEXT_STYLE | constant | The body of a <style> element. |
wire.escape.CONTEXT_TEXT | constant | Text between tags. |
wire.escape.CONTEXT_URL | constant | An attribute the browser resolves as a URL. |
wire.escape.DEFAULT_URL_SCHEMES | constant | The URL schemes Wire lets through by default. |
wire.escape.STYLE_SAFE | constant | The characters a value may contain inside a <style> body. |
wire.escape.URL_ATTRIBUTES | constant | The attributes whose value a browser resolves as a URL, and which therefore have to be checked for a… |
wire.escape.URL_LIST_ATTRIBUTES | constant | URL attributes holding a list of URLs rather than a single one. |
wire.escape.attribute | function | Escapes value for a double quoted attribute value, turning & and " into character references. |
wire.escape.attribute_context | function | The escaping context an attribute named name calls for. |
wire.escape.escape | function | Escapes value for the given context. |
wire.escape.is_script_attribute | function | Whether name is an event handler attribute, whose value a browser reads as JavaScript. |
wire.escape.is_url_attribute | function | Whether name is an attribute the browser resolves as a single URL. |
wire.escape.is_url_list_attribute | function | Whether name is an attribute holding a list of URLs. |
wire.escape.script | function | Escapes value for a place the browser reads as JavaScript, by encoding it as JSON and then hiding the… |
wire.escape.style | function | Escapes value for the body of a <style> element by dropping every character outside a conservative… |
wire.escape.text | function | Escapes value for text between tags, turning &, < and > into character references. |
wire.escape.text_context | function | The escaping context text inside an element named tag calls for. |
wire.escape.url | function | Escapes value for an attribute the browser resolves as a URL, replacing it with about:blank when its… |
wire.escape.url_list | function | Escapes value for an attribute holding several URLs, checking each entry’s scheme on its own. |
wire.escape.uses_descriptors | function | Whether a URL list attribute allows a descriptor after each URL, which srcset does and ping does not. |
wire.expression.Binary | class | An arithmetic or comparison operator. |
wire.expression.Call | class | A call, as in route('home') or user.display_name(). |
wire.expression.Conditional | class | condition ? consequence : alternative. |
wire.expression.DictLiteral | class | A dictionary literal. |
wire.expression.Expression | class | The base of every node in a parsed expression. |
wire.expression.Filter | class | A value passed through a filter, as in name|upper. |
wire.expression.Index | class | A bracketed lookup, as in items[index], where the key is itself an expression. |
wire.expression.KEYWORDS | constant | The words that are part of the language rather than names a template can bind. |
wire.expression.LONG_OPERATORS | constant | Operators made of more than one character, longest first so that <= is never read as < followed by =. |
wire.expression.ListLiteral | class | A list literal. |
wire.expression.Literal | class | A number, a string, or one of true, false and nil. |
wire.expression.Logical | class | and, or or ??, each of which decides whether to evaluate its right side after looking at its left. |
wire.expression.Member | class | A dotted lookup, as in user.name. |
wire.expression.Parser | class | Builds a syntax tree from an expression’s tokens. |
wire.expression.SHORT_OPERATORS | constant | Operators made of a single character. |
wire.expression.Token | class | One piece of an expression’s source: its kind, its value, and where in the expression it started. |
wire.expression.Unary | class | not x, !x or -x. |
wire.expression.Variable | class | A bare name, looked up in the variables the template was rendered with. |
wire.expression.parse | function | Compiles source into a syntax tree. |
wire.expression.tokenize | function | Splits source into tokens. |
wire.filters.BUILTIN | constant | Every filter Wire starts with, keyed by the name a template calls it by. |
wire.filters.abs | function | The value without its sign. |
wire.filters.capitalize | function | The value with its first letter in upper case and the rest left alone. |
wire.filters.ceil | function | The smallest whole number at or above the value. |
wire.filters.date | function | A date written out with the given format. |
wire.filters.default_to | function | fallback when the value is falsy, otherwise the value. |
wire.filters.empty | function | Whether the value has nothing in it. |
wire.filters.escape_value | function | Escapes a value for a context other than the one it is being written into, or escapes a value that was… |
wire.filters.filesize | function | A byte count written the way a person reads it. |
wire.filters.first | function | The first entry, or nil when there is none. |
wire.filters.floor | function | The largest whole number at or below the value. |
wire.filters.is | function | Whether the value equals expected. |
wire.filters.join | function | The entries joined into one string with glue between them. |
wire.filters.json | function | The value as JSON. |
wire.filters.json_script | function | The value as JSON wrapped in a <script type="application/json"> element, ready to be read back by a script… |
wire.filters.keys | function | A dictionary’s keys, in insertion order. |
wire.filters.last | function | The last entry, or nil when there is none. |
wire.filters.length | function | How many entries the value has. |
wire.filters.lower | function | The value in lower case. |
wire.filters.lpad | function | The value padded on the left with fill until it is width characters long. |
wire.filters.nl2br | function | The value with its line breaks turned into elements. |
wire.filters.not | function | Whether the value differs from expected. |
wire.filters.number_format | function | The value written out with thousands separated and a fixed number of decimal places. |
wire.filters.raw | function | Marks a value as markup so Wire writes it out without escaping it. |
wire.filters.repeat | function | The value repeated count times. |
wire.filters.replace | function | The value with every occurrence of search replaced by replacement. |
wire.filters.reverse | function | The entries in the opposite order, or a string backwards. |
wire.filters.round | function | The value rounded to places decimal places. |
wire.filters.rpad | function | The value padded on the right with fill until it is width characters long. |
wire.filters.slice | function | The entries from start up to but not including end. |
wire.filters.slug | function | The value as a lowercase, hyphen separated slug. |
wire.filters.sort | function | The entries in ascending order. |
wire.filters.split | function | The value split into a list on separator. |
wire.filters.strip_tags | function | The value with every HTML tag removed, leaving only its text. |
wire.filters.sum | function | The entries added together. |
wire.filters.title | function | The value with the first letter of every word in upper case and the rest in lower case. |
wire.filters.trim | function | The value with leading and trailing whitespace removed. |
wire.filters.truncate | function | The value cut down to length characters, with suffix put on the end when anything was actually cut. |
wire.filters.unique | function | The entries with later duplicates removed, keeping the first of each. |
wire.filters.upper | function | The value in upper case. |
wire.filters.url_encode | function | The value percent encoded for use inside a URL. |
wire.filters.values | function | A dictionary’s values, in insertion order. |
wire.is_safe | function | True when value is a Safe. |
wire.loader.Loader | class | Finds template files under one root directory. |
wire.normalize.Normalizer | class | A tokenizer that rewrites Wire’s pseudo elements on the way past. |
wire.normalize.parse | function | Parses source into a tree with Wire’s pseudo elements already rewritten. |
wire.render | function | Renders the template at path using the shared Wire. |
wire.render.Definition | class | One definition of a region, and what to render it against. |
wire.render.Frame | class | One level of variables, pointing at the level around it. |
wire.render.Renderer | class | Renders compiled templates. |
wire.render_string | function | Renders source using the shared Wire. |
wire.safe | function | Marks value as markup that Wire must not escape. |
wire.shared | function | The Wire behind the module-level render() and render_string(). |
wire.stringify | function | What value looks like once it reaches the page, before escaping. |
wire.truthy | function | Whether value counts as true in an x-if, an x-not, or a boolean operator inside an expression. |
wire.values.MAX_ARGUMENTS | constant | The most arguments a filter or a template function can be called with. |
wire.values.compare | function | Orders a before, with or after b, returning -1, 0 or 1. |
wire.values.invoke | function | Calls target with arguments spread into its parameters. |
wire.values.is_blank | function | Whether text is empty or is nothing but whitespace. |
wire.values.is_empty | function | Whether value has nothing in it, for the empty filter and for anything else that wants the question asked… |
wire.values.type_name | function | Wire’s name for value’s type, used in error messages so that a complaint reads “expected a list, got a… |
wire.values.unwrap | function | Strips the Safe wrapper off value, leaving anything else alone. |
wire.wire | function | A new Wire. |
Submodules
| Module | Reached as | Summary |
|---|---|---|
wire.compile | import wire.compile | Turning a parsed template into the instruction tree the renderer walks. |
wire.constants | wire.constants.* | The names Wire reserves: its directive attributes, the pseudo elements that are sugar for them, and the… |
wire.errors | wire.errors.* | The errors Wire raises, and the location information they carry. |
wire.escape | wire.escape.* | Turning a value into something safe to write at a particular place in a page. |
wire.expression | wire.expression.* | Wire’s expression language: the thing that sits between {{ and }}, and the thing an x-if or an x-for… |
wire.filters | wire.filters.* | The filters every Wire template starts with. |
wire.loader | import wire.loader | Turning the path written in an x-include into a file on disk, and refusing to when it points somewhere it… |
wire.normalize | wire.normalize.* | Rewriting Wire’s pseudo elements into something HTML’s own tree construction will not move, drop or reshape. |
wire.render | import wire.render | Walking a compiled template and writing the page out. |
wire.values | wire.values.* | How Wire reads the values a template is given: what counts as true, what a value looks like once it reaches… |
Functions
wire()
wire.wire(options: ?dict) -> Wire
A new Wire.
options takes any of root, extension, compact, comments,
auto_reload and url_schemes, each the same as the matching set_
method, so a whole configuration can be written in one place.
import wire
var view = wire.wire({
root: './views',
auto_reload: false,
})
Parameters
options(?dict)
Returns Wire
shared()
wire.shared() -> Wire
The Wire behind the module-level render() and render_string().
Configure this to use those without building your own, which is worth doing for a small program and not worth doing for anything that wants two different roots.
import wire
wire.shared().set_root('./views')
echo wire.render('pages/home', { user })
Returns Wire
render()
wire.render(path: string, variables: ?dict) -> string
Renders the template at path using the shared Wire.
Parameters
path(string)variables(?dict)
Returns string
Raises WireError
render_string()
wire.render_string(source: string, variables: ?dict, path: ?string) -> string
Renders source using the shared Wire.
Parameters
source(string)variables(?dict)path(?string)
Returns string
Raises WireError
Classes
Wire
class wire.Wire
A template engine: a root directory, a set of filters, and a cache of everything compiled so far.
One of these is normally built at startup, configured once, and used for the life of the process. Rendering does not change it, so it is safe to render from several places at once.
import wire
var view = wire.wire()
view.set_root('./views')
view.register_global('site_name', 'Example')
echo view.render('pages/home', { user })
Fields
| Field | Type | Description |
|---|---|---|
globals | Values every template can read without being handed them. | |
url_schemes | The URL schemes allowed in an href and its relatives. |
Constructor
wire.Wire(options: ?dict)
Parameters
options(?dict) — Any ofroot,extension,compact,comments,auto_reloadandurl_schemes, each the same as the matchingset_method.
Raises ArgumentError when an option is not of the type it should
be.
Wire.set_root()
wire.Wire.set_root(path: string) -> Wire
Sets the directory template paths resolve inside.
A relative path is taken from the working directory. The directory does not have to exist yet; a path under one that does not is reported as not found like any other.
Changing the root empties the cache, since the same path can now mean a different file.
Parameters
path(string)
Returns Wire
Raises ArgumentError
Wire.root()
wire.Wire.root() -> string
The directory template paths resolve inside, as an absolute path.
Returns string
Wire.create_root()
wire.Wire.create_root() -> bool
Creates the root directory when it does not exist, and reports whether it had to.
Wire never does this on its own. Creating directories is not a template engine’s business, and a typo in a root should read as a missing template rather than quietly produce an empty directory.
Returns bool
Raises Error when the directory cannot be created.
Wire.set_extension()
wire.Wire.set_extension(extension: string) -> Wire
Sets the extension tried when a path names no file as written.
It has to begin with a dot. render('home') then looks for home and
then for home plus this.
Parameters
extension(string)
Returns Wire
Raises ArgumentError when it does not begin with a dot.
Wire.set_compact()
wire.Wire.set_compact(compact: bool) -> Wire
Drops whitespace-only text from the output when compact is true.
This is the cheap kind of minification: the indentation between tags
goes, and nothing else is touched. Whitespace inside a <pre> is
whitespace-only only when the whole run is, so this can still change how
preformatted text reads; leave it off where that matters and reach for
html.minify() instead, which knows the difference.
Parameters
compact(bool)
Returns Wire
Raises ArgumentError
Wire.set_comments()
wire.Wire.set_comments(comments: bool) -> Wire
Keeps HTML comments in the output when comments is true.
Comments are dropped by default, which is what makes an HTML comment a server-side note. Nothing inside one is ever evaluated either way.
Parameters
comments(bool)
Returns Wire
Raises ArgumentError
Wire.set_auto_reload()
wire.Wire.set_auto_reload(reload: bool) -> Wire
Checks a cached template against its file before reusing it when reload is true, which is the default.
The check is one stat per template per render. Leave it on while
templates are being written and turn it off where throughput matters,
remembering that clear_cache() is then the only way a running process
notices a deployment.
Parameters
reload(bool)
Returns Wire
Raises ArgumentError
Wire.set_url_schemes()
wire.Wire.set_url_schemes(schemes: list) -> Wire
Replaces the URL schemes allowed in an href and its relatives.
A URL with no scheme is always allowed, so every relative link keeps
working whatever this is set to. A URL whose scheme is not listed is
replaced with about:blank.
The default is in escape.DEFAULT_URL_SCHEMES and deliberately leaves
out javascript, vbscript, data and file.
view.set_url_schemes(escape.DEFAULT_URL_SCHEMES + ['app'])
Parameters
schemes(list) — Scheme names without the colon, in lower case.
Returns Wire
Raises ArgumentError
Wire.register_filter()
wire.Wire.register_filter(name: string, handler) -> Wire
Registers a filter templates can use after a |.
The value being filtered arrives as the first argument and whatever the
template passed follows it. An argument the template left out arrives as
nil, so give it a default rather than insisting.
Registering a name that already exists replaces it, which is how an
application makes date mean its own thing.
view.register_filter('excerpt', @(value, words) {
var count = words == nil ? 25 : words
return ' '.join(value.split('/\\s+/').take(count)) + '…'
})
A filter returning a plain string has its result escaped like any other
value. One that genuinely produces markup returns wire.safe(), and is
then responsible for what is inside it.
Parameters
name(string)handler(callable)
Returns Wire
Raises ArgumentError
Wire.register_element()
wire.Wire.register_element(name: string, handler) -> Wire
Claims a tag name and hands every element of that name to handler instead of writing it out.
The handler is called with the Wire and a dictionary describing the
element:
| Key | Is |
|---|---|
name | the tag name |
attributes | its attributes, with every value already rendered and escaped |
content | its children, already rendered |
variables | the scope it was written in |
Return wire.safe(markup) to write markup, any other value to write it
as escaped text, or nil to write nothing at all.
view.register_element('icon', @(view, element) {
var name = element.attributes.get('name', 'dot')
return wire.safe('<svg class="icon"><use href="#${name}"></use></svg>')
})
Directives still work on a claimed element, so <icon x-if="…" />
behaves the way it reads. Reach for this only where the directives
genuinely cannot express something; a partial and x-include is easier
to read and does not need Zuri code to follow it.
Parameters
name(string)handler(callable)
Returns Wire
Raises ArgumentError
Wire.register_global()
wire.Wire.register_global(name: string, value) -> Wire
Makes value readable from every template by name.
A variable of the same name passed to render() wins, so a global is a
default rather than an override. A callable global is a function a
template can call.
Parameters
name(string)value(any)
Returns Wire
Raises ArgumentError
Wire.render()
wire.Wire.render(path: string, variables: ?dict) -> string
Renders the template at path and returns the page.
path is relative to the root and may leave off the extension. variables is what the template can read, and defaults to nothing at all.
echo view.render('pages/home', { user, posts })
Parameters
path(string)variables(?dict)
Returns string
Raises TemplateNotFoundError when the template does not exist or
is outside the root.
Raises TemplateSyntaxError when it does not compile.
Raises RenderError when rendering it fails.
Wire.render_string()
wire.Wire.render_string(source: string, variables: ?dict, path: ?string) -> string
Renders source as a template and returns the page.
path names the source in any error raised and defaults to <source>.
Includes and extends inside the source still resolve against the root.
The result is not cached, since there is no file to key it on. Use
render() for anything rendered more than once.
echo view.render_string('<p>{{ greeting }}</p>', { greeting: 'Hi' })
Parameters
source(string)variables(?dict)path(?string)
Returns string
Raises TemplateSyntaxError
Raises RenderError
Wire.compile()
wire.Wire.compile(path: string) -> Template
Compiles the template at path without rendering it, and returns it.
Useful for checking a directory of templates at build or startup time, so that a syntax error is found then rather than on the request that reaches it.
for name in os.list_dir('./views') {
view.compile(name)
}
Parameters
path(string)
Returns Template
Raises TemplateNotFoundError
Raises TemplateSyntaxError
Wire.clear_cache()
wire.Wire.clear_cache() -> Wire
Forgets every compiled template.
A long-running process with set_auto_reload(false) needs this to
notice a deployment.
Returns Wire
Wire.load()
wire.Wire.load(path: string, context, from: ?string, line: ?number, column: ?number) -> Template
The compiled template at path, from the cache when it is there and still current.
context names the element the template will sit inside, so that a partial holding table rows is parsed knowing it is going into a table. from, line and column describe where the include was written, for the error when the path goes nowhere.
The renderer calls this; there is rarely a reason to call it directly.
Parameters
path(string)context(?string)from(?string)line(?number)column(?number)
Returns Template
Raises TemplateNotFoundError
Raises TemplateSyntaxError
Wire.filter()
wire.Wire.filter(name: string) -> ?callable
The filter called name, or nil.
Parameters
name(string)
Returns ?callable
Wire.element()
wire.Wire.element(name: string) -> ?callable
The handler registered for the tag name, or nil.
Parameters
name(string)
Returns ?callable
2026, Richard Ore and The Zuri Contributors