Text

Templates

Jinja templates your users can edit for emails, invoices and pages: sandboxed, escaped for HTML, with filters such as money and truncate, and checked before they are saved.

Version 1.0.0 · 4 functions · 1 crate

#templates#email#html#text

Functions

What use templates.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use template_render, template_render_text from templates (Quillo 0.6).

template_render(source: str, vars: any) → stror an error

source rendered with vars (a map), for HTML: values are escaped, so <b> in a name shows as text; {{ x|safe }} puts HTML in as it is. Filters: Jinja's (upper, title, default, join, round, length, tojson, ...) and money ("{{ total|money('€') }}" is €1,234.50) and truncate(n). A missing variable prints nothing; an error says its line.

template_render_text(source: str, vars: any) → stror an error

As template_render, nothing escaped: for plain-text emails and SMS.

template_render_file(dir: str, name: str, vars: any) → stror an error

The template name from folder dir, rendered with vars; it may {% extends %} and {% include %} others in that folder (never outside it). Names ending in .html, .htm or .xml are escaped as HTML.

template_check(source: str) → str?

What is wrong with a template's syntax, with its line, or null when it reads well: check a user's template before saving it.

Get it

$ quillo install templates               # templates.qwer into this folder$ quillo install templates --example     # and its example, templates.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/templates.qwer$ curl -O https://quillolang.org/plugins/files/templates.qwe

Then, in your program, all of it or only what you call:

main.qwe
use templates.qweruse template_render, template_render_text from templates      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
minijinja = { version = "3.0", default-features = false, features = ["builtins", "macros", "multi_template", "serde", "json", "urlencode", "fuel", "std_collections"] }

Example

templates.qwe
// Order emails from templates a shop owner can edit, checked before they are saved.////   quillo run templates.qwe      then open http://localhost:8080/emailuse "templates.qwer"let order = {    customer: "Ada <Lovelace>",    number: 1042,    items: [{ name: "Notebook", qty: 2, price: 4.5 }, { name: "Fountain pen", qty: 1, price: 1225 }],    total: 1234.5,}let html_email = "<h1>Thanks, {{ customer }}!</h1><p>Order #{{ number }}:</p><ul>{% for i in items %}<li>{{ i.qty }} × {{ i.name|upper }}: {{ (i.qty * i.price)|money('€') }}</li>{% endfor %}</ul><p>Total: <b>{{ total|money('€') }}</b></p>"let text_email = "Hi {{ customer }}, your order #{{ number }} of {{ items|length }} items comes to {{ total|money }}."route GET /email = respond(200, template_render(html_email, order)?, { "content-type": "text/html; charset=utf-8" })route GET /text = { text: template_render_text(text_email, order)? }// What a shop owner typed, checked before it is kept.route POST /check = { problem: template_check(request.body) }route GET /broken = { problem: template_check("Hello {{ name }\n{% for x in %}") }
$ quillo run templates.qwe$ curl 'localhost:8080/email'$ curl 'localhost:8080/broken'

Source

