NAME

Template::Sandbox - Fast template engine sandboxed from your application.

SYNOPSIS

use Template::Sandbox;

my $template = Template::Sandbox->new();
$template->set_template( '/path/to/my/templates/accounts.html' );
$template->add_var( customers    => $customers );
$template->add_var( transactions => $transactions );
$template->add_vars( {
    session => $session_info,
    user    => $user_info,
    } );
print ${$template->run()};

my $template = Template::Sandbox->new(
    template_root => '/path/to/my/templates',
    template      => 'accounts.html',
    cache         => $cache,
    );
$template->add_vars( {
    transactions => $transactions,
    customers    => $customers,
    session      => $session_info,
    user         => $user_info,
    } );
print ${$template->run()};

Within /path/to/my/templates/accounts.html:

<: if user :>
<p>Welcome back, <: expr user.name :>.</p>
<: else :>
<p>Welcome.</p>
<: endif :>
<p>Recent Transactions:</p>
<table>
    <tr>
     <th>Transaction ID</th>
     <th>Customer</th>
     <th>Date</th>
     <th>Description</th>
    </tr>
<: foreach transaction in transactions :>
    <tr bgcolor="#<: if transaction.__odd__ :>ccffcc<: else :>ccccff<: endif :>">
     <td><: expr transaction.id :></td>
     <td><: expr customers[ transaction.customer ].name :></td>
     <td><: expr transaction.date :></td>
     <td><: expr transaction.description :></td>
    </tr>
<: endfor :>
</table>

DESCRIPTION

Template::Sandbox is Yet Another Templating module, designed primarily for use in a webserver environment but usable anywhere, providing a more secure "sandboxed" environment than most templating systems.

The core design philosophy for Template::Sandbox is that the template logic should have no access outside the template beyond that which you choose to permit it, this is frequently known as sandboxing.

Unlike many other template systems, available on CPAN or in other languages, Template::Sandbox doesn't give the template access to the global variables of your application or to the core functions of the language.

This means that your template authors only have access to the data and functionality that your application developers choose to grant them, this encourages both to work with "published" interfaces between the two systems - your template authors can't reach into the application's internal-only data, and so your application developers can change that internal data without worrying that the templates will stop working or expose confidential information.

Template::Sandbox also provides exceptional performance, ranking among the fastest of the fully-featured template engines that don't rely on embedding perl within the templates.

Template::Sandbox also provides the usual gamut of behaviours and optional features: caching compiled templates, includes, flow control, embedded expressions, cascading template candidates, and useful debugging information in case of errors.

Furthermore, Template::Sandbox is designed to be subclassable should you wish to customize or extend other of its features.

IMPORTANT CONCEPTS AND TERMINOLOGY

This section contains some important concepts and terminology that will help you get started and to understand the rest of this document.

Template Workflow

The workflow to use a template consists primarily of two stages, preparation and execution:

Template Preparation

The preparation stage consists of constructing a new template object, initializing it, setting template variables and loading and compiling the template.

Most of these operations can be done in any order, except that you need to register all functions you are going to use before you load and compile the template.

Some examples of things you might do in the preparation stage:

$template = Template::Sandbox->new();

#  Fine to add vars before the template is set.
$template->add_var( session => $session );

$template->set_template( 'control_panel/personal_details.html' );

#  Fine to add vars after the template is set too.
$template->add_var( {
  private_messages => $session->{ user }->get_private_messages(),
  recommendations  => $session->{ user }->get_recommendations(),
  } );
Template Execution

The execution stage on the other hand only happens once you're done preparing, anything you want to do change the output of the template needs to happen before this point.

It's fairly easy to understand since execution consists of only one action:

$outputref = $template->run();

It is currently assumed that after template execution you will have no further use for the template, so while some cleanup is done of the stateful information required by execution, running the same template instance multiple times is not currently supported. (Although it might work.)

Template Variables

Each template instance has its own namespace of template variables, these are added via the $template->add_var() and $template->add_vars() methods, throughout this document any reference to variables is assumed to mean template variables unless otherwise noted.

Template variables are the only variables your template can see, you cannot access the contents of a perl variable unless you either directly pass it as a template variable, indirectly pass it as a reference within a structure that you have added as a template variable, or return it as a value from within a template function.

Template Functions

Much like template variables, each template instance has its own namespace of template functions, there is also a common namespace across all templates that can contain template functions.

Also like template variables, your template cannot access any perl function unless the function has been directly registered with either the instance or the entire Template::Sandbox class, or is used within a function that has itself been registered.

OPTIONS

New Template::Sandbox objects can be created with the constructor Template::Sandbox->new( %options ), using any (or none) of the options below.

template => template filename

Specifies the template file to be loaded.

template_root => directory

Sets the base directory to which template filenames will be relative.

This is not enforced as a restriction, if someone wants to traverse outside the template_root with .. or other mechanics, they can do so.

logger => logging object

Sets the object to be used for logging purposes, by default Log::Any is invoked via Log::Any->get_logger(), if you're passing some other form of logger, you're responsible for ensuring it meets the same API as provided by Log::Any.

cache => cache object

Sets the template to search the given cache for compiled templates rather than compiling them anew.

The cache may be any that conforms to the Cache::Cache API.

Template::Sandbox however also detects the use of Cache::CacheFactory in order to make use of its last-modified dependencies checking, if you're using other caching mechanics you will need to ensure cache freshness via your own mechanisms.

See the section "Caching" for further discussion.

ignore_module_dependencies => 1 | 0

When using Cache::CacheFactory for caching, a list of dependencies for the cached version of the template is produced, this includes the template file itself and any included templates. By default this list also includes the module files for the template class and its superclasses, since if they change the compiled template may be invalidated.

Setting ignore_module_dependencies to a true value will prevent this list of module files from being appended, potentially a performance gain, however you probably should ensure that the cache is flushed between any updates to the Template::Sandbox module or any subclasses you have made, if they contain functional changes.

See the section "Caching" for further discussion.

template_function => template function definition

This lets you register a custom template function to the new template instance.

See the section "Custom Template Functions" for more details.

copy_global_functions => 1 | 0

On initializing the new template object, if this option is set to a true value, all template functions added at the class level will be copied as custom template functions local to that instance, this ensures that if the class function is later removed then the function will still be available to templates run by this instance.

See the section "Custom Template Functions" for more details.

library => [ $library => @import ]

This will import the list of template functions or import tags listed in @import from the template function library $library.

This is equivilent to calling:

$library->export_template_functions( $template, @import );

For more details see Template::Sandbox::Library.

template_syntax => template syntax definition

This lets you register a custom template syntax to the new template instance.

See the section "Custom Template Syntaxes" for more details.

open_delimiter => $open_delimiter
close_delimiter => $close_delimiter

Optionally set the delimiters that indicate, respectively, the start and end of a template token. By default the opening delimiter is <: and the closing delimiter is :>.

The open_delimiter and close_delimiter options were added in v1.02_01.

allow_bare_expr => 0 | 1

If set to a true value, this allows the expr token to be ommited from template expressions.

By default this is turned off.

For example:

$template = Template::Sandbox->new(
    allow_bare_expr => 1,
    );
$template->add_var( a => 'a is a fine letter' );
#  This would usually be a syntax error:
$template->set_template_string( '<: a :>' );
print ${$template->run()};

#  Output:
a is a fine letter

The allow_bare_expr option was added in v1.02_01.

vmethods => 0 | 1

If a set to a true value, this disables the usual behaviour of methods in Template::Sandbox and instead causes any method calls to be rewritten as template function calls with the "object" of the vmethod pushed onto the front of the arguments to the template function.

$template = Template::Sandbox->new(
    vmethods => 1,
    );
#  With vmethods enabled, this:
$template->set_template_string( '<: expr aaa.substr( 2, 4 ) :>' );
#  is compiled to the equivilent of:
$template->set_template_string( '<: expr substr( aaa, 2, 4 ) :>' );

This rewriting happens during compilation, so at runtime the behaviour is identical to if you had written the functional form directly.

This also has the effect that when retreiving cached templates, only the value of vmethods in the compiling template instance matters, it is ignored in the executing template instance entirely: avoid mixing and matching this option within template instances sharing the same cache, unless you want inconsistent and confusing behaviour.

Also note that all the behaviour of template functions apply, you will need to either add them yourself or import them from a template function library, this option does not itself add any new functions.

The vmethods option was added in v1.02_02.

template_toolkit_compat => 0 | 1

If set to a true value, this in turn sets several other constructor options to provide a degree of compatibility with the syntax of Template::Toolkit, easing a transition to Template::Sandbox.

By default this is turned off.

The options currently set make these two lines equivilent:

$template = Template::Sandbox->new(
    template_toolkit_compat => 1,
    );

#  is shorthand for

$template = Template::Sandbox->new(
    open_delimiter  => '[%',
    close_delimiter => '%]',
    allow_bare_expr => 1,
    vmethods        => 1,
    );

If you provide values explictly for any of these constructor options, then the values you have set will be used rather than those provided by template_toolkit_compat.

More compatibility options may be added and set by this option in the future.

Here's an example of it in use:

$template = Template::Sandbox->new(
    template_toolkit_compat => 1,
    );
$template->add_var( list => [ 1, 2, 4, 5, ] );
$template->set_template_string( <<'END_OF_TEMPLATE' );
[% FOREACH v IN list %]
[% v %]
[% END %]
END_OF_TEMPLATE
print ${$template->run()};

#  Output:
1
2
4
5

Some incompatable gotchas are:

iterator.key and iterator.value in hash loops need to change to iterator and iterator.__value__.

While vmethod syntax is supported, none of the standard Template::Toolkit vmethods are provided. You can, however, write your own and Template::Sandbox::StringFunctions and Template::Sandbox::NumberFunctions might give you a good start.

Any feature of Template::Toolkit that doesn't have a corresponding feature in Template::Sandbox will also be unsupported.

Still, it may ease the pain if you're converting simple templates.

The template_toolkit_compat option was added in v1.02_01.

PUBLIC METHODS

$template = Template::Sandbox->new( %options )

This is the constructor for Template::Sandbox, it will return a newly constructed template object, or throw an exception explaining why it couldn't.

The options you can pass in are covered in the "OPTIONS" section above.

$template->register_template_function( $function_definition )
$template->add_template_function( $function_definition )
$template->unregister_template_function( $function_name )
$template->delete_template_function( $function_name )

These methods let you register a custom template function to the new template instance, or to unregister one so that it is no longer available.

See the section "Custom Template Functions" for more details.

$template->register_template_syntax( $syntax_definition )
$template->add_template_syntax( $syntax_definition )
$template->unregister_template_syntax( $syntax_token )
$template->delete_template_syntax( $syntax_token )

These methods let you register a custom template syntax to the new template instance, or to unregister one so that it is no longer available.

See the section "Custom Template Syntax" for more details.

$template->get_valid_singular_constructor_param()
$template->get_valid_multiple_constructor_param()
$template->initialize( %options )

These three methods are used by the template constructor to determine valid parameters and initialize from them.

