module Bezel::Templates
The template compiler: HEEx-like templates (*.html.bz) made into
Quarz code over the HTML DSL (Bezel.html), which bezel templates
writes to src/views/generated/ and the application compiles as its
own code. Nothing is interpreted at run time; the generated code is
type-checked as any other, and escapes as the DSL does.
src = Bezel::Templates::Source.new("src/views/user/show.html.bz", "show",
"{@args name: String}\n<h1 class=\"title\">Hello, {name}!</h1>\n")
Bezel::Templates.compile(src).lines[1].strip # => "def self.show(name: String) -> Bezel::Html"
A template is HTML with:
{expr}: the value of a Quarz expression, in a text or as an attribute's value (href={Routes.user_path(id: user.id)}). AString(or any value, byto_s) is escaped, aBezel::Htmlis written as it is, nil writes nothing.{raw(expr)}writes aStringunescaped, as does aBezel::Htmlthe code made withHtml.raw: nothing else is unescaped, and the caller vouches for what these write. A{in a text is written{"{"}.{@args name: Type, ...}first: the parameters of the method the template becomes (as adef's, defaults and a block included). With one namedconn, the page isBezel.html(conn), whose forms carry the CSRF token.his the builder's name in the generated code, so no parameter or variable is namedh.:for={pattern <- expr}and:if={expr}on an element or a component: the element written for each element ofexpr(each, the pattern its block's parameters), or only whenexprholds; with both, the condition is tested for each element.<.card title="x">...</.card>: the component class Card (includingBezel::Component;.admin.user_cardis Admin::UserCard, names in lowercase), made with the attributes as keyword arguments (Card.new(title: "x"),classasclass_name, a-as_, a bare onetrue) and given the content as itscontent. A slot<:footer>...</:footer>directly inside one is the keyword argumentfooter:, aBezel::Html.<!-- ... -->: a comment of the template, left out of the page (but intextareaandtitle, whose content is text).
Elements are the DSL's (an unknown one is an error) and are closed
explicitly (<br> or <br/> for a void element, <div/> for an empty
one). An attribute is one of the element's own, a global one, class,
data-* and aria-*, or a name with a - or a : (written as it is,
attrs:); another name is an error, as a misspelled one would be. A
boolean attribute is written bare (<input required>) or with an
expression (disabled={locked}), an integer one with digits or an
expression. script and style hold their text as it is (what the
DSL would refuse in it is an error here); textarea and title hold
text and expressions. Entities in texts and attribute values are
decoded (the DSL escapes again): the numeric ones, and the named ones
of Templates::ENTITIES (another is an error; write the character).
Whitespace (ASCII: not ) with a line break is one line break
(all of it is kept inside pre and textarea), and the whitespace at
the template's start and end is dropped, as at a component's or a
slot's (but inside pre and textarea).
An expression's code goes in the generated code as written, in
parentheses, so it is one expression without # comments, heredocs,
lines starting with __END__ or =begin, or backslashes outside
literals; a / (or a % that could start a percent literal, %w[)
is an operator with a space on each side after a value
(a.size / 2, n % 3), or starts a regular expression or a percent
literal after (, [, a comma, the opening brace, or one of
= == =~ !~ => && || or a keyword argument's colon spaced
({name.gsub(/\s/, "")}, {%w[a b]}, {s =~ /x/}): the compiler
must see where the code ends as Quarz will, and refuses what it
cannot. The
generated code names the builder h and the slots' contents
__bz_slot_<n>, which the template's own names avoid.
A template that cannot compile is a Templates::Error naming its path
and line.
Classes and modules
Bezel::Templates::ErrorA template that cannot compile: the message names its path and line (src/views/user/show.html.bz:3: <span> is not closed).Bezel::Templates::SourceA template: its path (named in errors and in the generated file's header), the name of the method it becomes, and its text.
Constants
ENTITIES: Hash[String, String]
The named character references a template may use (with the numeric ones): those of markup, spaces and the common symbols.
Class methods
def self.compile_module(module_name: String, sources: Array[Bezel::Templates::Source]) -> String
The generated file of module module_name, a method per template
(in the order given): a header naming each template with the SHA-256
of its text, then the module.
def self.compile(source: Bezel::Templates::Source) -> String
The method source becomes (def self.<name>(<args>) -> Bezel::Html),
indented for a module's body.
def self.digest(text: String) -> String
The SHA-256 of text, in hexadecimal: the header's digest.
def self.digests(generated: String) -> Hash[String, String]
The digests a generated file's header names, by template path: what it was generated from.