templates.qwer
//! Templates your users can edit, for emails, invoices and pages: Jinja syntax ({{ name }}, {% for %}, filters), sandboxed so a template cannot read files or run for ever.//! version: 1.0.0//!//! [dependencies]//! minijinja = { version = "3.0", default-features = false, features = ["builtins", "macros", "multi_template", "serde", "json", "urlencode", "fuel", "std_collections"] }use minijinja::value::Serde;use minijinja::{path_loader, AutoEscape, Environment};fn env(escape: bool) -> Environment<'static> {    let mut e = Environment::new();    // A template may run 2 million steps: enough for long lists, not for an endless loop.    e.set_fuel(Some(2_000_000));    e.set_recursion_limit(100);    e.set_auto_escape_callback(move |_| if escape { AutoEscape::Html } else { AutoEscape::None });    e.add_filter("money", |v: f64, symbol: Option<String>| -> String {        let s = format!("{:.2}", v.abs());        let (whole, cents) = s.split_once('.').unwrap_or((&s, "00"));        let mut grouped = String::new();        for (i, c) in whole.chars().enumerate() {            if i > 0 && (whole.len() - i) % 3 == 0 {                grouped.push(',');            }            grouped.push(c);        }        format!("{}{}{}.{}", if v < 0.0 { "-" } else { "" }, symbol.unwrap_or_else(|| "$".into()), grouped, cents)    });    e.add_filter("truncate", |s: String, n: Option<usize>| -> String {        let n = n.unwrap_or(80);        if s.chars().count() <= n { s } else { s.chars().take(n.saturating_sub(1)).collect::<String>() + "…" }    });    e}fn limit(source: &str) -> Result<(), String> {    if source.len() > 500_000 { Err("templates up to 500 KB".into()) } else { Ok(()) }}/// `source` rendered with `vars` (a map), for HTML: values are escaped, so `<b>` in a name shows as text; `{{ x|safe }}` puts HTML in as it is. Filters: Jinja's (`upper`, `title`, `default`, `join`, `round`, `length`, `tojson`, ...) and `money` ("{{ total|money('€') }}" is €1,234.50) and `truncate(n)`. A missing variable prints nothing; an error says its line.pub fn template_render(source: &str, vars: serde_json::Value) -> Result<String, String> {    limit(source)?;    env(true).render_str(source, Serde(vars)).map_err(|e| e.to_string())}/// As `template_render`, nothing escaped: for plain-text emails and SMS.pub fn template_render_text(source: &str, vars: serde_json::Value) -> Result<String, String> {    limit(source)?;    env(false).render_str(source, Serde(vars)).map_err(|e| e.to_string())}/// The template `name` from folder `dir`, rendered with `vars`; it may `{% extends %}` and `{% include %}` others in that folder (never outside it). Names ending in .html, .htm or .xml are escaped as HTML.pub fn template_render_file(dir: &str, name: &str, vars: serde_json::Value) -> Result<String, String> {    let mut e = env(true);    e.set_auto_escape_callback(|n| if n.ends_with(".html") || n.ends_with(".htm") || n.ends_with(".xml") { AutoEscape::Html } else { AutoEscape::None });    e.set_loader(path_loader(dir.to_string()));    let t = e.get_template(name).map_err(|e| e.to_string())?;    t.render(Serde(vars)).map_err(|e| e.to_string())}/// What is wrong with a template's syntax, with its line, or null when it reads well: check a user's template before saving it.pub fn template_check(source: &str) -> Option<String> {    if source.len() > 500_000 {        return Some("templates up to 500 KB".into());    }    env(true).template_from_str(source).err().map(|e| e.to_string())}
HTML to text html_text.qwer · 1.0.0HTML as readable plain text for the text part of an email: headings, lists and tables laid out, links listed at the end, and one-line previews.emailhtmltextexport7 views · 7 downloadsHyphenation hyphenation.qwer · 1.0.0Words hyphenated by the rules of 80 languages and spellings, patterns built in: a word's syllables, soft hyphens put in text and HTML for narrow columns and e-mails, and text wrapped at a width with words broken where they may be.textlanguagesemailwriting6 views · 7 downloadsReadability readability.qwer · 1.0.0A web page's article without its menus, ads and footer: title, author, date, clean HTML, plain text or Markdown, for read-later apps, link previews and summaries.htmlscrapingtextmarkdownimport8 views · 7 downloadsText wrapping text_wrap.qwer · 1.0.0Text wrapped and filled at a width for plain-text emails and terminals: paragraphs refilled, hanging indents for lists, and > quoting for replies.emailtextterminalexport7 views · 6 downloadsTypography typography.qwer · 1.0.0Typed text set as a typographer would, by language: curly quotes (“ ”, « », „ “) and apostrophes, en and em dashes, ellipses, ×, ©, French spaces before ; : ! ?, and no widows; in plain text or HTML, code left alone.writingtexthtmllocale7 views · 10 downloadsCSS inliner css_inline.qwer · 1.0.0CSS inlined into HTML for email: style blocks and extra CSS written into each element's style attribute, with nothing ever fetched.emailcsshtmltemplates7 views · 6 downloads
Thread
78Useful? Like it, or add to its thread below.

Thread 0 posts

On Templates: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.