Expand description
htmpl is a library for generating HTML files from HTML templates.
§TL;DR
const TEMPLATE : &str = r#"
<htmpl-query name="q">SELECT name, uuid FROM users;</htmpl-query>
<htmpl-foreach query="q">
<htmpl-insert query="q(uuid)" ></htmpl-insert> <htmpl-insert query="q(name)" ></htmpl-insert></htmpl-foreach>
"#;
let result = htmpl::evaluate_template(TEMPLATE, &conn).unwrap();
assert_eq!(result.trim(),
"18adfb4d-6a38-4c81-b2e8-4d59e6467c9f cceckman
6de21789-6279-416c-9025-d090d407bc8c ddedkman");htmpl evaluates a template in the context of a database.
The template is an HTML document, with custom htmpl- elements
that describe how to alter document
based on data from the database.
The resulting altered document is the output of evaluation.
The htmpl- elements are:
htmpl-query: executes a query on the database and saves the resultshtmpl-insert: inserts a value from a previous query into the outputhtmpl-foreach: repeats a portion of the input template for each row of a previous queryhtmpl-attr: adds an attribute to selected HTML nodeshtmpl-if: conditionally outputs its content
Between SQL queries[^sqlite] in htmpl-query, and the rest of the elements,
you can generate a lot (maybe any?) HTML. The only limit is your imagination.
§Details
htmpl walks down the DOM tree recursively. Each step down the tree introduces a new scope in which query results can be bound to names.
When htmpl encounters an htmpl- element during this walk,
it evaluates it, per the below.
§htmpl-query
pub const CCECKMAN_UUID: &str = "18adfb4d-6a38-4c81-b2e8-4d59e6467c9f";
conn.execute(
r#"INSERT INTO users (uuid, name) VALUES (?, ?), (?, ?)"#,
rusqlite::params![CCECKMAN_UUID, "cceckman", OTHER_UUID, "ddedkman"],
).unwrap();
const TEMPLATE : &str = r#"
<htmpl-query name="const_uuid">
SELECT "18adfb4d-6a38-4c81-b2e8-4d59e6467c9f" AS uuid_value;
</htmpl-query>
<htmpl-query name="get_name" :uuid="const_uuid(uuid_value)" >
SELECT name FROM users WHERE uuid = :uuid;
</htmpl-query>
<htmpl-insert query="get_name(name)" ></htmpl-insert>
"#;
let result = htmpl::evaluate_template(TEMPLATE, &conn).unwrap();
assert_eq!(result.trim(), "cceckman");The content of the htmpl-query element is the SQL query.
The query may have parameters.
For each parameter in the query, htmpl will look for an attribute
on the htmpl-query element with the same name as the parameter.
(We recommend colon-prefixed names, e.g. :hello,
as they are valid parameter names and valid attribute names.)
In addition, the name attribute gives a name to the query’s results.
Note the above example also demonstrates how to generate “constants”
– in this case, the UUID in the const_uuid query.
§htmpl-insert
-
queryattribute names a selectorTODO: I should rename this, it’s not a query
-
Stringifies that value
- “string” affinity: just inserts the string
- “integer” affinity: base-10
- “real” affinity: ??? (Rust default format)
- “byte” affinity: comma-separated hex bytes (let me know if you want something more sensible!)
§Selectors {#selector}
TODO: Rename; need to distinguish CSS selectors.
Selectors are how data makes it back from SQL to the HTML document. A selector names a single (scalar) value from a previous query.
Selectors are used the htmpl-insert element’s query attribute,
as well as all parameter-providing attributes in an htmpl-query.
- Must name a query in the current scope or a parent scope.
- Query must name a single row. (See below on foreach for how to move from multiple rows to just-one.)
- Query must either contain a single column per row, or name a column.
Query-only form (if the query resulted in a single column): query_name
Query+column form: query_name(column_name)
§htmpl-foreach
-
queryattribute names aquery(note: not a selector, an actual query) -
For each row of that query’s results, evaluate the template within the
htmpl-foreachelement, with the namedqueryreplaced with a single-row result.i.e. for each row of the result, evaluate the inner template as if the query had returned just one row.
Note that, if the query returned no rows, the inner template will not appear at all.
§htmpl-attr
You may note that the above allows you to insert DOM nodes, but not to specify attributes.
The htmpl-attr sets an attribute to a variable value. An htmpl-attr modifies nodes:
- that are in its current scope (i.e. under the same parent)
- that are after the
htmpl-attrin the source - that are identified by a CSS selector
- that are not htmpl elements
const TEMPLATE : &str = r#"
<htmpl-query name="q">SELECT name, (uuid || " name") AS uuid_class FROM users ORDER BY name ASC LIMIT 1;</htmpl-query>
<htmpl-attr select=".name" query="q(uuid_class)" attr="class" ></htmpl-attr>
<div class="name"><htmpl-insert query="q(name)"></htmpl-insert></div>
"#;
let result = htmpl::evaluate_template(TEMPLATE, &conn).unwrap();
assert_eq!(result.trim(), r#"<div class="18adfb4d-6a38-4c81-b2e8-4d59e6467c9f name">cceckman</div>"#);
const TEMPLATE : &str = r#"
<htmpl-query name="q">
SELECT
name
, ("name item-" || ROW_NUMBER() OVER (ORDER BY name)) AS class
FROM users;
</htmpl-query>
<htmpl-foreach query="q"><htmpl-attr select=".name" query="q(class)" attr="class" ></htmpl-attr>
<div class="name"><htmpl-insert query="q(name)"></htmpl-insert></div></htmpl-foreach>
"#;
let result = htmpl::evaluate_template(TEMPLATE, &conn).expect("unexpected error");
assert_eq!(result.trim(), r#"
<div class="name item-1">cceckman</div>
<div class="name item-2">ddedkman</div>
"#.trim());§htmpl-if
Conditional evaluation of its body.
Name a selector with the true= attribute or false= attribute;
if the truthiness of the expression matches, the body of the htmpl-if is evaluated.
Note that, as usual, htmpl-if constitutes a scope; a query executed inside an htmpl-if
element will not be available outside of the htmpl-if.
pub const CCECKMAN_UUID: &str = "18adfb4d-6a38-4c81-b2e8-4d59e6467c9f";
conn.execute(
r#"INSERT INTO posts (title, text, draft) VALUES (?, ?, ?), (?, ?, ?)"#,
rusqlite::params![
"First Post", "This is my first post!", 0,
"Second Post", "This is my second post! But it isn't ready yet.", 1,
],
).unwrap();
const TEMPLATE : &str = r#"
<htmpl-query name="q">SELECT title, text, draft FROM posts;</htmpl-query>
<htmpl-foreach query="q">
<h1><htmpl-insert query="q(title)"></htmpl-insert><htmpl-if true="q(draft)"> (Draft)</htmpl-if></h1>
<p><htmpl-insert query="q(text)"></htmpl-insert></p>
</htmpl-foreach>
"#;
let result = htmpl::evaluate_template(TEMPLATE, &conn).expect("unexpected error");
assert_eq!(result.trim(), r#"
<h1>First Post</h1>
<p>This is my first post!</p>
<h1>Second Post (Draft)</h1>
<p>This is my second post! But it isn't ready yet.</p>
"#.trim());
}§Truthiness
Truthiness depends on the affinity of the type.
Null values are always false. Beyond that, truthiness depends on the value’s affinity:
- Integer: 0 is falsy, all other values are truthy
- String: the empty strings is falsy, all nonempty strings truthy
- Real: Positive zero, negative zero, and NaN are falsy; all other value truthy
- Blob: Empty (zero-length) blobs are falsy, all other values truthy
§Caveats
- “Database” is, for now, a single SQLite database.
- TODO: The database is (should be) read-only. htmpl is not PHP – it is a templating language, not a programming language.
Enums§
Functions§
- evaluate_
template - Parse the HTML tree, replacing htmpl elements and attributes.