NAME

Template::Plex - (P)erl (L)exical and (EX)tendable Templating

SYNOPSIS

Write a template:

    __DATA__        
    @{[ 
        init {
    use Time::HiRes qw<time>;
    $title="Mr.";
        }
    ]}

    Dear $title Connery,
    Ordered a $size pizza with $slices slices to share between @$people and
    myself.  That averages @{[$slices/(@$people+1)]} slices each.

Use a template:

      use Template::Plex;

      my $vars={
              size=>"large",
              slices=>8,
              people=>[qw<Kim Sam Harry Sally>]
      };

      my $template= Template::Plex->load(\*DATA, $vars);

      print $template->render;        



      #OUTPUT
      Dear Mr. Connery,
      Ordered a large pizza with 8 slices to share between Kim, Sam, Harry,
      Sally and myself.  That averages 1.6 slices each.     
      

Change values and render it again:

    $vars->{size}="extra large";
    $vars->{slices}=12;

    print $template->render;


    #OUTPUT
    Dear Mr. Connery,
    Ordered a extra large pizza with 12 slices to share between Kim, Sam,
    Harry, Sally and myself.  That averages 2.4 slices each.

When the template is no longer needed:

$template->cleanup;

DESCRIPTION

Template::Plex facilitates the use of Perl (not embedded perl) as a template language. It implements bootstrapping and a system to load, cache, inherit and render templates with minimal code.

The 'lexical' part of this module refers to the lexical aliasing of input variables into the template. This reduces the amount of markup required to reference variables and thus improves the style and readability of a template.

Templates can be extended and reused with sub templates and inheritance. The template system itself can be extended by sub classing Template::Plex and implementing customised load routines and other helper methods.

The short tutorial in this document plus the examples included in the distribution cover the basics to get you started. Reading through the load API options is also recommended to get a better understanding on how templates are processed.

MOTIVATION

Many templating systems are available, yet none use Perl as the template language? Perl already has a great text interpolation, so why not use it?

Lexical aliasing allows the input variables to be accessed directly by name (i.e. $name) instead of as a member of a hash ref (i.e.$fields->{name}) or by delimiting with custom syntax (i.e. <%=name%>)

I like the idea of Jekyll's 'Front Matter', but think its potential is limited as it can only support variables and not code. With Perl's flexible syntax introducing code is doable.

TUTORIAL

Syntax Genesis

We all know how to interpolate variables into a string in Perl:

    "This string $uses a $some{variables}"

But how can we easily interpolate a statement, function or method call? We can use the @{[]} construct.

    "This is a perl string interpolating @{[ map uc, qw<a b c d>]}"

If we need multiple statements, we can combine with a do statement. Like always the last statement executed in a do block is returned ( and interpolated into the string):

    "This is a perl string interpolating 
    @{[ do {
  my $result="STATEMENTS";
  ...
  lc $result;
      }
    ]}
    "

Combining the above examples, we make a Template::Plex template simply by removing the outer quoting operators:

    This string $uses a $some{variables}

    This is a perl string interpolating @{[ map uc, qw<a b c d>]}

    This is a perl string interpolating 
    @{[ do {
            my $result="STATEMENTS";
            lc $result;
            }
    ]}

A Template::Plex template is just Perl! The above is the literal text you can save to a file and load as a template.

Specifically, a Template::Plex template it is the subset of Perl that's valid between double quotation operators.

Smart Meta Data and Code

Templates can include an init{} block at the beginning which is executed (only once) during the setup stage of the template. In some ways this is similar to Jekyll 'Front Matter', but more powerful. You can manipulate input variables, define helper subroutines, or import modules:

    @{[ init {
  use Time::HiRes qw<time>;
  sub my_func{ 1+2 };
      }
    ]}

    Calculated @{[my_func]} at @{[time]}

The init block does not inject any content into a template, but manipulates the state of a template.

Each template has access to it self, using the $self variable. This comes in very handy when loading sub templates and doing more advanced task or even extending the template system.

