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
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 errorsource 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 errorAs template_render, nothing escaped: for plain-text emails and SMS.
template_render_file(dir: str, name: str, vars: any) → stror an errorThe 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.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/templates.qwer$ curl -O https://quillolang.org/plugins/files/templates.qweThen, in your program, all of it or only what you call:
use templates.qweruse template_render, template_render_text from templates // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
minijinja = { version = "3.0", default-features = false, features = ["builtins", "macros", "multi_template", "serde", "json", "urlencode", "fuel", "std_collections"] }Example
// 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 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())}Related
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.7 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.6 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.8 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.7 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.7 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.7 views · 6 downloadsThread 0 posts
On Templates: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.