NAME

DTL::Fast - Perl implementation of Django templating language.

VERSION

Version 1.612

SYNOPSIS

Complie and render template from code:

use DTL::Fast;
my $tpl = DTL::Fast::Template->new('Hello, {{ username }}!');
print $tpl->render({ username => 'Alex'});

Or create a file: template.txt in /home/alex/templates with contents:

Hello, {{ username }}!

And load and render it:

use DTL::Fast qw( get_template );
my $tpl = get_template( 'template.txt', ['/home/alex/templates'] );
print $tpl->render({ username => 'Alex'});

DESCRIPTION

This module is a Perl and stand-alone templating system, cloned from Django templating sytem, described in here.

Goals

Goals of this implementation are:

  • Speed in mod_perl/FCGI environment

  • Possibility to cache using files/memcached

  • Maximum compatibility with original Django templates

Current status

Current release implements almost all tags and filters documented on Django site.

There are no significant speed optimizations done yet.

Internationalization and localization are not yet implemented.

BASICS

You may get template object using three ways.

Constructor

Using DTL::Fast::Template constructor:

use DTL::Fast;

my $tpl = DTL::Fast::Template->new(
    $template_text,                             # template itself
    'dirs' => [ $dir1, $dir2, ... ],            # optional, directories list to look for parent templates and includes
    'ssi_dirs' => [ $ssi_dir1, $ssi_dir1, ...]  # optional, directories list allowed to used in ssi tag (ALLOWED_INCLUDE_ROOTS in Django)
    'url_source' => \&uri_getter                # optional, reference to a function, that can return url template by model name (necessary for url tag)
);

get_template

use DTL::Fast qw(get_template);

my $tpl = get_template(
    $template_path,                             # path to the template, relative to directories from second argument
    'dirs' => [ $dir1, $dir2, ... ],            # mandatory, directories list to look for parent templates and includes
    'ssi_dirs' => [ $ssi_dir1, $ssi_dir1, ...]  # optional, directories list allowed to used in ssi tag (ALLOWED_INCLUDE_ROOTS in Django)
    'url_source' => \&uri_getter                # optional, reference to a function, that can return url template by model name (necessary for url tag)
);

when you are using get_template helper function, framework will try to find template in following files: $dir1/$template_path, $dir2/$template_path ... Searching stops on first occurance.

select_template

use DTL::Fast qw(select_template);

my $tpl = select_template(
    [ $template_path1, $template_path2, ...],   # paths to templates, relative to directories from second argument
    'dirs' => [ $dir1, $dir2, ... ],            # mandatory, directories list to look for parent templates and includes
    'ssi_dirs' => [ $ssi_dir1, $ssi_dir1, ...]  # optional, directories list allowed to used in ssi tag (ALLOWED_INCLUDE_ROOTS in Django)
    'url_source' => \&uri_getter                # optional, reference to a function, that can return url template by model name (necessary for url tag)
);

when you are using select_template helper function, framework will try to find template in following files: $dir1/$template_path1, $dir1/$template_path2 ... Searching stops on first occurance.

render

After parsing template using one of the methods above, you may render it using context. Context is basically a hash of values, that will be substituted into template. Hash may contains scalars, hashes, arrays, objects and methods. Into render method you may pass a Context object or just a hashref (in which case Context object will be created automatically).

use DTL::Fast qw(get_template);

my $tpl = get_template(
    'hello_template.txt',          
    'dirs' => [ '/srv/wwww/templates/' ]
);

print $tpl->render({ name => 'Alex' });
print $tpl->render({ name => 'Ivan' });
print $tpl->render({ name => 'Sergey' });

or

use DTL::Fast qw(get_template);

my $tpl = get_template(
    'hello_template.txt',          
    'dirs' => [ '/srv/wwww/templates/' ]
);

my $context = DTL::Fast::Context->new({
    'name' => 'Alex'
});
print $tpl->render($context);

$context->set('name' => 'Ivan');
print $tpl->render($context);

$context->set('name' => 'Sergey');
print $tpl->render($context);

register_tag

use DTL::Fast qw(register_tag);

register_tag(
    'mytag' => 'MyTag::Module'
);

This method registers or overrides registered tag keyword with handler module. Module will be loaded when first encountered during template parsing. About handler modules you may read in "CUSTOM TAGS" section.

preload_tags

use DTL::Fast qw(preload_tags);

preload_tags();

Preloads all registered tags modules. Mostly for debugging purposes or persistent environment stability.

register_filter

use DTL::Fast qw(register_filter);

register_filter(
    'myfilter' => 'MyFilter::Module'
);

This method registers or overrides registered filter keyword with handler module. Module will be loaded when first encountered during template parsing. About handler modules you may read in "CUSTOM FILTERS" section.

preload_filters

use DTL::Fast qw(preload_filters);

preload_filters();

Preloads all registered filters modules. Mostly for debugging purposes or persistent environment stability.

register_operator

use DTL::Fast qw(register_operator);

register_operator(
    'xor' => [ 1, 'MyOps::XOR' ],
    'myop' => [ 0, 'MyOps::MYOP' ],
);