So far we have seen the do and init directives. General code can also be executed with a pl block. This does the same as do but does not inject the result into the template.

Loading and Rendering

There are a few ways of executing a template from your application. Each of them are accessible via class methods:

    #Load a template and render later
    my $template=Template::Plex->load($path, $vars, %options);              
    my $result=$template->render;

    #Load a template from cache and render later
    my $template=Template::Plex->cache($key, $path, $vars, %options);
    my $result=$template->render;

    #Load from cache and render now
    my $result= Template::Plex->immediate($path, $vars, %options);  

A load call returns a new template object each time, where a cache call returns a template already loaded (or loads it once), for a user defined key. The returned template is then rendered by calling the render method.

The immediate call loads and caches a template with a user defined key and then immediately renders it, returning the results.

Template Reuse

Reusing templates can reduce the repetitive nature of content. Template::Plex provides multiple facilities for template reuse.

Sub Templates

A sub template is just another template. While you can load a sub template with the class methods shown previously, it's not recommended. This is because you would need to specify all the variables and options again manually.

You normally would like to pass on the same variables and options to sub templates, so a better way is to call the same method on the $self object:

    @{[$self->load(...)]}
    @{[$self->cache(...)]}
    @{[$self->immediate(...)]}

This will automatically link the variables and relevant options to be the same as the current template.

Better still, these methods are made available within a template simply as a subroutine call:

    @{[load ... ]}
    @{[cache ... ]}
    @{[immediate ...]}

Slots and Inheritance

A sub template can be used at any location within a template. However there are special locations called slots. These are defined with the slot directive:

    @{[slot slot1=>"some text"]}

    @{[slot slot_abc=> cache "sub template"]}

    @{[slot]}

The slot name can be any string and the value can either be text or a template object. This value is the default value, which is used when no child template wants to fill the slot.

A slot named 'default' (or no name) is special and is the location at which a child template body will be rendered.

A child template can also fill other slots in the parent by explicitly using the fill_slot directive. The value can be text or a loaded template

    @{[fill_slot name=>"override content"]}
    @{[fill_slot another=>load "path to template"]}

Child can setup inheritance by using the inherit directive within a init block, specifying the template to use as the parent:

    @{[ init {
            inherit "my_parent.plex";
            }
    ]}

The following is an example showing a child template inheriting from a parent. The child will provide content to the default slot in the parent and also override the 'header' slot with another template which it loads:

Parent Template:

    @{[slot header=>"==HEADER=="]}
    More parent content...
    @{[slot]}
    @{[slot footer=>"==FOOTER=="]}

Sub template (header):

    -=-=-=Fancy header=-=-=-