Each is detailed further in "SUBCLASSING Template::Sandbox".

$template->get_template_candidates( $filename, $dir )
$template->get_include_candidates( $filename, $dir )

These two methods govern how to find a template file from the requested filename.

Each is detailed further in "SUBCLASSING Template::Sandbox".

$template->get_additional_dependencies()

Returns if there are any additional file dependencies beyond the usual for the current template.

This method is detailed further in "SUBCLASSING Template::Sandbox".

$template->set_cache( $cache )

Sets the cache to $cache, as per the cache constructor option.

$template->set_template_root( $dir )

Sets the template_root to $dir, as per the template_root constructor option.

$template->set_template( $filename )
$template->set_template( $filename, $defines )

Loads and compiles the template in $filename, optionally setting compile defines from the hashref $defines.

$template->set_template_string( $template )
$template->set_template_string( $template, $defines )

Loads and compiles the template given in the string $template, optionally setting compile defines from the hashref $defines.

$template->add_var( $name, $value )

Sets the template variable named $name to have value $value.

Note that you can only add "top-level variables", that is you can do the first of these but not the second:

$template->add_var( 'user' => { profile => $profile, }, );  #  Works.
$template->add_var( 'user.profile' => $profile );           #  Wrong!
$template->add_vars( $vars )

Adds a template variable with name and value from each key and value of the hashref $vars.

Like $template->add_var(), this can only add top-level variables.

$template->merge_var( $name, $value )

Merges the contents of $value into the template variable named $name.

How the merge is performed depends on the nature of of $value:

$value is a scalar

If the named template variable does not already exist, it is set to $value. If the variable already has a value, it remains unchanged.

$value is an arrayref

If $value is an arrayref, then the contents of the arrayref are pushed onto the arrayref contents of the template variable, or assigned if no arrayref already exists.

$value is a hashref

Each key and value of $value is merged with each key and value of the hashref in the named template variable.

If this seems a little complicated, think of it that arrayref variables get appended to, and hashrefs "have any missing entries filled in":

#  In one part of your app:
$template->merge_var(
    stylesheets => [ 'login_widget.css' ],
    );

#  Then elsewhere:
$template->merge_var(
    stylesheets => [ 'search.css', 'advertising.css' ],
    );

#  Contents of 'stylesheets' is now:
[ 'login_widget.css', 'search.css', 'advertising.css' ]


#  Or a more complicated (and contrived) example:
$template->merge_var(
    userprefs => {
        private_messages => {
            message_order      => 'oldest-first',
            delete_when_viewed => 1,
            fave_tags          => [ 'music', 'video' ],
            },
        },
    );
$template->merge_var(
    userprefs => {
        private_messages => {
            delete_when_viewed => 0,
            friends_only       => 1,
            fave_tags          => [ 'computers' ],
            },
        },
        public_messages  => {
            message_order      => 'newest-first',
        },
    );

#  Contents of 'userprefs' is now:
{
    private_messages =>
        {
            message_order      => 'oldest-first',
            #  This already existed and remained unchanged.
            delete_when_viewed => 1,
            #  This didn't exist and was added.
            friends_only       => 1,
            #  This already existed and was appended to.
            fave_tags          => [ 'music', 'video', 'computers' ],
        },
    #  This didn't exist and was added.
    public_messages  =>
        {
            message_order      => 'newest-first',
        },
}
$template->merge_vars( $vars )

For each key and value in the hashref $vars, perform a $template->merge_var() with that key and value.

$template->clear_vars()

Clears all template variables that have been added so far, as if no template variables had been added at all.

This method added in version 1.01_11 of Template::Sandbox.

$template->run()

Runs the template, returning a reference to the output.

$template->run() will always return a valid string reference, or raise an exception trying: even if no output is produced a reference to the empty string will be returned, so the following is safe (if ugly):

print ${$template->run()};
$template->dumpable_template()

Returns a somewhat human-readable dump of the compiled template program, this probably isn't very useful unless you're me, or doing me the kindness of debugging something for me. :)

TEMPLATE SYNTAX

With the exception of compile defines (detailed below in "Compile Defines"), all Template::Sandbox syntax is written as statements enclosed within <: and :> symbols, for example:

<: if a :>some content<: else :>some other content<: endif :>
<: for x in y :>some loop content<: endfor :>

Everything outside the <: :> delimiters is considered to be template content and will be reproduced unaltered in the template's output.

A short summary of what statements are available and their arguments follows, with a more detailed section on each further below.

<: expr expression :>

Substitutes the statement with value of expression when the template is run. The expression itself may be a literal value, a variable or the result of function calls or operators.

For further details please see "EXPRESSIONS".

<: if condition :> branch content <: endif :>

The if statement conditionally chooses from several different branches of content and only one of those branches will be in the final template output. Collectively the if, else, else if, end if, and variant statements are refered to as "CONDITIONAL STATEMENTS".

<: for iterator in group :> loop content <: endfor :>

The for or foreach statement cycles an iterator variable through each element in the group array or hash and substitutes the loop content into the template output each time.

See ""LOOPS" for further details.

<: end :>

The end statement terminates the current if, for or foreach statement as if the corressponding endif or endfor statement was provided.

This statement is mostly provided for people used to the syntax of Template::Toolkit. You are strongly encouraged to use endif or endfor instead, as it makes it far easier to debug that you've got the wrong closing statement in the wrong place if your if blocks and for loops don't all have the same ending statement.

<: include filename :>

Includes the contents of the given filename at the current location within the template, the included file will itself be treated as a template and any template statements within it will also be run.

This is further detailed in the "INCLUDES" section.

<: # comment :>

The # statement is removed entirely from the template output (it's entirely removed from the compiled template in fact), behaving like a normal perl comment.

This allows you to easily and quickly comment out statements while developing the template:

<: # if session.user :>
This is where we'd display their control panel link once we've
written the session.user object.
<: # endif :>

Note that, in this example, only the if and endif statements are commented out, the template content between them will still be in the template.

EXPRESSIONS

Template expressions are much like those in any language, they can be formed by combinations of literal values, variables, operators and in some circumstances method calls.

Literal Values

Literal values can be either numbers or they can be string values enclosed in single-quotes, for example:

<: expr 'a literal string' :>
<: expr 42 :>

Strings have no interpolation done except backslash escaping: backslash followed by another character represents that character devoid of any special meaning, so if you wish to have a string containing a literal single-quote or backslash you could do the following:

<: expr 'a string with a single-quote (\') within it' :>
<: expr 'a string with a backslash (\\) within it' :>

Note that one consequence of the "no interpolation" rule is that you will not be able to embed a \n in your string and receive a newline/carriage-return, you'll just get a literal n instead, this may change in a future release, but for now you can make use of the cr special variable as detailed in "SPECIAL VARIABLES".

Variables

Template variables are refered to by bare names using a syntax designed to be familiar to javascript developers rather than perl developers. As such, it uses a 'dotted index' notation interchangably with square-bracket indices.

For example, user would refer to the template variable known as user, and both user.name and user[ 'name' ] would refer to the name index of the user template variable.

When using the square-bracket notation, the contents of the brackets are evaluated as an expression and the result is used as the index value, so the following is valid (if nasty to read):

customers[ transactions[ transaction.id ].customerid ]

As you can see from the example, you can also mix and match the notations, the following expressions are all identical:

customer.address.street
customer[ 'address' ].street
customer.address[ 'street' ]
customer[ 'address' ][ 'street' ]