This method registers or overrides registered operator handlers. Handler module will be loaded when first encountered during template parsing.

Arguments hash is:

'operator_keyword' => [ precedence, handler_module ]

Currently there are 9 precedences from 0 to 8, the lower is less prioritised. You may see built-in precedence in the DTL::Fast::Expression::Operator module.

More about custom operators you may read in "CUSTOM OPERATORS" section.

preload_operators

use DTL::Fast qw(preload_operators);

preload_operators();

Preloads all registered operators modules. Mostly for debugging purposes or persistent environment stability.

TEMPLATING LANGUAGE

Tags

This module supports almost all built-in tags documented on official Django site. Don't forget to read incompatibilities and extensions sections.

block_super

New tag for using with inheritance in order to render a parent block. In Django you are using {{ block.super }} which is also currently supported, but depricated and will be removed in future versions.

firstofdefined

New tag, that works like firstof tag, but checks if value is defined (not true)

sprintf

{% sprintf pattern var1 var2 ... varn %}

Works exactly like a perl's sprintf function with pattern and substitutions. This tag was recently implemented and should be considered as experimental.

url

url tag works a different way. Because there is no framework around, we can't obtain model's path the same way. But you may pass url_source parameter into template constructor or get_template/select_template function. This parameter MUST be a reference to a function, that will return to templating engine url template by some 'model path' (first parameter of url tag). Second parameter passed to the url_source handler will be a reference to array of argument values (in case of positional arguments) or reference to a hash of arguments (in case of named ones). Url source handler may just return a regexp template by model path and templating engine will try to restore it with specified arguments. Or, you may restore it yourself, alter replacement arguments or do whatever you want.

warn

{% warn var1 var2 ... varn %}

warn tag is useful for development and debugging. Dumps variable using Data::Dumper to STDERR.

Without and argument dumps full context object.

Filters

This module supports all built-in filters documented on official Django site. Don't forget to read incompatibilities and extensions sections.

numberformat

{{ var1|numberformat }}

Formats 12345678.9012 as

12 345 678.9012

Split integer part of the number by 3 digits, separated by spaces.

reverse

Reverses data depending on type:

  • Scalar will be reversed literally: "hi there" => "ereht ih"

  • Array will be reversed using perl's reverse function

  • Hash will be reversed using perl's reverse function

  • Object may provide reverse method to be used with this filter

split

{{ var1|split:"\s+"|slice:":2"|join:"," }}

Splitting variable with specified pattern, using Perl's split function. Current implementation uses //s regexp. Filter returns array. This filter was recently implemented and should be considered as experimental.

strftime

Formatting timestamp using Date::Format module. This is C-style date formatting, not PHP one.

CUSTOM CACHE CLASSES

To do...

CUSTOM TAGS

To do...

CUSTOM FILTERS

To do...

CUSTOM OPERATORS

To do...

INCOMPATIBILITIES WITH DJANGO TEMPLATES

  • {{ block.super }} construction is currently supported, but depricated in favor of {% block_super %} tag.

  • Django's setting ALLOWED_INCLUDE_ROOTS should be passed to tempalte constructor/getter as ssi_dirs argument.

  • csrf_token tag is not implemented, too well connected with Django.

  • _dtl_* variable names in context are reserved for internal system purposes. Don't use them.

  • output from following tags: cycle, firstof, firstofdefined are being escaped by default (like in later versions of Django)

  • escapejs filter works other way. It's not translating every non-ASCII character to the codepoint, but just escaping single and double quotes and \n \r \t \0. Utf-8 symbols are pretty valid for javascript/json.

  • fix_ampersands filter is not implemented, because it's marked as depricated and will beremoved in Django 1.8

  • pprint filter is not implemented.

  • iriencode filter works like urlencode for the moment.

  • urlize filter takes well-formatted url and makes link with this url and text generated by urldecoding and than escaping url link.

  • wherever filter in Django returns True/False values, DTL::Fast returns 1/0.

  • url tag works a different way. Because there is no framework around, we can't obtain model's path the same way. But you may pass url_source parameter into template constructor or get_template/select_template function. This parameter MUST be a reference to a function, that will return to templating engine url template by some 'model path' (first parameter of url tag). Second parameter passed to the url_source handler will be a reference to array of argument values (in case of positional arguments) or reference to a hash of arguments (in case of named ones). Url source handler may just return a regexp template by model path and templating engine will try to restore it with specified arguments. Or, you may restore it yourself, alter replacement arguments or do whatever you want.

EXTENSIONS OF DJANGO TEMPLATES