Child template:

    @{[ init {
            
            inherit "parent.plex";
        }
    ]}

    @{[slot header=> load "header.plex";
    This content will render into default slot

Inclusion

Depricated. Please use sub templates to achieve the same result Much like the C language preprocessor, including an other template or other file will do a literal copy of its contents into the calling template. The resulting text is processed again and again as long as more include statements are present:

    @{[include("...")]}

This basically makes a single large template. As such the included templates will use the same aliased variables.

In simple use cases, it is similar to loading a sub template. However it lacks the flexibility of sub templates.

Logging and Error Handling

As templates are executed, they may throw an exception. If a syntax error or file can not be read, an exception is also thrown during load.

In the case of a syntax error, die is called with a summary of template, prefixed with line numbers which caused the error. Currently 5 line before and after the error are included for context generated by Error::Show. Deliberately breaking the synopsis example (see examples dir) gives the following error output:

perl -I lib examples/synopsis_syntax_error.pl
GLOB(0x7f8510025928)
 1   {@{[
 2       init {
 3    use Time::HiRes qw<time>;
 4     a+1
 5=>  $title="Mr.";
 6       }
 7   ]}
 8   Dear $title Connery,
 9   Ordered a $size pizza with $slices slices to share between @$people and
10   myself.  That averages @{[$slices/(@$people+1)]} slices each.}
syntax error at GLOB(0x7f8510025928) 5 near "1

It is recommended to use a try/catch block to process the errors.

Currently Log::ger combined with Log::OK is utilised for logging and debugging purposes. This comes in very handy when developing sub classes.

Also note that line numbers reported in errors can be inaccurate when the block_fix and include features are in use, as the content of the source file is altered

Filters

Unlike other template system, there are no built in filters. However as Template::Plex templates are just Perl you are free to use builtin string routines or import other modules in to your template.

API

load

    #Class method. Used by top level applciation
    Template::Plex->load($path, $vars, %opts);

    #Object method. Used within a template
    @{[$self->load($path, $vars, %opts);    

    #Subroutine. Prefered within a template. 
    @{[load $path, $vars, %opts]}                   

    #Reuse existing $vars and %opts from withing a template
    @{[load $path]}
    
    

A factory method, returning a new instance of a template, loaded from a scalar, file path or an existing file handle.

From a top level user application, the class method must be used. From within a template, either the object method form or subroutine form can be used.

If no variables or options are specified when loading a sub templates, the variables and options from the calling templates are reused.

Arguments to this function:

cache

      # Class method
# 
      Template::Plex->cache($key, $path, $vars, %options);

      # Object method
      $self->cache($key, $path, $vars, %options); 

      # Subroutine
      cache $key, $path, $vars, %options;
              #Force the current line/package/template as a key

      cache undef, $path, $vars, $%opts;

# v0.6.0 onwards also supports additional arguments forms

# Implicit key, explicit variables and options
#
Template::Plex->cache($path, $vars, %options);
      $self->cache($path, $vars, %options);
      cache $path, $vars, %options;

# Implicit key and implicit variables/options
#
      Template::Plex->cache($path);   
      $self->cache($path);   
      cache $path;   

This is a wrapper around the load API primarily used to improve performance of sub templates used in loops.

From v0.6.0: If the number of arguments passed to the cache functions/method is 1, it is assumed to be a path and an implicit cache key is used and implicit reuse of variables and options is assumed. If the number of arguments is larger than 1, AND the second argument is a hash ref of variables, then an implicit cache key is used and the first argument is expected to be a path.

Otherwise an explicit cache key is expected as the first argument and the second argument is expected to be a path.

Subsequent calls with the same key will return the already loaded template from active cache.

If called from the top level user application, the cache is shared. Templates have their own cache storage to prevent cross collisions.

If the explicit key provided is undef or an implicit key is used, then information about the caller (including the line number, package and target template) is used generate one. This approach allows for a template which maybe rendered multiple times in a loop, to only be loaded once for example.

Returns the loaded or cached template

immediate

      # Class method
      Template::Plex->immediate($key, $path, $vars, %options);
      
      # Object method
      $self->immediate($key, $path, $vars, %options);
      # Subrutine
      immediate $key, $path, $vars, %options;

      #Use current line/package/template as key
      immediate undef, $path, $vars, %options;


# v0.6.0 onwards also supports additional arguments forms

# Implicit key, explicit variables and options
      Template::Plex->immediate($path, $vars, %options);
      $self->immediate($path, $vars, %options);
      immediate $path, $vars, %options;

# Implicit key and implicit variables/options
#
      Template::Plex->immediate($path);
      $self->immediate($path);
      immediate $key;

Loads and renders a template immediately. Uses the same arguments as cache. Calls the cache API but also calls render on the returned template.

From v0.6.4: The vars argument is also used as the extra fields for a render call. This allows for an immediately loaded/rendered template to now use field values as well the initial lexical variables.

From v0.6.0: Please refere to the cache api on details regarding argument handling

Returns the result of the rendered loaded/cached template.

include

    @{[include("path")}]

Depricated and will be removed in later versions. For new code just use load and with no vars to get the same result but with better debugging ability.

This is a special directive that replaces the directive with the literal contents of the file pointed to by path in a similar style to #include in the C preprocessor. This is a preprocessing step which happens before the template is prepared for execution.

If root was included in the options to load, then it is prepended to path if defined.

When a template is loaded by load the processing of this is subject to the no_include option. If no_include is specified, any template text that contains the @{[include("path")}] text will result in a syntax error

NOTE: in the case of a syntax error present in the template, the line numbers maybe incorrect if include is used, as it effectively adds lines to the template source.

pl

block

    @{[ block { ... } ]}

            # or 

    @{[ pl { ... }  ]}

A subroutine which executes a block just like the built in do. However it always returns an empty string.

Only usable in a template @{[]} construct, to execute arbitrary statements. However, as an empty string is returned, Perl's interpolation won't inject anything at that point in the template.

If you DO want the last statement returned into the template, use the built in do.

    eg
            
            @{[
                    # This will assign a variable for use later in the template
                    # but WILL NOT inject the value 1 into template when rendered
                    pl {
                            $i=1;
                    }

            ]}


            @{[
                    # This will assign a variable for use later in the tamplate
                    # AND immediately inject '1' into the template when rendered
                    do {
                            $i=1
                    }

            ]}

init

    @{[ init {...} ]}

It is used to configure or setup meta data for a template and return immediately. It takes a single argument which is a Perl block.

Only the first init {...} block in a template will be executed.

A init {...} block is executed once, even when the template is rendered multiple times

Before the block is run, the pre_init method is called. After the block is run, the post_init method is called.

After the initialisation stages have run, a initialisation flag is set and the remainder on the template is skipped with the skip method.

This means only the first init block in a template will be executed

pre_init

Do not call this directly. It is called internally by an init block. Implemented as an empty method designed to be overridden in a subclass.

post_init

Do not call this directly. It is called internally by an init block. Implemented as an empty method designed to be overridden in a subclass.

inherit

    @{[ init {
            inherit "Path to template";
            }
    ]}

Specifies the template which will is the current template's parent. The current template will be rendered into the default slot of the parent.

slot

    @{[slot name=>$value]}

Declares a slot in a template which can be filled by a child template calling fill_slot directives.

name is the name of the slot to render into the template. If not specified, the slot is the default slot which will be rendered by the content of a child template.

$value is optional and is the default content to render in the case a child does not provide data for the slot. It can be a scalar value or a template loaded by load or cache

fill_slot

    @{[fill_slot name=>$value]}

Fills an inherited slot of name name with $value, replacing the current contents.

The default slot cannot be specified. It is filled with the rendered result of the child template.

append_slot

    @{[append_slot name=>$value]}

Appends to an inherited slot of name name with $value.

prepend_slot

    @{[prepend_slot name=>$value]}

Prepends to an inherited slot of name name with $value.

fill_var

@{[fill_var name=>$value]}

Sets the value of a package variable of name with $value. Useful for shared global variables accessible outside of template inheritance. Returns an empty string.

append_var

@{[append_var name=>$value]}

Appends $value to an global package variable of name name. Returns an empty string.

prepend_var

@{[prepend_var name=>$value]}

Prepends $value to an global package variable of name name. Returns an empty string.

clear

    clear;

Subject to change. Clears the cached templates

jmap

    jmap {block} $delimiter, @array

Performs a join using $delimiter between each item in the @array after they are processed through block

Very handy for rendering lists:

    eg
            <ul>
                    @{[jmap {"<li>$_</li>"} "\n", @items]}
            </ul>

Note the lack of comma after the block.

skip

Causes the template to immediately finish, with an empty string as result. From within a template, either the class method or template directive can be used:

    @{[$self->skip]}
    @{[skip]}

meta

Returns the options hash used to load the template. From within a template, it is recommended to use the %options hash instead:

    @{[$self->meta->{file}]}
            or
    @{[$options{file}]}

This can also be used outside template text to inspect a templates meta information

    $template->meta;

args

Returns the argument hash used to load the template. From within a template, it is recommended to use the aliased variables or the %fields hash instead:

    @{[$self->args->{my_arg}]}
            or
    @{[$fields{my_arg}]}

            or
    $my_arg

This can also be used outside template text to inspect a templates input variables

    $template->args;

parent

    $self->parent;

Returns the parent template.

render

    $template->render($fields);

This object method renders a template object created by load into a string. It takes an optional argument $fields which is a reference to a hash containing field variables. fields is aliased into the template as %fields which is directly accessible in the template

    eg
            my $more_data={
                    name=>"John",
            };

            my $string=$template->render($more_data);
            
            #Template:
            My name is $fields{John}

Note that the lexically aliased variables setup in load are independent to the %fields variable and can both be used simultaneously in a template

cleanup

$template->cleanup;

After a template is no longer needed, this is called to release internal variables. Due to the complex relationship with aliasing, simply letting the variable going storing the template going out of scope will not destroy the template

SUB CLASSING

Sub classing is as per the standard Perl use parent. The object storage is actually an array.

Package constants are defined for the indexes of the fields along with KEY_OFFSET and KEY_COUNT to aid in adding extra fields in sub classes.

If you intend on adding additional fields in your class you will need to do the following as the object

    use parent "Template::Plex";

    use constant KEY_OFFSET=>Template::Plex::KEY_OFFSET+ Template::Plex::KEY_COUNT;

    use enum ("first_field_=".KEYOFFSET, ..., last_field_);
    use constant  KEY_COUNT=>last_field_ - first_field_ +1;

Any further sub classing will need to repeat this using using your package name.

FEATURE CHEAT SHEET

TIPS ON USAGE

Potential Pitfalls

More on Input Variables

If the variables to apply to the template completely change (note: variables not values), then the aliasing setup during a load call will not reflect what you want.

However the render method call allows a hash ref containing values to be used. The hash is aliased to the %fields variable in the template.

    my $new_variables={name=>data};
    $template->render($new_variables);

However to use this data the template must be constructed to access the fields directly:

    my $template='my name is $fields{name} and I am $fields{age}';

Note that the %field is aliased so any changes to it is reflected outside the template

Interestingly the template can refer to the lexical aliases and the direct fields at the same time. The lexical aliases only refer to the data provided at preparation time, while the %fields refer to the latest data provided during a render call:

    my $template='my name is $fields{name} and I am $age

    my $base_data={name=>"jimbo", age=>10};

    my $override_data={name=>"Eva"};

    my $template=load $template, $base_data;

    my $string=$template->render($override_data);
    #string will be "my name is Eva and I am 10

As an example, this could be used to 'template a template' with global, slow changing variables stored as the aliased variables, and the fast changing, per render data being supplied as needed.

ISSUES

Enabling lexically scoped features (i.e. use feature "say") is only in the block it used in. Unfortunately that means that features enabled in an init block will not be active in subsequent blocks. The inject or use option would need to be utilised to achieve this currently.

Templates are completely processed in memory. A template can execute sub templates and run general IO code, so in theory it would be possible to break up very large data templates and stream them to disk...

This module uses eval to generate the code for rendering. This means that your template, being Perl code, is being executed. If you do not know what is in your templates, then maybe this module isn't for you.

Aliasing means that the template has write access to variables outside of it. So again if you don't know what your templates are doing, then maybe this module isn't for you

Using normal Perl comments requires spreading the @{[]} over multiple lines

Perl is pretty smart with matching interpolation delimiters. However in the case your template is generating code, unmatched '{' or '}' will need to be escaped (ie '\{' and '\}')

TODO

Extending the template system has been mentioned but not elaborated on. Probably need to make an other tutorial document.

SEE ALSO

Yet another template module right?

Do a search on CPAN for 'template' and make a cup of coffee.

REPOSITORY and BUG REPORTING

Please report any bugs and feature requests on the repo page: GitHub

AUTHOR

Ruben Westerberg, drclaw@mac.com

COPYRIGHT AND LICENSE

Copyright (C) 2023 by Ruben Westerberg

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