Which you use is largely a matter of choice although the usual convention for clarity is to use the dotted notation for 'constant indices' (ones that don't change) and the square-bracket notation for ones that may vary.

When indexing arrays it's customary to use square-brackets too:

results[ 12 ]

Variables usually refer to template variables added via $template->add_var() or $template->add_vars(), however in some circumstances they can refer to locally-scoped variables set with the assign operator or include variables, both detailed in the "Operators" and ""INCLUDES" sections and further under "SCOPED VARIABLES".

There are a number of special variables that exist as indexes of other variables, you can recognise these as they are surrounded by double-underscores, some examples:

customers.__size__
transaction.__odd__

These variables are described in the "SPECIAL VARIABLES" section.

A quirk of the parsing for methods as well as template variables means that you can use the -> operator in place of the . operator to seperate variable segments, permitting:

customer->address->street
customer[ 'address' ]->street
customer->address[ 'street' ]

This is likely to confuse your perl programmers into trying this sort of thing though:

#  Won't work!
customer->[ 'address' ]->[ 'street' ]
#  Even further from working...
customer->{ 'address' }->{ 'street' }

So my humble advice is to stick to using the dotted notation, so that it looks like Javascript.

Operators

Operators exist to combine various subexpressions into a larger expression, Template::Sandbox supports most standard operators, listed below in order of precedence (with the exception of comparision operators, see their notes if precedence is important.)

Arithmetic operators (*, /, %, -, +)

These perform their standard arithmetic functions on numeric values. Note that + behaves like Perl's + operator, not that of Javascript: it expects a numeric value, if you want to concatinate strings you should use the . string concatination operator below.

String concatination (.)

Concatinates two strings into a single string.

Logic operators (!, not, &&, ||, and, or)

Perform logical negation, ANDing and ORing.

Note that the &&, ||, and, or operators all perform left-wise "short circuit" behaviour: that is, if the left-hand expression is sufficient to determine the result of the operator as a whole, the right-hand expression will never be evaluated.

String comparison operators (lt, gt, le, ge, eq, ne, cmp)

These operators compare two strings as in the equivilent Perl operators.

Although grouped together in this document for convenience, the precedence of the string comparison operaters is interleaved with the matching numeric comparison operators: lt, <, gt, >, le, <=, etc.

Numeric comparison operators (<, >, <=, >=, ==, !=, <=>)

These operators compare two numbers as in the equivilent Perl operators, note that if you supply strings to them, like you would to the equivilent operators in Javascript, then you will cause warnings.

Assignment operator (=)

This assigns the right-hand value to the scoped variable on the left-hand side. If there is no scoped variable of that name visible in the current scope, a new one will be created within the current scope.

See "SCOPED VARIABLES" for more details on this behaviour.

Note that you can only assign to a 'top-level' variable, ie you can assign to day_name but not date.day_name. This is intentional to reduce complexity and performance on variable evaluation, and because if you really need it, you're probably trying to do something that should be in your application layer, and not trying to write the application within the template.

Variable assignment returns the value assigned as its value, ie, the following template produces "blue" when run:

<: if ( a = 4 + 1 ) == 5 :>
blue
<: else :>
red
<: endif :>

However, if the assign is at the top level of an expr statement, it will return the empty string '', so that it leaves your template output unmarked, ie:

x<: expr a = 4 + 1 :>x

produces:

xx

and not:

x5x

Generally this will mean it will just "Do What I Want".

Brackets

If you're combining several expressions and are uncertain of the operator precedence, or simply want to make things clearer, you can use () round-brackets in the traditional way to group expressions in order of execution.

Some examples:

<: expr ( 1 + 2 ) * 5 :>
<: expr config.baseurl . '?page=' . ( param.page + 1 ) :>

Functions

Function calls may be made within an expression using the, familiar to many languages, syntax of:

functionname( arg1, arg2, ... )

For convenience and familiarity to Perl developers you can also use => as an argument separator, ie the following are equivilent, but the second two may be more readable:

<: expr url( 'q', 'bald-headed eagle', 'lang', 'en' ) :>
<: expr url( 'q' => 'bald-headed eagle', 'lang' => 'en' ) :>
<: expr url(
  'q'    => 'bald-headed eagle',
  'lang' => 'en',
  ) :>

Note however that unlike Perl, => does not auto-quote barewords on its left-hand side:

<: # Probably not going to do what you want :>
<: expr url( q => 'bald-headed eagle', lang => 'en' ) :>

This will pass the contents of template variable q as the first argument and the contents of lang as the third.

Note also that these function calls are not directly calls to perl functions, instead they are calls to functions that have been registered as template functions with the current template.

By default only three functions are registered, those three are needed for internal behaviour of certain special variables and for the test suite, it is part of Template::Sandbox's core philosophy that, like template variables, you must explicitly grant access to more than this if you wish to do so.

Ideally Template::Sandbox would ship with no functions enabled, and so these functions may be moved to the optional functions libraries in a future release if possible.

The three default functions are:

void()

Takes any args and returns the empty string.

This function is retained for legacy reasons as it was previously used internally to provide the void-context for variable assigns at the top level of an expr statement.

This function may be removed in a future release.

size( arg )

Takes a single argument and returns the "size" of it. For hashes that's the number of keys, for arrays it's the number of elements and for strings it's the length of the string.

Note that supplying a numeric argument will result in the number being converted to and treated as a string, and so will most likely result in returning the number of digits in the number. This behaviour is undefined and subject to change.

This function is required for global use since it is used internally to implement the __size__ special variable. (See "SPECIAL VARIABLES".)

defined( arg )

Takes a single argument and returns 1 or 0 to indicate whether the value was defined or not.

This function is required by the test suite at a stage before the function registration has been confirmed as working, as such will remain for at least the initial few releases to simplify CPAN smoke-testing feedback.

Methods

Methods on objects can be used within an expression, using a syntax familar to either Javascript or Perl developers, for example both of these are identical:

message.mark_as_read( 1 )
message->mark_as_read( 1 )

In either case the mark_as_read method will be called on the object in the template variable message, with an argument of 1.

However, in keeping with the purpose of Template::Sandbox, you cannot just call methods on any old object, every method call is preceded by a call to valid_template_method as a method on the target object, with the method to be called as an argument.

In the example above this would be message->valid_template_method( 'mark_as_read' ).

If this method returns true, then the mark_as_read method call is permitted to go ahead, if it returns false then an error will be raised.

Methods are mostly provided for completeness, there are performance implications in using them detailed in "PERFORMANCE CONSIDERATIONS AND METRICS", however it may be that someone will find them invaluable. Maybe.

If you have set the vmethods option to a true value then methods as detailed in this section cannot be compiled, instead they become equivilent to template functions with the "object" pushed onto the front of the list of arguments. See the "OPTIONS" section for more details and examples.

CONDITIONAL STATEMENTS

Template::Sandbox provides if, else if, else, end if, unless, else unless and end unless constructs to conditionally choose between different sections of template content, much like if statements in other languages choose between blocks of statements.

Any valid template expression (see "EXPRESSIONS") may be used as the condition, the true/false value of the result is all that the conditional statement cares about.

Each condtional construct is made up of an opening if or unless statement, optionally one or more else if or else unless statements, optionally a single else statement and is closed by a end if or end unless.

All template content between each of these statements is considered to be the "branch content" for the immediately preceding condition, and only appears in the final template output if the statement is the first true statement of the entire construct.

You can also nest as many if constructs as you wish, provided each one is entirely contained within a single content block of its parent (ie, is properly nested and not "overlapping".)

Simply put, it behaves like an if construct in every other language.

The following statements are available:

<: if condition :>
<: unless condition :>

All conditional constructs must open with an if or unless statement. Like in Perl the unless statement is just a convenience syntax for the logical negation of the condition.

<: else if condition :>
<: elseif condition :>
<: elsif condition :>
<: else unless condition :>
<: elseunless condition :>
<: elsunless condition :>

Depending on preference, you can choose from several functionally-equivilent spellings of the else if statement.

You can have no else if statements, or you can have as many as you like, the only restriction is that they must come before any else statement for the construct.

<: else :>

This optionally defines the block that will be used if no other condition within the statement is true, there can only be one of them in each if construct, and it must be the last branch - since it's a "catch all", having anything after it wouldn't make sense...

<: end if :>
<: endif :>
<: end unless :>
<: endunless :>

This marks the end of the conditional construct, whatever form of if or unless you have used to open your construct, you can use any of the above close it, for clarity you may wish to use a matching one.

LOOPS

Template::Sandbox provides a "loop" mechanism like the foreach statement of Perl, it creates and sets a locally scoped variable, sets it to the first value in a set of values and executes the contents of the loop, sets the loop variable to the next in the set of values and repeats, until there are no more entries in the set to loop through, whereupon the loop exits.

Unlike Perl or Javascript there is currently no last or continue mechanism to exit from a loop or jump to the next iteration directly.

Each for loop takes the following format:

<: for iterator in set :>
loop content
<: end for :>

The iterator is created as a scoped variable (see "SCOPED VARIABLES") within a new scope context, so it will mask the existence of any previous variable with that name within the scope of the loop.

Additionally the iterator has several special variable subscripts attached for convenience, such as iterator.__first__, these are detailed in the ""SPECIAL VARIABLES" section.

The set of values to iterate across may be a simple number, an array or a hash, or an expression resulting in one of these. The behavior in each case is detailed in its own section below.

Array Loops

If the set to iterate across is evaluated to be an array, then the iterator is set to each element of the array in order, from first to last.

Hash Loops

If the set to iterate across is evaluated to be a hash, then the iterator is set to each key of the array in alphabetical order, from first to last, with the special variable iterator.__value__ set to the corresponding value of the hash in addition to the usual special loop variables.

Numeric Loops

If the set to iterate across is evaluated as a single number, such as:

<: for x in 10 :>
<: expr 10 - x :> green bottles standing on the wall.
<: end for :>

it is taken to mean an array of values from 0 to n where n is the number given. In the example above this would be 0 to 10.

If the value happens to be a floating point (or even a string), it will be turned into a number via perl's int() function.

In all other respects, a numeric loop will be have as if you had supplied an array of the numbers directly. (See "Array Loops".)

INCLUDES

It's possible with the include statement to include the contents of other templates at the current statment's position, this allows you to easily share common sections of a template between several templates rather than cut-n-paste it into each.

Basic usage is fairly simple:

<: include transaction_row.html :>

The included template file is looked for relative to the current template's directory, in subclasses of Template::Sandbox you can override this with the get_include_candidates() method.

All includes are done at compile-time, this means that if there compile errors in an included file, the template as a whole will fail to compile even if the include is in a section of the template that will never be reached during run-time. It also means you cannot include a filename based on a run-time parameter, ie the following is unlikely to be working as intended:

<: # Wrong!!! :>
<: include our_files[ chosen_file ] :>

This will try to load a template with literal filename "our_files[ chosen_file ]" and not the presumed intention of a template whose filename is stored in the the our_files array or hash with the index stored in the chosen_file variable.

Setting compile defines with include

It's possible to set "Compile Defines" when including a file, to do so just set the values after the filename in the include statement, as in one of these examples:

<: include transaction_row.html TDCLASS=green :>
<: include transaction_row.html TDCLASSODD=green TDCLASSEVEN=blue :>
<: include transaction_row.html TDCLASS="value with spaces" :>

Any upper-case named parameter to include will set the corresponding compile define when compiling the included template, this define value will mask any existing define of that name for the duration of the compile of the included template. (And any templates it, in turn, includes.)

Setting scoped variables with include

You can also set variables scoped locally to the included template from the include statement, you do so in much the same manner as setting an include, except the parameter name is lower-case:

<: include login_widget.html user=session.user :>

This would set the user scoped variable to be equal to the value of the session.user template variable when control enters into the included file at runtime. This variable would be local to the included file and any files it, in turn, includes. See "SCOPED VARIABLES" for more details.

You can use any valid template expression to assign to the scoped variable, but if the expression contains spaces you must double-quote ("") the expression:

<: include image_with_border.html img="user.id . '/' . gallery.id" :>

SCOPED VARIABLES

All template variables added via $template->add_var(), $template->add_vars(), $template->merge_var(), and $template->merge_vars() have global scope within the template instance the method was called on, however it is also possible to create variables that have a shorter scope than the entire template instance, these are called scoped variables.

There are three different ways of creating scoped variables: the iterator variable of a for loop, variables created via the assign operator, and variables set during an include statement.

Scoped variables behave much like Perl variables created with local: they exist for the remainder of the current context and and are visible to all inner contexts.

New contexts are created on entering a for loop (unless one is deemed uneccessary, see "Template Program Optimizations"), and on entering a file via an include statement.

Note that the behaviour of the assign operator differs from for and include in that it only creates a scoped variable in the current context if no scoped variable already exists in a visible context.

That is, for and include create scoped variables that mask any previous scoped variable or template variable of the same name, whereas the assign operator will set any previous scoped variable (but not template variable) or create a new one.

This difference in behaviour allows you to produce something akin to subroutine calls with a dirty hack by assigning a variable to a dummy value to create it in the outer scope then setting it within an include with a 'return value':

In an outer template:
<: expr returnval = 0 :>
<: include faux_subroutine.html a=12 b=44 :>
<: expr returnval :>

Contents of faux_subroutine.html:
<: expr returnval = a + b :>

When the outer template is run, it produces:
56

While this behaviour can be useful in some situations, it's probably a sign that you need to create a new template function to do the heavy lifting for you.

There is currently a subtle bug with assigns to new template variables within context-folded loops persisting for longer than expected, this is detailed further in "KNOWN ISSUES AND BUGS".

SPECIAL VARIABLES

undef
null

Both undef and null provide access to the Perl undef value, null is provided as a familiar name for Javascript developers.

cr

Because there's, currently, no interpolation within literal strings inside template expressions, this prevents you from using '\n' to provide a newline/carriage-return. The cr special variable exists to provide easy(ish) access to that value:

<: expr 'Hello' . cr . 'World!' :>

This will produce template output:

Hello
World!

Needing to resort to use of a variable to get this functionality could be considered a bug, or at the least a missing feature, so it may become unneccessarily in a future release. The use of cr will still be supported beyond that point for backwards-compatibility.

var.__size__

Provides the size of the indexed variable, as provided by the size() template function.

For arrays this is the number of elements in the array, for hashes it is the number of keys in the hash, for strings it is the number of characters in the string.

For numbers it has currrently undefined behaviour that is the number of characters in the string when the number is converted to string. This may be subject to change in future releases.

The following special variables are only available as indices of the loop variable of a for or foreach loop.

iterator.__value__

Available only when iterating across a hash, this provides access to the value corresponding to the key the iterator is currently set to, this can be less typing (and faster to execute) if the hash being iterated over was the result of a long expression, for example the following two loops are equivilent, but the second is more convenient and also executes faster:

<: for x in this.is.the.bottom.of[ 'a' ][ 'long' ].chain :>
<: expr this.is.the.bottom.of[ 'a' ][ 'long' ].chain[ x ] :>
<: end for :>

<: for x in this.is.the.bottom.of[ 'a' ][ 'long' ].chain :>
<: expr x.__value__ :>
<: end for :>
iterator.__counter__

Gives the numeric count of which iteration of the loop is currently being run, numbered from zero:

<: for x in y :>
<: expr x.__counter__ :>
<: end for :>

Will give output "0", "1", "2", "3", "4", etc.

iterator.__even__
iterator.__odd__

Set to true or false if this an odd or even iteration of the loop.

Commonly useful for easily doing alternating bands of background colour in tables for legibility:

<tr bgcolor="#<: if row.__odd__ :>ccffcc<: else :>ccccff<: endif :>">

Note that since this is derived from __counter__, which starts at zero, this means that the first iterator is __even__ and not __odd__.

iterator.__first__

Set to true if this is the first iteration of the loop, or false subsequently.

iterator.__last__

Set to true if this is the last iteration of the loop, or false otherwise.

iterator.__inner__

Set to true if this is neither the first nor the last iteration of the loop, otherwise false.

iterator.__prev__
iterator.__next__

Give you convenient access to the previous and next values that the iterator was (or will be) set to, or undef if you are at the start or end of the loop respectively.

Note that in none of the loop special variables will be set for the contents of iterator.__prev__ or iterator.__next__, ie these expr statements will error:

<: for entry in myhash :>
<: # These will error :>
<: expr entry.__prev__.__value__ :>
<: expr entry.__next__.__prev__ :>
<: end for :>

COMPILE DEFINES

Compile defines are a special type of variable that get replaced at compile-time. (To be picky, they actually get replaced as the template is read, before compilation begins.)

This means two things: 1) they're constant and cannot change during repeated runs of the same compiled template, or within a single run; 2) they can contain anything you like, including fragments of template statements rather that just values to use in an expression.

