Text templates with a small, forgiving language

Texl fills any text, from Markdown and HTML to configuration files and e-mails, with values computed by Swan: an expression language that is quick to learn and never stops with an error.

Template
<% order = {id: 1042, items: ["pen", "ink"], total: 18.5}
%>Order #<% order.id %>
Items: <% list.enum order.items %>
Total: $<% order.total %><% order.total > 15 ? " (free shipping)" %>
Output
Order #1042
Items: pen, ink
Total: $18.5 (free shipping)

Why Texl

Texl keeps templates readable: plain text, with a few tags where values go.

Any text format

Only the <% %> tags are special. Everything else is copied exactly, so Texl works for Markdown, HTML, YAML, source code and plain prose alike.

Expressions, not statements

Every tag is one Swan expression: values, functions, namespaces and tuples, with conditions and mapping instead of if and for.

Errors are values

An invalid operation produces an Undefined value instead of stopping the rendering, and tells you what went wrong when you inspect it.

Imports and libraries

Templates import other templates by relative path or URL, to share headers, footers and libraries of functions. Each one loads once.

Waits for data

A tag can return a promise, such as a document being fetched or an async host function, and the rendering waits for it.

One engine, many hosts

The engine is written in Rust and runs on the command line, in the browser through WebAssembly, and inside Python and Rust programs.

Meet Swan

Swan is the language inside the tags. It is small enough to learn in an afternoon.

  • Functions are values: double = x -> x * 2.
  • Tuples apply an operation to many values at once.
  • cond ? a ; b is all the branching you need.
  • Namespaces group data and functions.
  • A built-in library for text, lists, math and dates.

Take the tour of the language →

Swan
double = x -> x * 2
(1, 2, 3) => double            # => (2, 4, 6)

greet = name -> "Hi " + (name ; "there")
greet "Ada"                    # => "Hi Ada"
greet ()                       # => "Hi there"

point = {x: 3, y: 4}
math.hypot point.x point.y     # => 5

"a" - 1                        # => Undefined

Use it from

The template language is the same everywhere. Pick the host that fits your project.

The command line

Render template files from a terminal or a build script.

cargo install --git https://github.com/onlabsorg/texl texl-cli
texl report.texl -o report.md

JavaScript

Render templates in a web page, with your data and functions.

import init, { TexlEngine } from "@onlabsorg/texl";
await init();
const engine = new TexlEngine({ user: "Ada" });
await engine.render("Hi <% user %>!");

Python

Render templates in Python, with your data and async functions.

import asyncio, pytexl
engine = pytexl.Engine({"user": "Ada"})
asyncio.run(engine.render("Hi <% user %>!"))

Rust

Embed the engine, with your own loaders and host functions.

let engine = Engine::new(spawner);
engine.add_builtin("user", Value::Text("Ada".into()));
let text = engine.render("Hi <% user %>!").await;

A Node.js library is coming soon

Documentation

Everything you need, from your first template to the engine's internals.