May be some of this features implemented in Django itself. Let me know about it.

  • filters may accept several arguments, and context variables can be used in them, like {{ var|filter1:var2:var3:...:varn }}

  • defined logical operator. In logical constructions you may use defined operator, which works exactly like perl's defined

  • alternatively, in logical expresisons you may compare (==,!=) value to undef or None which are synonims

  • slice filter works with ARRAYs and HASHes. Arrays slicing supports Python's indexing rules and Perl's indexing rules (but Perl's one has no possibility to index from the end of the list). Hash slicing options should be a comma-separated keys.

  • You may use brackets in logical expressions to override natural precedence

  • forloop context hash inside a for block tag contains additional fields: odd, odd0, even and even0

  • variables rendering: if any code reference encountered due variable traversing, is being invoked with context argument. Like:

    {{ var1.key1.0.func.var2 }} 

    is being rendered like:

    $context->{'var1'}->{'key1'}->[0]->func($context)->{'var2'}
  • you may use filters with static variables. Like:

    {{ "text > test"|safe }}
  • objects behaviour methods. You may extend your objects, stored in context to make them work properly with some tags and operations:

    • as_bool - returns logical representation of object

    • and(operand) - makes logical `and` between object and operand

    • or(operand) - makes logical `or` between object and operand

    • div(operand) - divides object by operand

    • equal(operand) - checks if object is equal with operand

    • compare(operand) - compares object with operand, returns -1, 0, 1 on less than, equal or greater than respectively

    • in(operand) - checks if object is in operand

    • contains(operand) - checks if object contains operand

    • minus(operand) - substitutes operand from object

    • plus(operand) - adds operand to object

    • mod(operand) - returns reminder from object division to operand

    • mul(operand) - multiplicates object by operand

    • pow(operand) - returns object powered by operand

    • not() - returns object inversion

    • reverse() - returns reversed object

    • as_array() - returns array representation of object

    • as_hash() - returns hash representation of object

BENCHMARKS

I've compared module speed with previous abandoned implementation: Dotiac::DTL in both modes: FCGI and CGI. Test template and scripts are in /timethese directory. Django templating in Python with cache works about 80% slower than DTL::Fast.

FCGI/mod_perl

Template parsing permormance with software cache wiping on each iteration:

Benchmark: timing 5000 iterations of DTL::Fast  , Dotiac::DTL...

DTL::Fast  :  5 wallclock secs ( 4.52 usr +  0.16 sys =  4.68 CPU) @ 1068.45/s (n=5000)
Dotiac::DTL: 41 wallclock secs (38.87 usr +  2.14 sys = 41.01 CPU) @ 121.93/s (n=5000)

DTL::Fast parsing templates 8.7 times faster, than Dotiac::DTL.

To run this test, you need to alter Dotiac::DTL module and change declaration of my %cache; to our %cache;.

Rendering of pre-compiled template (software cache):

Benchmark: timing 3000 iterations of DTL::Fast  , Dotiac::DTL...

DTL::Fast  : 45 wallclock secs (44.48 usr +  0.36 sys = 44.84 CPU) @ 66.90/s (n=3000)
Dotiac::DTL: 53 wallclock secs (53.02 usr +  0.51 sys = 53.52 CPU) @ 56.05/s (n=3000)

Tests shows, that DTL::Fast works a 20% faster, than Dotiac::DTL in persistent environment.

CGI

This test rendered test template many times by external script, invoked via system call:

Benchmark: timing 300 iterations of Dotiac render     , Fast cached render, Fast render

DTL::Fast  : 41 wallclock secs ( 0.01 usr  0.12 sys + 37.09 cusr  4.03 csys = 41.25 CPU) @  7.27/s (n=300)
Dotiac::DTL: 49 wallclock secs ( 0.04 usr  0.09 sys + 44.30 cusr  4.45 csys = 48.88 CPU) @  6.14/s (n=300)

Tests shows, that DTL::Fast works 18% faster, than Dotiac::DTL in CGI environment.

DTL::Fast steps performance

1 Cache key  :  0 wallclock secs ( 0.19 usr +  0.00 sys =  0.19 CPU) @ 534759.36/s (n=100000)
2 Decompress :  0 wallclock secs ( 0.27 usr +  0.00 sys =  0.27 CPU) @ 377358.49/s (n=100000)
3 Serialize  :  4 wallclock secs ( 3.73 usr +  0.00 sys =  3.73 CPU) @ 26824.03/s (n=100000)
4 Deserialize:  5 wallclock secs ( 4.26 usr +  0.00 sys =  4.26 CPU) @ 23479.69/s (n=100000)
5 Compress   : 10 wallclock secs (10.50 usr +  0.00 sys = 10.50 CPU) @ 9524.72/s (n=100000)
6 Validate   : 11 wallclock secs ( 3.12 usr +  8.05 sys = 11.17 CPU) @ 8952.55/s (n=100000)

7 Parse      :  1 wallclock secs ( 0.44 usr +  0.23 sys =  0.67 CPU) @ 1492.54/s (n=1000)
8 Render     : 11 wallclock secs ( 9.30 usr +  1.14 sys = 10.44 CPU) @ 95.82/s (n=1000)    

SEE ALSO

LICENSE

This module is published under the terms of the MIT license, which basically means "Do with it whatever you want". For more information, see the LICENSE file that should be enclosed with this distributions. A copy of the license is (at the time of writing) also available at http://www.opensource.org/licenses/mit-license.php.

AUTHOR

Copyright (C) 2014-2015 by Alexandr Evstigneev (hurricup@evstigneev.com)