You can set compile defines at two stages, either when you call $template->set_template( $filename, $defines ) (or set_template_string), or as parameters to an include statement. (For more details look at "INCLUDES".)

However you set them, a compile define is a symbol consisting of an entirely UPPERCASE name, that will be used for literal replacement within the template contents being read. You may also use underscores (_) and numbers within a define name.

The template is scanned looking for constructs of the form:

${NAME}
${NAME:default}
${'NAME'}
${'NAME:default'}

And will replace them according to the rules below.

Plain Compile Defines

If the token being replaced has the form ${NAME}, the contents of the define will be substituted verbatim into the source of the template being read.

For example:

$contents = q/Welcome to ${PAGEOWNER}'s Home Page!/;

$template->set_template_string( $contents,
    {
        PAGEOWNER => 'Joe',
    } );
print ${$template->run()};
# Produces: Welcome to Joe's Home Page!

Compile Define Defaults

If the token has form ${NAME:default}, then if there is a compile define with name NAME with a defined value, that will be used for substitution, otherwise the value of default will be used.

For example:

$contents = q/Welcome to ${PAGEOWNER:Fred}'s Home Page!/;

$template->set_template_string( $contents,
    {
        PAGEOWNER => 'Joe',
    } );
print ${$template->run()};
# Produces: Welcome to Joe's Home Page!

$template->set_template_string( $contents );
print ${$template->run()};
# Produces: Welcome to Fred's Home Page!

Quoted Compile Defines

