Overview
The Rosella Template library is a text-templating and string-formatting library with configurable syntax and semantics. By specifying a text template and a data context object, the templating engine can create output text.
Concepts
Templates
Templates are strings of text which contain both literal data and metadata. During template rendering, an output string is created which contains the literal data verbatim and generated output data is inserted in place of the metadata. The result should be an expected output, clean of the logic which was used to create it.
Templates are constructed using two components: The first is the string literal which forms the template itself. The second is the data object which is used to feed values and control into the template’s metadata. This separation of concerns is important. The template literal specifies exactly what should be shown in the output and how. The data can change on each usage to create distinct, if similar, outputs. The third component is the rendering engine, the details of which are safely encapsulated from both the template text and the input user data. This division of concerns is called “Model View Controller” (MVC). In this case, the Model is the user data context object, the view is the template, and the controller is the template rendering engine, provided in the Rosella Template library.
Data Context
The data context object is an object which is provided to the template which contains user data. The provided user data is treated as read-only, but the Context object which wraps it contains temporary storage locations to hold edits and temporary values during rendering.
The user context object is typically a hash or a hash-like object. It is searched using the Rosella Path library to find values and nested sub-values. Any object which can be traversed with the Path library can be used as a context object for rendering templates.
Namespaces
Rosella.Template
The Rosella.Template namespace contains a small number of support routines to help with using templates. Rosella ships with a number of “standard” templates, which can be easily accessed using the routines get_standard_template_file
and get_standard_template_text
.
Classes
Template.Context
The Rosella.Template.Context
object is a hash-like wrapper object around a user context object. It provides access to the user data object and also provides storage for temporary values. On value lookup, the context object searches in the temporary storage first, and looks in the user data object second. In this way, edits to existing keys can be made, while not actually modifying the user data.
Template.Engine
The Rosella.Template.Engine
object is the primary user-visible interface to the template rendering capabilities of the library. The engine brings together the tools and data necessary, renders the template, and returns the output to the user.
The Engine sets up the various sub-components to use a default set of syntax and semantics for templates. Most of these components can be modified using an API on the Engine object to change details about templates and rendering.
Template.Node
The Engine object reads in the template text and parses it into a tree of Rosella.Template.Node
objects. Each Node represents a single bit of the template, be it a string of literal text or a complicated logic node with children. Node is an abstract parent class, all the interesting behaviors happen in subclasses.
After parsing, the Engine traverses the tree of Nodes, calling the render
method on each to produce output. During rendering, each node writes its text into a StringBuilder object, which is assembled into a String at the end.
Template.Node.Data
Rosella.Template.Node.Data
nodes are simple lookups into the Context. When a Data Node is rendered, it looks up the argument in the Context, and outputs the value verbatim into the StringBuilder.
The default syntax for the Data node uses <# #>
markers. Here’s an example of a Data region in a template using this default syntax:
The cow says: "<# foo.bar.baz #>"
If we have the following data context object:
var ctx = { "foo" : { "bar" : { "baz" : "Mooo!" } } };
The output which will be rendered is: The cow says: "Mooo!"
.
Template.Node.Eval
Rosella.Template.Node.Eval
nodes take string literals of executable code. By default, the code recognized is Winxed, though this is configurable. During rendering, the Eval node compiles the code into an executable subroutine, and executes it. The output from that subroutine is inserted into the buffer.
The default syntax for the Eval node uses <% %>
markers. Here’s an example of an Eval region in a template, using this default syntax:
The cow says: "<% return "Hello world!"; %>"
This snippet will render in the output as The cow says: "Hello world!"
.
Template.Node.Factory
The Rosella.Template.Node.Factory
is used to create nodes, using a lookup map provided to it by the Engine. The nodes created by the node Factory can be modified by calling appropriate methods on the Engine.
Template.Node.Literal
Rosella.Template.Node.Literal
nodes are literal text which is not influenced by metadata. The text of the literal is output verbatim into the buffer. All text outside of specially configured tagged regions is literal text.
Template.Node.Logic
Rosella.Template.Node.Logic
nodes implement various behaviors. Every logic node type has a name, and that name is used to look up a suitable Handler object. The Logic node is a thin placeholding wrapper around the individual Handler, which controls the way surrounding text is parsed and how the node is rendered.
Some logic nodes, such as a for
loop contain child nodes. The Logic node and its Handler determine how the children, or inner, nodes are rendered. In these cases where the Logic node has children, it will be accompanied by an end
node. For instance, the Logic node <$ for x in y $>
would have several child nodes followed by a <$ endfor $>
node. Here’s an example:
<$ for x in y $>
This is literal text and will be repeated verbatim
This is the current value of x: <# x #>
<$ endfor $>
Template.Node.Master
The Rosella.Template.Node.Master
node is the top-level node which represents the template as a whole. It is used to indicate the top of the parse tree and to recursively render the rest of the nodes in the parse tree.
Template.Handler
Rosella.Template.Handler
objects provide behavior for Logic nodes. Each handler does different things, but from the point of view of the Engine they all have the syntax of Logic nodes and are treated uniformly. Handler is an abstract parent class which defines the interface for all handlers. Do not use this type directly, use a subclass instead.
Template.Handler.Children
Rosella.Template.Handler.Children
is the parent class of most node types which have children. for
and repeat
loops, for instance, are subclasses of the Children handler. Some types, such as if/else
blocks need more complicated management logic and do not descent from Children. for the Eval node uses <% %>
markers. Here’s an example of an Eval region in a template, using this This is also an abstract parent type and should not be used directly.
Template.Handler.Factory
Rosella.Template.Handler.Factory
is a factory type for creating Handlers. It is used internally by the Engine, and can be configured through method calls on the Engine.
Template.Handler.For
Rosella.Template.Handler.For
performs a for
loop over an aggregate. On each iteration of the loop it defines three variables: __FIRST__
is 1 on the first iteration of the loop only, 0 otherwise. __LAST__
is 1 on the final iteration of the loop only, 0 otherwise. If iterating over a hash, the special variable __KEY__
will contain the string key.
Template.Handler.If
Rosella.Template.Handler.If
implements if/else conditionals. The if block takes a basic conditional that can be in one of two forms:
<$ if foo.bar $>
...
<$ endif $>
And
<$ if foo.bar > foo.baz $>
...
<$ endif $>
In the first form with one argument, it looks up the argument in the context and uses normal truth tests to determine if the value evaluates to true or false. In the second case with three arguments, it takes two values and performs a basic relational comparison on them. The two values can be either paths in the context, or they can be string or number literals:
<$ if foo.bar <= foo.baz $>
<$ if foo.bar < 1 $>
<$ if foo.bar == "string" $>
<$ if "string" == foo.bar $>
An if block can optionally contain an else
block. If provided, the else block is rendered whenever the if condition fails.
<$ if foo.bar == foo.baz $>
This literal text is output if ctx["foo.bar"] equals ctx["foo.baz"]
<$ else $>
This literal text is output otherwise
<$ endif $>
Template.Handler.Include
Rosella.Template.Handler.Include
performs literal file inclusion. The contents of a separate file are included inline and parsed as part of the template. There is a recursion limit which can be configured in the Engine. By default the recursion limit is something restrictive, like 10
.
<$ include 'myfile.template' $>
Template.Handler.Repeat
The Rosella.Template.Handler.Repeat
handler is a loop that executes a fixed number of times with integer indices.
<$ repeat 1 to 5 $> <# __INDEX__#> <$ endrepeat $>
That example will print out:
1 2 3 4 5
Likewise, we can go in reverse:
<$ repeat 10 to 5 $> <# __INDEX__ #> <$ endrepeat $>
…Which prints:
10 9 8 7 6 5
Like the for loop, the repeat loop has __FIRST__
and __LAST__
values which are true during the first and last iteration of the loop, respectively.
Template.Handler.Set
The Rosella.Template.Handler.Set
node is used to set a value to a named context key. It takes two forms:
<$ set foo.bar as foo.baz $>
This first form sets the key foo.bar
to the same value as is in foo.baz
. Programmatically, it’s the same as writing
ctx["foo.bar"] = ctx["foo.baz"];
The second form of set
is:
<$ set foo.bar $>
This literal string and other contents like <# foo.baz #>
are stored in foo.bar
<$ endset $>
In this second form, the contents of the set block are stored in the key foo.bar
. The rendering of the contents happens when the set
block is rendered, so the variable and other logic in the middle don’t get re-rendered dynamically when values change.
Template.Handler.Unless
Rosella.Template.Handler.Unless
is identical to the if
handler in all ways except it reverses the logical condition. Unless with an else
clause is identical to an if
with an else
clause except the ordering of the two clauses is reversed.
<$ if x $>
This is printed if true
<$ else $>
This is printed if false
<$ endif $>
<$ unless x $>
This is printed if false
<$ else $>
This is printed if true
<$ endunless $>
If the else
clause is ommitted, the unless
handler operates like an exact opposite of the if
handler.
Examples
Winxed
Rendering a template with Winxed is easy. The real complications come in the syntax for defining templates.
var rosella = load_packfile("rosella/core.pbc");
var(Rosella.initialize_rosella)("template");
var engine = new Rosella.Template.Engine();
string output = engine.generate(template, context);
NQP
my $rosella := pir::load_bytecode__ps("rosella/core.pbc");
Rosella::initialize_rosella("template");
my $engine := Rosella::construct(Rosella::Template::Engine);
my $output := $engine.generate: $template, $context;
Templates
Here is a for
loop, using special loop control variables to construct an snippet of code for an array literal:
<$ for x in y $>
<$ if __FIRST__ $>[<$ endif $>
<# x #> <$ unless __LAST__ $>,<$ endunless $>
<$ else $>];<$ endif $>
<$ endfor $>
Given this context:
var ctx = { "y" : [1, 2, 3, 4] };
…would produce this output:
[
1,
2,
3,
4
];
Users
- Rosella has several utility programs which use the template library to automatically generate files and code snippets, such as unit tests and test harnesses.