If the token takes the form ${'NAME'} or ${'NAME:default'} then replacement is done as above, with the addition that the replacement is enclosed in single-quotes (') and has the contents escaped correctly to be safe within those enclosing single-quotes.

This is mostly useful if you wish to include the contents of a compile define within an expression as a string, but are unsure if the define will contain single-quotes that would terminate your string and produce syntax errors, and wish to avoid placing the burden of proper escaping on whoever is setting the define's value.

For example, you wish to have an alert_header.html:

<p class="alert">
<: expr html_escape( ${'MOTD'} ) :>
</p>

and in the main navigation template for your side you want to do:

<: include alert_header.html
   MOTD="We're currently experiencing some service disruptions, please bear with us" :>

This will be replaced and produce the (safe) template source of:

<p class="alert">
<: expr html_escape( 'We\'re currently experiencing some service disruptions, please bear with us' ) :>
</p>

Without the ${'MOTD'} quoting mechanism, the quote in "We're" would be unescaped and terminate the string, causing a syntax error within the template.

You could manually escape the contents of MOTD when you set it within the include statement, but while this may be possible, it's inconvenient and the sort of thing you're likely to accidentally forget to do. It also leads to error messages at the point where the define is used rather than where the define is set, which can be a pain to track back up to in complicated template structures.

CUSTOM TEMPLATE FUNCTIONS

In order to use any functions beyond the basic ones within your template you will need to register them as a custom template function.

This can be done either with the template_function constructor option or the $template->register_template_function() method.

To these you need to supply a name for the function, as it will be invoked from within your templates, and a data-structure providing the function and describing some flags on the function's behaviour.

To assist in producing the function definition you can import some helper functions using the ':function_sugar' import tag on your use line:

use Template::Sandbox qw/:function_sugar/;

These imported functions, described in "Function Sugar", allow you to pass an anonymous subroutine (or function reference) and produce a data-structure suitable for registering as a template function.

Some examples probably make this a lot clearer:

use Template::Sandbox qw/:function_sugar/;

#  Register 4 template functions during construction.
$template = Template::Sandbox->new(
    template_function => [
        int => ( one_arg  sub { int( $_[ 0 ] ) } ),
        max => ( two_args sub { $_[ 0 ] > $_[ 1 ] ? $_[ 0 ] : $_[ 1 ] } ),
        min => ( two_args sub { $_[ 0 ] < $_[ 1 ] ? $_[ 0 ] : $_[ 1 ] } ),
        var => ( one_arg inconstant needs_template
                     sub { $_[ 0 ]->_var_value( $_[ 1 ] ) } ),
        ],
    );

#  Register a template function after construction.
$template->register_template_function(
    localtime => ( no_args inconstant sub { scalar localtime() } ),
    );

#  Whoops, no we didn't want that function after all.
$template->unregister_template_function( 'localtime' );

#  Actually, we wanted it available to all templates.
Template::Sandbox->register_template_function(
    localtime => ( no_args inconstant sub { scalar localtime() } ),
    );

Now within your templates you can do the following sorts of things:

<: if max( pricea, priceb ) > 50 :>
Price too high, max price (rounded down) is:
<: expr int( max( pricea, priceb ) ) :>.
<: else :>
Prices all within tolerences.
<: endif :>
Page generated on <: expr localtime() :>.

Function Sugar

These helper functions can be exported into your namespace with:

use Template::Sandbox qw/:function_sugar/;
inconstant

Indicates that the function returns a value that is not constant even if the arguments are constant, and that the function should not be subject to constant-folding optimizations at compile time. You should also use this if the function returns a constant value for constant input but has an important side-effect that must happen each call.

Some examples of inconstant functions in Perl would be time() or random(), where the return varies for constant input; or flock() where the side-effect needs to happen at run-time.

needs_template

Indicates that the function would like the template instance passed as the first argument in addition to any arguments supplied within the template.

undef_ok

Indicates that the function finds it acceptable to be passed undefined arguments and disables the warnings that would otherwise be produced by them.

no_args

States that the function should be passed no arguments, attempting to do so will produce a compile-time error.

one_arg
two_args
three_args

States that the function should have the relevent number of arguments, passing a larger or smaller number produces a compile-time error.

any_args

States that the function does not care about how many arguments it receives, it will accept any number of them (or none).

has_args

Lets you define the number of arguments manually rather than using one of the convenience wrappers above, takes two args, the first being the function or chained function sugar, the second being the number of arguments the function expects, for example this gives you a function that takes 5 arguments (although it doesn't use them):

has_args sub { 'has a lot of args but ignores them' }, 5;

Using anything other than 5 arguments to this function would then be a compile-time error.

def_func

Not exported by default.

This is the function used internally by the function sugar functions listed above, it takes either an already-sugared function or a sub reference as the first argument, the second argument is the index of the function definition to alter, and the third is the value to set the entry to.

You shouldn't ever need to use this, but it can be exported if you find a need for it with:

use Template::Sandbox qw/:function_sugar def_func/;

CUSTOM TEMPLATE SYNTAXES

This API is incomplete and subject to change, nevertheless the current state-of-play is documented here in case you need to make use of it.

It's possible to add new single-statement template keywords to be compiled into the template and run by your own custom callbacks.

You do this with either the template_syntax constructor option or the $template->register_template_syntax() method:

$template = Template::Sandbox->new(
    template_syntax => [
        yarr => {
            compile => sub { [] },
            run     => sub { 'Yarr!' },
            },
        ],
    );

$template->register_template_syntax(
    lubber => {
        compile => sub { [] },
        run     => sub { 'Ye scurvy landlubber!' },
        },
    );

$template->set_template_string( "<: yarr :> <: lubber :>\n" );
print ${$template->run()};

Yarr! Ye scurvy landlubber!

As can be seen, two arguments are passed, the first is the name of the token to be added as valid template syntax, the second is a hash of options for that syntax.

Currently the only options allowed for the syntax are compile for the compile callback and run for the run callback.

The compile callback is called as the syntax is compiled, it's passed the template object, the token being compiled, the position data-structure marking the current position in the template and a hashref of the named args set in the statement being compiled.

It should return a value that will be passed as an argument to the run callback when the compiled statement is executed, or undef to indicate that the statement should be dropped entirely from the compiled template.

The run callback is called whenever the compiled statement is run, being passed the template object, the token being run and the compiled arguments. (Note that the position is not passed to the run callback.)

It should return a string containing the content to be inserted into the template output, or undef if no output is to be produced.

Here's an example from a Template::Sandbox subclass to allow <: url :> statements:

sub initialize
{
    my ( $self, $param ) = @_;

    $self->register_template_syntax(
        'url' =>
            {
                compile => \&compile_url,
                run     => \&run_url,
            }
            );

    $self->SUPER::initialize( $param );
}

sub compile_url
{
    #  This isn't a method despite similar args.
    my ( $self, $token, $pos, $args ) = @_;

    $args = {
        map
        {
            $_ => $self->_compile_expression( $args->{ $_ } )
        }
        keys( %{$args} ) };
    $args = 0 unless scalar( keys( %{$args} ) );

    return( $args );
}

sub run_url
{
    #  This isn't a method despite similar args.
    my ( $self, $token, $args ) = @_;

    $args ||= {};

    #  Craft url with given param.
    return( $app->input()->url( {
        map
        {
            $_ => $self->_eval_expression( $args->{ $_ }, 1 )
        }
        keys( %{$args} ) },
        1 ) );
}

Currently custom template syntaxes don't really let you achieve anything you couldn't achieve with a custom template function, and you potentially miss out on fringe benefits like constant-folding optimizations. You also can't produce block-style statements like if or for constructs, both of these situations may change in future releases.

ZERO-WIDTH FOLDING

Flow control statements (such as if and for) and include statements are subject to zero-width folding, this means that the existence of the statement token itself should be treated as invisible to the output of your document, even if for clarity reasons the token has been placed on a single line by itself.

Cutting through the jargon, what this means is that these statements won't liberally sprinkle newlines through your document if you do the following:

This is some text.
<: if a :>
Case a is true.
<: else :>
Case a is false.
<: endif :>
This is some more text.

With zero-width folding, this produces (with a true):

This is some text.
Case a is true.
This is some more text.

Whereas without zero-width folding, the more literal output is probably not what you intended:

This is some text.

Case a is true.

This is some more text.

This is because each clause of the if statement actually has a newline preceding and trailing it, so while the statement itself produces no output you're left with doubled newlines - the zero-width folding reduces these to the single newline you probably intended.

It is not currently (and may never be, unless someone really needs it) possible to disable zero-width folding.

CACHING

Like most template systems Template::Sandbox is heavily optimized for speed when a template is run, sometimes at the expense of the time taken to compile the template in the first place: currently compilation of a template is approximately three to four times slower than running it. (This is a very rough metric based on my practical experience for a typical template: large or small numbers of loops at runtime will sway this figure back and forth - they only get compiled once, but run many times.)

This strategy assumes that you'll be using some form of persistent caching mechanism, and Template::Sandbox provides an in-built caching mechanic to make this easier for you.

Via the cache constructor param or the $template->set_cache( $cache ) method, you can supply a cache object of your own choosing and configuration for Template::Sandbox to use.

The only restriction on the cache object is that it must conform to either the Cache::Cache API or the extended set() API of Cache::CacheFactory.

If $cache->set_takes_named_param() exists and returns true, or $cache->isa( 'Cache::CacheFactory' ) returns true then the extended (named-parameter) version of $cache->set() will be used, this will pass the dependencies of the template as a named parameter dependencies which is suitable for use for the 'lastmodified' expiry policy of Cache::CacheFactory.

If the extended version of set() is not used, then no dependencies checking will be performed at all. This may change in future versions with Template::Sandbox running its own dependencies checking to support caches that don't perform their own.

By default Template::Sandbox includes all superclasses of the template as dependencies for the template, since if the template module changes it may invalidate the compiled template. In a production environment this can impose a performance hit as these relatively unchanging module files are stat()ed on each template request. To avoid this penalty you can prevent the modules from being placed on the dependencies list by setting the ignore_module_dependencies constructor option to a true value. If you do this, you should manually flush any cached templates when you upgrade the template modules.

Some example code snippets for caching:

#  Using Cache::Cache's Cache::FileCache.pm
$cache    = Cache::FileCache->new();
$template = Template::Sandbox->new(
    cache    => $cache,
    template => 'profile.html',
    );
print ${$template->run()};

$cache    = Cache::FileCache->new(
    namespace  => 'mytemplates',
    cache_root => '/var/tmp/template_cache',
    );
$template = Template::Sandbox->new();
$template->set_cache( $cache );
$template->set_template( 'profile.html' );
print ${$template->run()};

#  Using Cache::CacheFactory
$cache = Cache::CacheFactory->new(
    namespace => 'mytemplates',
    storage   => 'file',
    validity  => 'lastmodified',
    );
$template = Template::Sandbox->new(
    cache    => $cache,
    template => 'profile.html',
    );
print ${$template->run()};

#  Using CHI
$cache = CHI->new(
    driver  => 'Memory',
    global  => 1,
    );
$template = Template::Sandbox->new(
    cache    => $cache,
    template => 'profile.html',
    );
print ${$template->run()};

See Cache::Cache, Cache::CacheFactory and CHI for further details on configuring cache objects.

Being able to pass in your own cache object to Template::Sandbox allows you to choose a cache that truly fits your needs, and to make use of some of the great caching modules on CPAN.

Choosing the right caching module can gain you large performance advantage over the in-built caching methods of many other template systems, here's some example situations with code-snippets to generate a suitable cache for it.

In-memory cache, per process

$cache = Cache::CacheFactory->new(
      storage       => 'fastmemory',
      validity      => 'lastmodified',
      no_deep_clone => 1,
      );

This builds you a cache that will store the compiled template in-memory once per process, the 'fastmemory' storage policy provides a fast set/get process and the no_deep_clone option turns off cloning of the data structure, this is safe because once the template has been compiled it's treated in a read-only manner, so there's no need to worry about polluting the cache by running it.

The 'lastmodified' storage policy causes the dependencies of the template to be checked against the cached data, this imposes some speed penaltly and can be left out, although if you're planning to do that (and not use any of Cache::CacheFactory's other features), you should consider using the Cache::FastMemory module directly for additional speed.

This is great to use when you're in a situation where you're using a small number of templates repeatedly within each process, or you have a long-running process using a large number of templates and memory is no issue. (We can all dream, right?)

Shared memory cache

$cache = Cache::FastMmap->new(
    share_file => "/tmp/somewhere-or-other",
    cache_size => '100k',
    ),

Sick of multi-gajillion megabyte apache processes, but still want the speed of a memory cache?

Cache::FastMmap is for you, it'll store the compiled templates in a file accessed via mmap() for blistering speed. You lose somewhere around 20% performance compared to using Cache::FastMemory, but if multiple processes are using the same template you'll have fewer cache-misses and use a lot less memory.

Started from within your startup.pl to ensure the memory is shared among all server children, this makes a really nice match for a mod_perl environment.

On-disk cache

$cache = CHI->new(
    driver   => 'File',
    root_dir => '/tmp/my-template-cache-dir',
    );

$cache = Cache::CacheFactory->new(
    storage  => { 'file' => { cache_root => '/tmp/cachedir', }, },
    validity => 'lastmodified',
    );

If you're not so fussed about the performance of repeated template use within a process, but want subsequent processes to not take the hit of having to compile each time, then an on-disk cache is what you need.

This is probably what you want if you're running in a CGI environment.

CHI provides a faster disk cache than Cache::CacheFactory, but doesn't hook up to the dependencies checking, both are pretty fast and are only somewhere in the region of 25-50% of the speed of using memory caching.

CHI also requires Moose, so if large dependency chains or startup time are an issue for you, it's something to be aware of.

Multi-level caches

Both CHI and Cache::CacheFactory allow you to set up multi-level caches so that you can check a memory cache first, and fetch from a disk cache second if the memory cache fails.

That's somewhat outside the scope of this document however, so I suggest you read the documentation of the respective modules for details.

SUBCLASSING Template::Sandbox

Useful methods to override when subclassing

These methods are likely to be of interest to you when subclassing Template::Sandbox.

$template->initialize( %param )

Called as part of the constructor, initialize() is designed for you to override, with the hash %param passed as a single argument containing the merged valid parameters passed to the constructor.

If you override this method, make sure that you call $self->SUPER::initialize( %param ) at some point, otherwise Template::Sandbox won't get chance to do its own initialization.

Singular param (see below) will be supplied as a single value in %param. Multiple param will be supplied as an arrayref of the exact values that were passed to the constructor, this means that if you pass an arrayref of values as the param, you will end up with an arrayref of arrayref(s).

$template->get_valid_singular_constructor_param()
$template->get_valid_multiple_constructor_param()

Override these methods to add to the list of valid parameters that the constructor should accept and place into the %param passed to initialize(). Make sure that you include the contents of $self->SUPER::get_valid_singular_constructor_param() or $self->SUPER::get_valid_multiple_constructor_param() otherwise the standard paramaters won't be accepted as valid.

The singular version lists those param that may only be supplied once to the constructor, and multiple for those that may be supplied more than once.

$template->get_template_candidates( $filename, $dir )
$template->get_include_candidates( $filename, $dir )

These two methods are called to find the candidate filenames to check for existence before loading a template.

They're supplied the filename as passed to the template constructor option or the $template->set_template( $filename ) method, and the current directory.

The current directory is the current working directory for templates and the directory of the template doing the including for includes.

The list they return will be iterated through until the first file that actually exists is found, which will then be used as the template file.

This would allow you to, for example, make your subclass cascade back up a directory structure looking for a matching filename, or to search through a list of include directories.

Note that the filename parameter does not have any template_root prepended, the behaviour of template_root is in fact implemented within the default version of $template->get_template_candidates() and you are free to support or ignore the behaviour in your implementation.

$template->get_additional_dependencies()

This method is called when building a list of dependencies for the current template for the purposes of checking if the cached version of a compiled template is still fresh.

If for some reason your subclass contains dependencies that are not discovered by the existing methods, you can provide your own mechanism here to add more to the list.

An example could be if some behaviour of the compile of your template is effected by entries your application's config file, you could return the filename of the config file here, and whenever the config file is updated any old cache entries will be invalidated.

NOTES ON INTERNAL IMPLEMENTATION AND OTHER GORY DETAILS

This section contains a lot of technical information on how Template::Sandbox is implemented, you probably don't need to know any of this stuff, so feel free to skip this section entirely unless you're morbidly curious or feel it may be relevent to your use.

Parsing and Compilation

Parsing of templates is the first step of the compile phase, it's done by hand-crafted (and exceedingly ugly) regexps.

These regexps make heavy use of the (??{ ... }) subexpression syntax to handle dealing with bracket- and quote-matching.

Sorry to older Perls who don't understand this newfangled stuff.

These regexps could run faster with some of the fancy new perl 5.10 regexp syntaxes designed for subregexps, but 5.10 is a bit too newfangled just now thanks.

These regexps don't run across your entire template at once (thankfully), instead the template is broken down into hunks by opening <: and each hunk is then proccessed in turn.

The first stage of hunk processing is to check that it fits the general format of a known statement, broadly: <: known_statement some_stuff :>, without being too fussy about what some_stuff is, and dumping anything after the closing :> onto the hunk queue. Note that since we're not fussy about what's in some_stuff this imposes limitations on whether we can check we've matched "the right" closing :> or not, see "KNOWN ISSUES AND BUGS" for more on this.

Depending on the statement, some_stuff gets further parsing with appropriate regexps and turned into an instruction and arguments (and sometimes some additional flags) for the compiled template program.

In the case of loops and branches, a stack is maintained of any opening statements, and when the corresponding closing statements are produced the stack is popped and each branch has its jump target updated to point to the statement after the end of the construct.

As you may deduce from the previous paragraph, the compiled program itself is a fairly simple linear list of instructions with jumps to implement flow-control, it could have been implemented as an abstract syntax tree or the like, but it wasn't. Although the structure of a compiled expression is one, just to be contrary.

If you're curious about the structure, you can make use of the $template->dumpable_template() method to produce a somewhat literal dump of the compiled program with a degree of human-readability (for strange humans anyway).

Template Program Optimization

After compilation, several optimization passes are made over the template program to eliminate common inefficiencies:

Expression Constant Folding.

This doesn't actually happen within the optimization sweep, but instead happens as the expression arguments are compiled, it's documented here because this is where you'd expect to find the documentation.

Expressions have constant-folding applied at two main places:

Operator Constant Folding

If both sides of an operator are constant values then the operator is evaluated at compile-time and the result substituted as a constant itself.

For unary operators the same occurs if the single argument is constant.

Note that, currently, constant-folding only occurs if both sides of an operator are constant, even in the case where left-wise lazy-evaluation (aka "short circuit") with a constant LHS would make an inconstant RHS irrelevent.

That is:

<: expr 42 || 0 :>

Will be folded to

<: expr 42 :>

but:

<: expr 42 || a :>

will not be folded even though, when run, the right-hand side will never be evaluated because 42 is always true.

Function Constant Folding

If all arguments to a function are constant and the function wasn't constructed with the inconstant flag, then the function is evaluated at compile-time and the result substituted as a constant value.

"Void Wrapping" of assigns.

Not really an optimization, however this is performed at the same time as the optimization sweep: any assigns at the top level of an expr statement are flagged as being in a void context so that they don't insert their value into the output of the document.

Constant-Expression Constant Folding

Any expr statements that consist solely of a constant value are converted into a literal statement of that value. This doesn't check for constant-folding within an expression since that is done during the compilation stage of expressions automatically.

Conditional Branch Constant Folding

Any if statement branches that are the result of a constant expression are either converted into unconditional branches or pruned entirely from the program accordingly.

Context Folding

Any cases of a CONTEXT_PUSH where there is no need for a new context, such as an include of a file with no scoped variables, are removed.

While the empty CONTEXT_PUSH and CONTEXT_POP itself is fairly painless it adds an extra loop iteration up the context stack to every variable evaluation within that context, which can rapidly add up, so pruning these is a surprisingly "big win". This is also another good reason to use defines within an include statement where possible instead of scoped variables. (See "Defines vs Scoped Variables" for more details.)

The equivilent context-folding for for loops happens at run-time, a marginal gain could be made by pushing this up to the optimization sweep, but would result in significantly more complexity to achieve the same behaviour.

Adjacent-Literal Constant Folding

Any adjacent literal values are now merged into a single literal, unless there's a reason not to (such as the second being the target of a JUMP instruction.)

Especially after the pruning of previous optimizations, this can reduce the number of LITERAL instructions quite significantly, and fewer instructions is always better, especially one so lightweight as LITERAL where the overhead of running the instruction is disproportional to the actual work entailed in the instruction itself.

Special Loop Variable Pruning

Each loop is analyzed to see if the special loop variables are used within that loop and if not a flag is set against that loop to indicate it should skip creating them.

This is an all-or-nothing affair, use of any __inner__, __counter__, etc special variable will cause them all to be created for that loop.

Note that this has nothing to do with access to the loop variable itself. For example, this will optimize:

<: for x in 5 :><: expr x :><: endfor :>

Whereas, this will not:

<: for x in 5 :><: expr x.__inner__ :><: endfor :>

Also note that any non-constant-folded expression subscript against the loop variable cannot be analysed at runtime, so causes the special variables to be created just in case. So, this will not optimize, even if z isn't set to the name of a special variable:

<: for x in y :><: expr x[ z ] :><: endfor :>

The gain per loop is only on the order of 20 microseconds, so really don't stress yourself too much about reaching for this optimization if you're doing anything much at all within the body of the loop.

There are several further candidates for optimizations on the TODO list, the most important is probably to make the for loop context-folding occur in the optimization sweep rather than at run-time.

Template Program Execution

Internal Constants

The following internal constants are used within Template::Sandbox and can be accessed via Template::Sandbox::CONSTANTNAME if needed for some reason.

General indices:

SELF
OP_LHS
OP_RHS

Compiled statement indices:

LINE_INSTR
LINE_POS
LINE_ARG

Instruction opcodes:

LITERAL
DEBUG
EXPR
JUMP
JUMP_IF
FOR
END_FOR
CONTEXT_PUSH
CONTEXT_POP
LOCAL_SYNTAX

Expression opcodes:

OP_TREE
UNARY_OP
FUNC
METHOD
VAR
TEMPLATE

Template function array indices:

FUNC_FUNC
FUNC_ARG_NUM
FUNC_NEEDS_TEMPLATE
FUNC_INCONST
FUNC_UNDEF_OK

Special loop variable array indices:

LOOP_COUNTER
LOOP_EVEN
LOOP_ODD
LOOP_FIRST
LOOP_INNER
LOOP_LAST
LOOP_PREV
LOOP_NEXT
LOOP_VALUE

For loop stack array indices:

LOOP_STACK_COUNTER
LOOP_STACK_LAST
LOOP_STACK_SET
LOOP_STACK_HASH
LOOP_STACK_CONTEXT
LOOP_STACK_SPECIALS

PERFORMANCE CONSIDERATIONS AND METRICS

This section aims to give you a few hints and tips on making your templates run efficiently.

Not all of these points apply in all situations, and many are fairly marginal unless you're running a large and complicated template, these are merely presented as starting points and explanations that may (or may not) prove helpful.

Defines vs Scoped Variables

Where possible you should consider using compile defines instead of scoped variables when you're doing an include of another template, in fact you should consider using compile defines over any kind of variable if possible, but it's particularly relevent to includes:

Pros of Defines over Scoped Variables
Compile-time vs run-time

Compile defines are inserted into the template as literal values as the template is compiled, whereas variables are evaluated at run-time each time they are used.

Allows context-folding of the included template

Whenever you pass scoped variables to an include a new context must be pushed onto the context stack, if no scoped variables are passed then the include is a candidate for context-folding optimizations.

Since the context stack must be traversed for every variable access, this makes access of any variables within your included template (or anything it includes) faster.

Allows constant-folding of expressions using the define

Since compile defines are placed in the template as constant values it make them candidates for constant-folding optimizations, this can't happen with a variable as there's no way of telling that it is unchanging between runs.

Cons of Defines over Scoped Variables
Defines contribute to the cache key, causing more cache misses

When a template is cached it needs to have a cache key that reflects all parameters that contributed to the compiled content, that includes the names and values of all defines.

This means that a template with any defines with differing values from previous compiles will cause a cache-miss, meaning an expensive compile phase and more entries overall in your cache. This may or may not be a downside for you.

Method Calls

Method calls within template expressions are potentially much more inefficient than template functions, where possible you should consider creating a template function instead.

Here's a list of things to consider in the evaluation of a method call within a template, that prevent it from being as fast:

Evaluation of object

The object to call the method on is stored within a template variable which must be evaluated.

Since pretty much all template variables originate in your perl code, it's highly likely that a custom template function could determine the object directly from your application's data-structure eliminating the template variable access.

The same applies to any arguments to the method that originate in template variables.

Object is inconstant

Because the object to call the method on is within a template variable, it cannot be determined at compile-time (even if it does turn out to be a constant value), meaning that methods cannot have constant-folding optimizations applied to them, unlike template functions.

Every method call is actually two method calls

Each call to a method within a template actually translates to two method calls on the object in perl: the first is the call to valid_template_method( $method_name ) to determine if the method call should be permitted.

The cost of calling this permission method may be insignificant (depending on your implementation), but if the method to be called is also insignificant in cost it may, proportionaly speaking, be a large overhead.

Constant-folding vs Operators

Operators only have constant-folding applied if both sub-expressions are constants, even if lazy-evaluation would prevent a right-hand expression from being evaluated.

This is detailed further in "KNOWN ISSUES AND BUGS".

Metrics

Here's a dump of some raw metrics from the benchmark_template_engines script provided by Template::Benchmark.

I'll state up front that I'm also the author of Template::Benchmark, and while I endeavour to make it give unbiased results, I wouldn't want anyone thinking I was trying to pass it off as an independent evaluation. :)

Also note that the template is a very simple template making little use of the advanced features of many of the template systems, since not all systems support equivilent features, this benchmark is the result of the "lowest common denominator" set of features, if you want to check performance of a specific set of features, I heartily (and shamelessly self-promotingly) recommend investigating Template::Benchmark.

I make no claims for how representive these numbers are of any particular real-world situation, and it should be taken as read that I know how to do a better job of optimizing my own templating system's setup compared to others - these results may well be doing other systems an injustice.

With that disclaimer said, the results do seem reasonable and give a broad indication of strengths and weaknesses of some systems, so here's the data.

Output of benchmark_template_engines --progress \
  --norecords_loop_template --novariable_if_template \
  --novariable_if_else_template
--- Starting Benchmarks --------------------------------------------------------
ETA: 73 benchmarks to run = 730 seconds minimum.
progress: 100% [============================================================ ]D 0h14m58s--- Template Benchmark @Sun Mar 21 13:25:14 2010 -------------------------------
HT         - HTML::Template (2.9)
HTC        - HTML::Template::Compiled (0.94)
HTE        - HTML::Template::Expr (0.07)
HTJ        - HTML::Template::JIT (0.05)
HTP        - HTML::Template::Pro (0.93)
MoTe       - Mojo::Template (0.999921)
NT         - NTS::Template (2.1)
TAHT       - Template::Alloy (1.013) in HTML::Template mode
TATT       - Template::Alloy (1.013) in Template::Toolkit mode
TATT_P     - Template::Alloy (1.013) in Template::Toolkit mode (compile to
             perl)
TATT_PS    - Template::Alloy (1.013) in Template::Toolkit mode (compile to
             perl, using process_simple())
TATT_S     - Template::Alloy (1.013) in Template::Toolkit mode (using
             process_simple())
TS         - Template::Sandbox (1.03) without caching
TS_CF      - Template::Sandbox (1.03) with Cache::CacheFactory (1.10)
             caching
TS_CHI     - Template::Sandbox (1.03) with CHI (0.34) caching
TS_FMM     - Template::Sandbox (1.03) with Cache::FastMmap (1.34) caching
TT         - Template::Toolkit (2.22)
TT_X       - Template::Toolkit (2.22) with Stash::XS (no version number)
TT_XCET    - Template::Toolkit (2.22) with Stash::XS (no version number) and
             Template::Parser::CET (0.05)
TTiny      - Template::Tiny (0.11)
TeMMHM     - Text::MicroMason (2.07) using Text::MicroMason::HTMLMason (no
             version number)
TeMMTeTe   - Text::MicroMason (2.07) using Text::MicroMason::HTMLMason (no
             version number)
TeMT       - Text::MicroTemplate (0.11)
TeTe       - Text::Template (1.45)
TeTeSimp   - Text::Template::Simple (0.81)
TeTmpl     - Text::Tmpl (0.33)
Tenj       - Tenjin (0.051)
--- uncached_string ------------------------------------------------------------
           Rate   TTiny TeMMTeTe    HTC     TT   TT_X    HTE TT_XCET   TATT TeTeSimp    TS  TeMT  MoTe  TAHT    HT TeMMHM  TeTe TeTmpl   HTP
TTiny    1.45/s      --     -14%   -83%   -85%   -87%   -89%    -93%   -93%     -94%  -95%  -95%  -95%  -96%  -97%   -97%  -98%  -100% -100%
TeMMTeTe 1.69/s     17%       --   -80%   -83%   -85%   -87%    -91%   -91%     -93%  -94%  -95%  -95%  -95%  -97%   -97%  -98%  -100% -100%
HTC      8.65/s    497%     411%     --   -11%   -25%   -35%    -56%   -56%     -66%  -71%  -72%  -72%  -74%  -82%   -83%  -87%   -99% -100%
TT       9.72/s    571%     474%    12%     --   -16%   -27%    -51%   -51%     -62%  -68%  -69%  -69%  -71%  -80%   -81%  -86%   -98% -100%
TT_X     11.5/s    696%     581%    33%    19%     --   -13%    -41%   -42%     -54%  -62%  -63%  -63%  -66%  -76%   -78%  -83%   -98%  -99%
HTE      13.3/s    820%     687%    54%    37%    16%     --    -32%   -32%     -47%  -56%  -57%  -57%  -60%  -73%   -74%  -80%   -98%  -99%
TT_XCET  19.7/s   1258%    1061%   127%   102%    70%    48%      --    -0%     -22%  -35%  -37%  -37%  -42%  -60%   -62%  -71%   -97%  -99%
TATT     19.7/s   1263%    1065%   128%   103%    71%    48%      0%     --     -22%  -35%  -36%  -37%  -41%  -59%   -62%  -71%   -97%  -99%
TeTeSimp 25.3/s   1647%    1393%   192%   160%   119%    90%     29%    28%       --  -17%  -18%  -19%  -25%  -48%   -51%  -63%   -96%  -99%
TS       30.3/s   1995%    1691%   251%   212%   163%   128%     54%    54%      20%    --   -2%   -3%  -10%  -38%   -41%  -56%   -95%  -99%
TeMT     31.0/s   2040%    1730%   258%   219%   169%   133%     58%    57%      23%    2%    --   -1%   -8%  -36%   -40%  -55%   -95%  -99%
MoTe     31.4/s   2066%    1752%   263%   223%   172%   135%     59%    59%      24%    3%    1%    --   -7%  -36%   -39%  -54%   -95%  -98%
TAHT     33.6/s   2222%    1886%   289%   246%   192%   152%     71%    70%      33%   11%    8%    7%    --  -31%   -35%  -51%   -95%  -98%
HT       48.7/s   3261%    2774%   463%   401%   322%   265%    148%   147%      92%   60%   57%   55%   45%    --    -5%  -29%   -92%  -98%
TeMMHM   51.4/s   3449%    2935%   494%   429%   346%   286%    161%   160%     103%   69%   66%   64%   53%    6%     --  -25%   -92%  -98%
TeTe     68.2/s   4613%    3930%   689%   602%   492%   412%    247%   246%     170%  125%  120%  118%  103%   40%    33%    --   -89%  -97%
TeTmpl    612/s  42196%   36067%  6982%  6200%  5211%  4495%   3015%  3003%    2322% 1919% 1876% 1853% 1722% 1158%  1092%  797%     --  -70%
HTP      2072/s 143027%  122288% 23867% 21218% 17871% 15450%  10441% 10402%    8095% 6732% 6587% 6509% 6064% 4158%  3933% 2937%   238%    --
--- uncached_disk --------------------------------------------------------------
           Rate TeMMTeTe    HTC    HTE TeTeSimp  MoTe    TS    HT    NT TeMMHM  Tenj  TeTe TeTmpl   HTP
TeMMTeTe 1.68/s       --   -80%   -87%     -93%  -94%  -94%  -96%  -96%   -97%  -97%  -98%  -100% -100%
HTC      8.48/s     405%     --   -35%     -66%  -69%  -72%  -82%  -82%   -84%  -87%  -87%   -99% -100%
HTE      13.1/s     676%    54%     --     -48%  -53%  -57%  -72%  -72%   -75%  -80%  -81%   -98%  -99%
TeTeSimp 25.2/s    1398%   197%    93%       --   -9%  -17%  -46%  -46%   -51%  -61%  -63%   -96%  -99%
MoTe     27.7/s    1545%   226%   112%      10%    --   -9%  -40%  -40%   -47%  -57%  -59%   -95%  -99%
TS       30.2/s    1698%   256%   132%      20%    9%    --  -35%  -35%   -42%  -53%  -55%   -95%  -99%
HT       46.3/s    2655%   446%   255%      84%   67%   53%    --   -0%   -11%  -27%  -31%   -92%  -98%
NT       46.4/s    2657%   447%   255%      84%   68%   53%    0%    --   -11%  -27%  -31%   -92%  -98%
TeMMHM   51.9/s    2984%   511%   297%     106%   87%   72%   12%   12%     --  -19%  -23%   -91%  -97%
Tenj     63.9/s    3698%   653%   389%     154%  131%  111%   38%   38%    23%    --   -5%   -89%  -97%
TeTe     67.4/s    3910%   695%   416%     168%  144%  123%   46%   45%    30%    6%    --   -88%  -97%
TeTmpl    570/s   33798%  6618%  4266%    2163% 1961% 1785% 1130% 1129%   999%  792%  745%     --  -72%
HTP      2072/s  123109% 24319% 15768%    8124% 7390% 6752% 4372% 4368%  3895% 3144% 2973%   263%    --
--- disk_cache -----------------------------------------------------------------
           Rate TATT_P TATT_PS    TT  TATT TATT_S  TeMT   HTC TT_XCET  TT_X  TAHT    HT TeTeSimp TS_CF TS_CHI Tenj  HTP
TATT_P   25.2/s     --     -0%  -10%  -23%   -23%  -41%  -44%    -50%  -50%  -59%  -62%     -76%  -80%   -80% -91% -99%
TATT_PS  25.3/s     0%      --   -9%  -22%   -22%  -40%  -44%    -50%  -50%  -58%  -62%     -76%  -80%   -80% -91% -99%
TT       27.9/s    11%     10%    --  -14%   -14%  -34%  -38%    -45%  -45%  -54%  -58%     -74%  -78%   -78% -90% -99%
TATT     32.5/s    29%     29%   17%    --    -0%  -23%  -28%    -36%  -36%  -46%  -51%     -70%  -74%   -74% -88% -98%
TATT_S   32.6/s    29%     29%   17%    0%     --  -23%  -28%    -35%  -36%  -46%  -51%     -70%  -74%   -74% -88% -98%
TeMT     42.5/s    69%     68%   52%   31%    30%    --   -6%    -16%  -16%  -30%  -35%     -60%  -66%   -66% -84% -98%
HTC      45.1/s    79%     78%   62%   39%    38%    6%    --    -11%  -11%  -26%  -32%     -58%  -64%   -64% -83% -98%
TT_XCET  50.5/s   100%    100%   81%   55%    55%   19%   12%      --   -0%  -17%  -23%     -53%  -59%   -60% -81% -98%
TT_X     50.6/s   101%    100%   81%   55%    55%   19%   12%      0%    --  -17%  -23%     -53%  -59%   -60% -81% -98%
TAHT     60.8/s   141%    141%  118%   87%    87%   43%   35%     20%   20%    --   -8%     -43%  -51%   -51% -77% -97%
HT       65.9/s   161%    161%  136%  102%   102%   55%   46%     30%   30%    8%    --     -38%  -47%   -47% -75% -97%
TeTeSimp  107/s   325%    323%  284%  229%   229%  152%  138%    112%  112%   76%   63%       --  -14%   -14% -60% -95%
TS_CF     124/s   394%    392%  346%  282%   282%  193%  176%    147%  146%  105%   89%      16%    --    -1% -53% -94%
TS_CHI    125/s   396%    395%  349%  285%   284%  195%  178%    148%  147%  106%   90%      17%    1%     -- -53% -94%
Tenj      267/s   959%    956%  857%  720%   719%  528%  492%    429%  428%  339%  305%     149%  115%   113%   -- -87%
HTP      2070/s  8111%   8087% 7323% 6261%  6255% 4772% 4493%   4001% 3994% 3304% 3043%    1833% 1564%  1554% 676%   --
--- shared_memory_cache --------------------------------------------------------
         Rate TS_CHI TS_FMM    HTP
TS_CHI  129/s     --    -3%   -94%
TS_FMM  133/s     3%     --   -94%
HTP    2073/s  1505%  1454%     --
--- memory_cache ---------------------------------------------------------------
           Rate    HTE  TeMT  TAHT    HT   HTC TS_CHI TS_CF TeTeSimp   HTP   HTJ
HTE      15.3/s     --  -64%  -77%  -80%  -88%   -88%  -91%     -96%  -99%  -99%
TeMT     42.7/s   179%    --  -36%  -44%  -66%   -67%  -74%     -89%  -98%  -98%
TAHT     67.2/s   340%   57%    --  -12%  -47%   -49%  -59%     -83%  -97%  -97%
HT       76.0/s   397%   78%   13%    --  -40%   -42%  -54%     -81%  -96%  -97%
HTC       128/s   734%  198%   90%   68%    --    -3%  -22%     -68%  -94%  -95%
TS_CHI    131/s   759%  207%   95%   73%    3%     --  -20%     -67%  -94%  -95%
TS_CF     164/s   970%  283%  143%  115%   28%    25%    --     -59%  -92%  -93%
TeTeSimp  402/s  2529%  841%  498%  429%  215%   206%  146%       --  -81%  -83%
HTP      2073/s 13453% 4750% 2983% 2627% 1525%  1478% 1166%     415%    --  -14%
HTJ      2399/s 15584% 5512% 3468% 3056% 1781%  1726% 1366%     497%   16%    --
--- instance_reuse -------------------------------------------------------------
           Rate  TATT TATT_S TATT_P TATT_PS    TT  TeTe  TT_X TT_XCET    TS Tenj TeMMHM TeMMTeTe MoTe
TATT     34.3/s    --    -0%    -4%     -4%  -24%  -78%  -79%    -79%  -80% -88%   -98%     -98% -99%
TATT_S   34.4/s    0%     --    -4%     -4%  -24%  -78%  -78%    -79%  -80% -88%   -98%     -98% -99%
TATT_P   35.8/s    4%     4%     --     -0%  -20%  -77%  -78%    -78%  -79% -88%   -98%     -98% -99%
TATT_PS  35.9/s    5%     4%     0%      --  -20%  -77%  -78%    -78%  -79% -88%   -98%     -98% -99%
TT       45.1/s   31%    31%    26%     26%    --  -71%  -72%    -72%  -74% -84%   -98%     -98% -99%
TeTe      156/s  356%   354%   337%    336%  247%    --   -2%     -2%   -9% -46%   -92%     -93% -95%
TT_X      160/s  365%   364%   345%    345%  254%    2%    --     -0%   -7% -45%   -92%     -93% -95%
TT_XCET   160/s  367%   366%   347%    347%  256%    2%    0%      --   -7% -44%   -92%     -93% -95%
TS        172/s  400%   398%   379%    379%  281%   10%    8%      7%    -- -40%   -91%     -92% -94%
Tenj      288/s  739%   735%   703%    702%  538%   84%   80%     79%   68%   --   -85%     -87% -91%
TeMMHM   1937/s 5546%  5524%  5305%   5301% 4198% 1138% 1113%   1108% 1028% 573%     --     -13% -37%
TeMMTeTe 2219/s 6366%  6342%  6090%   6085% 4822% 1318% 1290%   1284% 1192% 671%    15%       -- -28%
MoTe     3096/s 8924%  8890%  8538%   8532% 6769% 1879% 1839%   1831% 1703% 976%    60%      40%   --

As can be seen from the results, Template::Sandbox ranks well when it comes to on-disk caching with only the libtmpl-based Text::Tmpl and the compile-to-C HTML::Template::Pro running faster.

You can also see this when looking at the in-memory caching, where the performance gap is even larger among the mostly-perl template modules, all of whom trail far behind the more exotic compilation methodologies of HTML::Template::Pro and HTML::Template::JIT.

With the instance-reuse caching (similar but not directly comparable to in-memory caching), Template::Sandbox still ranks highly, with only the "embedded-perl" template engines of Text::MicroMason and Mojo::Template performing better (much much better it must be said). Of particular note is the performance of TT_XCET, which is Template::Toolkit running with the XS version of its stash and parser, surpassed by the pure-perl Template::Sandbox. Since Template::Sandbox and Template::Toolkit are roughly comparable as being heavyweights in terms of features, this makes a useful comparison.

When it comes to the string-template benchmarks, you can see the impact of being forced to compile the template each time, this is representative of a cache-miss if you're using caching, and here Template::Sandbox shows a fairly mediocre performance, partly due to its pure-perl implementation and partly due to the heavy optimization phase - you can see similar costs in all the more heavily-compiled template systems.

This paints only the broadest picture and is the tip of the iceberg when it comes to template benchmarking, if you're interested in the numbers, I suggest looking at producing your own reports using Template::Benchmark.

PRIVATE AND SEMI-PRIVATE METHODS

You're unlikely to ever need to call these methods directly, or to change them when subclassing Template::Sandbox.

$template->find_template( $filename, $current_dir )
$template->find_include( $filename, $current_dir )

These two methods find a matching template for the given filename and dir, they basically query $template->get_template_candidates() or $template->get_include_candidates() and then traverse the list looking for a file that exists.

COMPAT NOTE: $current_dir will be undef in most cases when find_template is called, previous versions passed Cwd::cwd() in, however this imposed dramatic performance penalties if you didn't need it.

$template->cache_key( $keys )

Takes a hashref of parameters that uniquely identify the factors that could alter the compiled template and produces a scalar value suitable to use as a cache key.

In practice the key is a hashref of the defines (including template filename) used in compiling the template, and the result is an MD5 hexdigest of a canonical nfreeze( $keys ) from Storable.pm.

$template->log_error( $message )
$template->log_warning( $message )

Logs $message with the logger object as an error or warning.

$template->error( @message_fragments )
$template->caller_error( @message_fragments )

Raises (and logs) an error with the message produced by concatinating @message_fragments into a single string. caller_error() reports the error from the point of view of the calling code.

The error will have any relevent information to do with the current template position added.

$template->fatal_exit( $message )
$template->caller_fatal_exit( $message )

These two do the actual die or croak needed by error and caller_error, you can override these if you want to prevent the die or croak and perform some other behaviour.

$template->warning( @message_fragments )
$template->caller_warning( @message_fragments )

Raise (and log) a warning with a message composed of the message fragments provided. caller_warning() raises the warning from the perspective of the caller.

The warning will have any relevent information to do with the current template position added.

QUALITY ASSURANCE AND TEST METRICS

As of version 1.04 there are 1993 tests within the distribution, if the this isn't the current version number, I've forgotton to update this section, sorry. :)

The tests have coverage:

---------------------------- ------ ------ ------ ------ ------ ------ ------
File                           stmt   bran   cond    sub    pod   time  total
---------------------------- ------ ------ ------ ------ ------ ------ ------
blib/lib/Template/Sandbox.pm   98.7   91.9   89.2  100.0  100.0   99.8   95.7
...mplate/Sandbox/Library.pm  100.0  100.0   36.4  100.0  100.0    0.1   93.1
...andbox/NumberFunctions.pm  100.0    n/a    n/a  100.0    n/a    0.0  100.0
...andbox/StringFunctions.pm  100.0    n/a    n/a  100.0    n/a    0.0  100.0
Total                          98.8   92.2   86.8  100.0  100.0  100.0   95.7
---------------------------- ------ ------ ------ ------ ------ ------ ------

You can generate this report within the distribution's directory by:

perl Build.PL
./Build testcover

Pretty HTML reports will also be produced in the cover_db/ subdirectory.

Why these coverage figures will never be 100%

Most uncovered statements and branches are "cannot happen" internal sanity checks, the low conditional coverage reflects frequent use of

function( $thing ) || $thing

constructs with always-true values vs the slower:

function( $thing ) ? function( $thing ) : $thing

Devel::Cover then thinks (correctly but irrelevently for this purpose) that the false-false case hasn't been tested and so gives a 67% coverage to the condition.

KNOWN ISSUES AND BUGS

caller_error() and caller_warning() currently degraded.

Carp currently is intermittantly taking fatal issue in certain places when generating carp() or croak()s, until I've resolved this issue these two methods behave as the error() and warning() messages and don't report from the perspective of your calling code.

line numbers and char counts partially broken.

Line numbers and character counts into the original file are still occassionally incorrect in a couple of (mostly pathological) situations where template defines either contain fragments of a template statement that spans the boundary of the statement, or that contain partial defines themselves.

For example, both of these will confuse the character position count:

#  ${EGADS} overlaps one, but not both, end of the <: expr :> statement.
$template->set_template_string(
    "This is${EGADS}ological' :>.",
    {
        EGADS => " <: expr 'path",
    } );

#  ${ERKLE} overlaps one, but not both, end of ${HURKLE}.
$template->set_template_string(
    "And so${ERKLE}KLE}this.",
    {
        ERKLE  => '${HUR',
        HURKLE => " <: expr 'is' :> ",
    } );

Although in both these examples the expr will be constant-folded away and the misnumbered positions will be undetectable. Examples that don't optimize away are more complicated and unfortunately obscure the root problem behaviour.

Quite what line numbers result from these constructs is undefined and unsupported, and possibly will be subject to change without notice.

This bug is not likely to be corrected soon unless it impacts people trying to do something sane. :)

Quoted-':>' inside expressions still terminate the statement

Because of the implementation of the parsing of statements, the next :> after an opening <: will always be consided the closing delimeter, even if it's enclosed within quotes within an expression.

Whilst this is almost certainly a bug (in that it doesn't do what you clearly intended it to do), it's unlikely to be resolved since it simplifies and optimizes the parsing phase very considerably, and as a work-around you can use either of the following:

<: expr ':\>' :>
<: expr ':' . '>' :>

to achieve the intended (but broken):

<: expr ':>' :>

As a side-note, because of compile-time constant-folding, the resulting compiled template is no different than that which would have been achieved by the intended code.

cr special variable instead of \n.

Needing to use cr as a special variable instead of the expected \n interpolation in strings is pretty ugly and awkward.

No last or continue within loops.

Although it helps prevent people getting too exotic with the complexity in their templates, instead of in the application layer where it probably belongs, having no last or continue can definitely be counted as a missing feature.

Can't set filename scoped variable in an include.

Because the include statement internally uses the filename argument name to pass the name of the included file around, this prevents you from setting up a filename variable local to the include from the include statement, eg:

<: include included_file.html filename=widget.selected_file :>

Will in fact try to include a file named 'widget.selected_file' when the template is compiled.

The include statement should probabably use an internal argument name that isn't going to clash in this manner.

Flow Control Constructs inside multiple files.

Building an if-construct or for-loop where the statements are in different files is currently unsupported and undefined in behavior, it might do what you want, it might not. It may just die horribly. It may not do any of those things consistently. It is certainly subject to change in future versions, when the behaviour may become defined.

Note that it's perfectly ok to span multiple files, as long as the include statement is entirely nested within the flow control structure, ie, this is fine and expected:

<: for row in table :>
<: include table_row.html :>
<: end for :>

This however will probably cause problems:

<: if a :>
A is true.
<: include subclauses.html :>
<: else :>
Nothing is true.
<: endif :>

Then in subclauses.html:

<: elsif b :>
B is true.

Quite what it will do in this situation is undefined and subject to a number of variables depending on the exact circumstances, what is certain is that it won't reliabily be behaving correctly.

"Short-circuit" operators only partially constant-folded

As detailed in "Template Program Optimization", operators that "short circuit" down the left-hand side are only subject to constant-folding optimizations if both sides of the operator are constants, even in situations where the "short circuit" would make the RHS irrelevent at run-time.

This is only a (probably minor) performance bug, and in no way impacts correct behaviour.

Assigns within context-folded for loops persist

There's a subtly inconsistent behaviour between for loops that have been context-folded and those that haven't, if there is an assign to a new template variable within the loop, in the non-folded case, the new variable will exist only for the duration of the loops, whereas the context-folded case will cause the new variable to exist until the end of the outer context the for loop was called from.

This is a bug, and the correct behaviour would be to not context-fold the loop if there's an assign inside, however this is difficult to test until context-folding of loops is performed during the compile-phase optimization rather than at runtime.

Void-context assigns should be zero-width

Since void-context assigns produce no template output, they should be subject to zero-width folding, currently however the void-context flagging happens (long) after the zero-width folding, so this doesn't happen.

There's probably no reason why void-context flagging couldn't happen as part of the expression compilation stage, which would be in time to also flag it as zero-width, so this should be expected to happen in a future release.

JMP_IF confusingly named

The JMP_IF instruction is confusingly misnamed, since it's really a JMP_UNLESS instruction. That is it means "jump if the expression is false" as opposed to the intuitive "jump if the expression is true". Unless you're messing with the compiled template for some reason, this is unlikely to effect you.

SEE ALSO

Template::Sandbox::Library, Template::Sandbox::NumberFunctions, Template::Sandbox::StringFunctions, Cache::CacheFactory, Cache::Cache

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Template::Sandbox

You can also look for information at:

THANKS

Thanks to Paul Seamons for creating the benchmark script distributed with Template::Alloy, the benchmarks in the "PERFORMANCE CONSIDERATIONS AND METRICS" section were generated with a modified version of this script.

AUTHORS

Original author: Sam Graham <libtemplate-sandbox-perl BLAHBLAH illusori.co.uk>

Last author: $Author: illusori $

COPYRIGHT & LICENSE

Copyright 2005-2010 Sam Graham, all rights reserved.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.