For a little background...
I had a module that was a simple TT style variable
replacer. All it could handle were things like [% a %] or
[% a.0 %]
or [% a.a.0 %]. It didn't allow for argument passing, or
directives,
or operators. All it did was variables. The perldoc made
clear the
limitations and said to use Template::Toolkit if anything
more
complicated was desired.
Years went on. I decided it would be useful to have basic
IF
directive support as well as allowing for argument passed to
functions
(such as [% a(b) %]). I added these features, played around
with
things, toyed with tag parsing methods. At first there was
no optree
- the finished document was built as the parse progressed.
But then I
discovered that blocks and meta tags can be out of order in
the
document. It became easier to build an optree. It was
relatively
easy. It was only 1800 lines and took about 80 hours to
write.
I then tried it against the TT 2.14 test suite. It took
another 20
hours to make it pass the rest of the TT test suite. I was
done
mostly around late January 2006 - but I've been waiting for
the
anticipated 2.15 release so I could make sure that the new
module is
fully compliant with any of the bug fixes that have gone
into TT. I'm
still waiting - but I'm getting to the point I'd like to
release the
code.
So the first big question: Is there room for two engines
that implement
the Template Toolkit specification?
Obviously there are pros and cons to releasing another
module that does
the same thing that Template::Toolkit does.
Next question. If I do release it - should I go to lengths
to support all
of the extended options that TT uses? For example, there is
no support
for V1DOLLAR and there won't be. I've also removed
support for the
ANYCASE option (Increased the overall string parsing from
339% listed in the
example below to 359%. That isn't a major increase. And
removing ANYCASE has
no effect on compiled templates at all.)
Some other options to consider removing in CET:
- Direct access to blocks located in other templates.
- "References"
- PERL and RAWPERL blocks (CET doesn't use perl for
execution)
I'd still leave the eval_perl option though.
The big things missing that won't be implemented are TT2
views, and I
may or may not include the new magic TT3 provider namespaces
such as
"hash:" or "file:" (etc).
Whether I release a separate module or not, the TT dev team
(mostly
Andy) will be welcome to cut out what ever portions of code
they may
find useful. The CET code base is somewhat different from
TT in that
TT uses a separate object for each phase of execution while
CET uses a
separate method. This makes CET either easier or harder to
override
functionality depending upon which programming camp you come
from.
Finally - Is releasing this premature? Will TT3's speed
increase? Is
increasing the speed a factor for anybody else? Does
anybody even
care if there is another module?
I apologize that this letter doesn't include the source
code. I'd
rather wait until I can see the general feelings of people
on the
matter. I'm sure there will be split feelings either way.
If you have something scathing to say in response, you are
certainly
welcome to. But please decide if it is appropriate for the
entire
list to hear. Otherwise just email me directly.
Paul Seamons
///---------------------------------------------------------
-------///
Three files are attached.
7_template_00_base.t - The file used to test most of the
language constructs
of CGI::Ex::Template. If you uncomment the line that says
$module =
'Template' it will use Template instead. It is useful for
finding edge
cases.
bench_template.pl - Used to test relative performance. All
benchmarks should
be taken with a grain of salt. The output of the run on my
machine is listed
below. The output varies depending upon memory, disk and
processor speed.
perldoc - This is perldoc CGI::Ex::Template > perldoc -
It is the unfinished
perldoc for CGI::Ex::Template. Did I mention that it is
unfinished.
///---------------------------------------------------------
-------///
The following results are the output of the
bench_template.pl script
included in the email. The computer used is a 1.86 Pentium
M with 1
Gig of ram running Breezy Kubuntu. All percentages are
CGI::Ex::Template vs TT2.14. The first column shows the
"template"
that is being processed. The second column is both engines
using
compiled templates that are already in memory. For the
third column
the engines use a new object each time. In the fourth
column the
engine is using file side caches (CACHE_EXT is set) (it
needs to load
a new object each time). The fifth column is passing the
template as
a scalar ref rather than loading from file. The last column
is the
number of iterations per second of CET running in memory
(iterations
used for determining the first column).
Copying and pasting this into a fixed width display will
make things line up a
little better.
### All
percents are
CGI::Ex::Template vs TT2
### (The
percent that CET
is faster than TT)
Existing object by
string ref #
New object
with CACHE_EXT set
# #
New object
each time #
# #
This percent is compiled in memory (repeated calls)
# #
# #
"",
# 236% # 645% # 337%
# 457% # 19787.6/s #
"[% one %]",
# 168% # 592% # 421%
# 476% # 14355.1/s #
"[% one %]"x100,
# 31% # 341% # 79%
# 348% # 940.2/s #
"[% SET one = 2 %]",
# 161% # 534% # 419%
# 406% # 14121.9/s #
"[% SET one = 2 %]"x100,
# 12% # 279% # 37%
# 276% # 864.5/s #
"[% SET one = [0..30] %]",
# 42% # 320% # 247%
# 211% # 7518.1/s #
"[% hash.a %]",
# 173% # 619% # 423%
# 494% # 13334.4/s #
"".((" "x100)."[% one
%]\n")x10, # 82% # 517% #
261%
# 476% # 5848.1/s #
"".((" "x10)."[% one
%]\n")x100, # 23% # 432% #
116%
# 422% # 867.7/s #
"".("[% \"".("
"x100)."\$one\" %]\n")x10,
# -15% # 1415% # 102%
# 1467% # 2700.5/s #
"".("[% \"".("
"x10)."\$one\" %]\n")x100,
# -50% # 299% # 2%
# 302% # 329.3/s #
"[% 2 %]",
# 177% # 602% # 459%
# 459% # 15838.0/s #
"[% 1 + 2 %]",
# 87% # 435% # 334%
# 299% # 10746.9/s #
"[% 1 + 2 + 3 + 5 + 6 + 8 %]",
# 67% # 320% # 301%
# 218% # 9570.1/s #
"[% c.d.0.hee.0 %]",
# 171% # 616% # 416%
# 527% # 13717.7/s #
"[% SET c.d.0.hee.0 = 2 %]",
# 156% # 519% # 364%
# 413% # 10628.0/s #
"[% c.d.0.hee.0 %]"x100,
# 54% # 464% # 82%
# 467% # 752.8/s #
"[% SET c.d.0.hee.0 = 2 %]"x100,
# 107% # 341% # 91%
# 341% # 342.0/s #
"[% t = 1 || 0 ? 0 : 1 || 2 ? 2 : 3 %][% t %]",
# 73% # 291% # 256%
# 210% # 8674.9/s #
"[% a=1 %][% IF a %]Two[% END %]",
# 126% # 481% # 347%
# 378% # 11166.3/s #
" [% IF a %]Two[% END %]",
# 161% # 596% # 412%
# 492% # 13846.3/s #
"[% IF a %]A[% ELSE %]B[% END %]",
# 141% # 526% # 378%
# 422% # 12728.4/s #
"[% IF a %]A[% ELSIF b %]B[% ELSE %]C[% END %]",
# 131% # 519% # 359%
# 407% # 11386.8/s #
"[% FOREACH i = [0..10] ; i ; END %]",
# 16% # 230% # 154%
# 155% # 2376.1/s #
"[% FOREACH i = [0..100] ; i ; END %]",
# -15% # 38% # 13%
# 18% # 362.5/s #
"[% FOREACH [0..10] ; i ; END %]",
# 23% # 245% # 162%
# 169% # 2463.0/s #
"[% FOREACH [0..100] ; i ; END %]",
# -4% # 56% # 28%
# 32% # 384.9/s #
"[% f = 10 %][%WHILE f%][%f=f- 1%][%f%][% END
%]", # -14% # 152% # 50%
# 102% # 1279.5/s #
"[% f = 10; WHILE (g=f) ; f = f - 1 ; f ; END
%]", # -18% # 115% # 34%
# 75% # 1014.6/s #
"[% f = 1; WHILE (g=f) ; f = f - 1 ; f ; END
%]", # 38% # 302% # 193%
# 223% # 5061.4/s #
"[% PROCESS bar.tt %]",
# 236% # 592% # 397%
# 506% # 10189.6/s #
"[% INCLUDE baz.tt %]",
# 157% # 427% # 296%
# 370% # 6549.1/s #
"[% BLOCK foo %]Hi[% END %][% PROCESS foo %]",
# 162% # 592% # 422%
# 487% # 10030.6/s #
"[% BLOCK foo %]Hi[% END %][% INCLUDE foo %]",
# 140% # 557% # 387%
# 462% # 8555.5/s #
"[% MACRO foo BLOCK %]Hi[% END %][% foo %]",
# 79% # 413% # 301%
# 309% # 7363.9/s #
"[% MACRO foo(n) BLOCK
%]Hi[%n%][%END%][%foo(2)%]", # 65% # 296% # 265%
# 219% # 6081.4/s #
"[% MACRO foo PROCESS bar;BLOCK
bar%]7[%END;foo%]", # 96% # 449% # 316%
# 355% # 5962.3/s #
"[% n = 1 %][% n | repeat(2) %]",
# 128% # 432% # 370%
# 336% # 9705.7/s #
"[% n = 1 %][% n FILTER repeat(2) %]",
# 91% # 367% # 325%
# 256% # 8025.8/s #
"[% n=1; n FILTER echo=repeat(2); n FILTER
echo%]", # 36% # 317% # 241%
# 243% # 5321.4/s #
"[% constants.simple %]",
# 176% # 617% # 473%
# 448% # 15760.8/s #
"[%one='ONE'%][% PERL %]print
\"[%one%]\"[%END%]", # 66% # 447%
# 300%
# 357% # 6472.9/s #
"[% 'hi' | \$filt %]",
# 97% # 517% # 348%
# 400% # 9500.9/s #
"[% ' ' | uri %]",
# 127% # 620% # 402%
# 505% # 11498.0/s #
"[% foo | eval %]",
# 326% # 600% # 498%
# 517% # 5022.9/s #
"[% b = \\code(1); b(2) %]",
# 60% # 270% # 239%
# 174% # 6451.9/s #
"[% foo = BLOCK %]Hi[% END %][% foo %]",
# 105% # 438% # 312%
# 326% # 9962.1/s #
"$longer_template",
# 51% # 305% # 139%
# 264% # 1098.6/s #
# overall # 94%
# 439% # 268%
# 359% #
# overall (with Stash::XS) # 27%
# 371% # 212%
# 307% #
# Note that TT is faster on for loops and while loops. The
opcode tree method
of CET can't outperform the low level loops in perl.
### NOTE - this perldoc is not yet finished. I include it
with this
### email to provide more insight into what CET will do.
NAME
CGI::Ex::Template - Lightweight TT2/3 engine
SYNOPSIS
my $t = CGI::Ex::Template->new(
INCLUDE_PATH => [’/path/to/templates’],
);
my $swap = {
key1 => ’val1’,
key2 => ’val2’,
code => sub ,
hash => {a => ’b’},
};
$t->process(’my/template.tt’, $swap)
││ die $t->error;
DESCRIPTION
CGI::Ex::Template happened by accident. The CGI::Ex
suite included a
base set of modules for doing anything from simple to
complicated CGI
applications. Part of the suite was a simple
variable interpolater
that used TT2 style variables in TT2 style tags
"[% foo.bar %]". This
was fine and dandy for a couple of years. In winter
of 2005-2006 CET
was revamped and provided for most of the features of
TT2 as well as
some from TT3.
CGI::Ex::Template (CET hereafter) is smaller, faster,
uses less memory
and less CPU than TT2. However, it is most likely
less portable, less
extendable, and probably has many of the bugs that
TT2 has already mas‐
saged out from years of bug reports and patches from
a very active com‐
munity and mailing list. CET does not have a vibrant
community behind
it. Fixes applied to TT2 will take longer to get
into CET, should they
get in at all.
PUBLIC METHODS
new
my $obj = CGI::Ex::Template->new({
INCLUDE_PATH =>
[’/my/path/to/content’, ’/my/path/to/content2’],
});
Arguments may be passed as a hash or as a
hashref. Returns a CGI::Ex::Template object.
There are currently no errors during
CGI::Ex::Template object creation.
process
process_simple
error
Should something go wrong during a
"process" command, the error
that occured can be retrieved via the error
method.
$obj->process(’somefile.html’, {a
=> ’b’}, \$string_ref)
││ die $obj->error;
define_vmethod
This method is available for defining extra
Virtual methods or fil‐
ters. This method is similar to
Template::Stash::define_vmethod.
SEMI PUBLIC METHODS
The following list of methods are other interesting
methods of CET that
may be re-implemented by subclasses of CET.
exception - Creates an exception object blessed into
the package listed
in $CGI::Ex::Template::PACKAGE_EXCEPTION.
execute_tree - Executes a parsed tree (returned from
parse_tree)
get_variable - Turns a variable identity array into
the parsed vari‐
able. This method is also repsonsible for playing
opererators and run‐
ning virtual methods and filters. The method could
more accurately be
called play_expression.
get_variable_reference - similar to get_variable but
returns a variable
identity that can be used repeatedly to lookup the
stored variable
name. This is different from TT2 currently in that
it resolves argu‐
ments but makes no attempt to auto-vivify the
structure. The reference
is more literally a name lookup while TT returns an
actual perl refer‐
ence to the appropriate data structure.
include_filename - Takes a file path, and resolves it
into the full
filename using paths from INCLUDE_PATH.
_insert - Resolves the file passed, and then returns
its contents.
list_filters - Dynamically loads the filters list
from Template::Fil‐
ters when a filter is used that is not natively
implemented in CET.
list_plugins - Returns an arrayref of modules that
are under a base
Namespace.
my modules = { $self->list_plugins({base =>
’Template::Plugins’}) }:
load_parsed_tree - Given a filename or a string
reference will return a
parsed document hash that contains the parsed tree.
my $doc = $self->load_parsed_tree($file) ││
$self->throw(’undef’, "Zero length
content");
parse_args - Allow for the multitudinous ways that TT
parses arguments.
This allows for positional as well as named
arguments. Named arguments
can be separated with a "=" or
"=>", and positional arguments should be
separated by " " or ",".
This only returns an array of parsed vari‐
ables. Use vivify_args to translate to the actual
values.
parse_tree - Used by load_parsed_tree. This is the
main grammar engine
of the program. It uses method in the $DIRECTIVES
hashref to parse
different DIRECTIVE TYPES.
parse_variable - Used to parse a variable, an
expression, a literal
string, or a number. It returns a parsed variable
tree. Samples of
parsed variables can be found in the VARIABLE PARSE
TREE section.
set_variable - Used to set a variable. Expects a
variable identity
array and the value to set. It will autovifiy as
necessary.
throw - Creates an exception object from the
arguments and dies.
undefined_any - Called during get_variable if a value
is returned that
is undefined. This could be used to magically create
variables on the
fly. This is similar to Template::Stash::undefined.
It is suggested
that undefined_get be used instead. Default behavior
returns undef.
You may also pass a coderef via the UNDEFINED_ANY
configuration vari‐
able. Also, you can try using the DEBUG =>
’undef’, configuration
option which will throw an error on undefined
variables.
undefined_get - Called when a variable is undefined
during a GET direc‐
tive. This is useful to see if a value that is about
to get inserted
into the text is undefined. undefined_any is a
little too general for
most cases. Also, you may pass a coderef via the
UNDEFINED_GET
configuration variable.
vivify_args - Turns an arrayref of arg identities
parsed by parse_args
and turns them into the actual values.
OTHER UTILITY METHODS
The following is a brief list of other methods used
by CET. Generally,
these shouldn’t be overwritten by subclasses.
apply_precedence - allows for parsed operator array
to be translated to
a tree based upon operator precedence.
context - used to create a "pseudo"
context object that allows for
portability of TT plugins, filters, and perl blocks
that need a context
object.
DEBUG - TT2 Holdover that is used once for binmode
setting during a TT2
test.
debug_node - used to get debug info on a directive if
DEBUG_DIRS is
set.
filter_* - implement filters that are more than one
line.
get_line_number_by_index - used to turn string index
position into line
number
interpolate_node - used for parsing text nodes for
dollar variables
when interpolate is on.
parse_* - used by parse_tree to parse the template.
These are the
grammar.
play_* - used by execute_tree to execute the parsed
tree.
play_operator - to execute any found operators
_process - called by process and the PROCESS, INCLUDE
and other direc‐
tives.
slurp - reads contents of passed filename - throws
file exception on
error.
split_paths - used to split INCLUDE_PATH or other
directives if an
arrayref is not passed.
_vars - Return a reference to the current stash of
variables. This is
currently only used by the pseudo context object and
may disappear at
some point.
vmethod_* - implement virtual methods that are more
than one line.
weak_copy - used to create a weak reference to self
to avoid circular
references. (this is needed by macros and references.
TODO
Add WRAPPER config item
Add ERROR config item
Document which TT2 test suites pass
HOW IS CGI::Ex::Template DIFFERENT
CET uses the same template syntax and configuration
items as TT2, but
the internals of CET were written from scratch. In
addition to this,
the following is a list of some of the ways that
configuration and syn‐
tax of CET different from that of TT.
Numerical hash keys work [% a = {1 => 2} %]
Quoted hash key interpolation is fine [% a =
{"$foo" => 1} %]
Range operator returns an arrayref [% a = 1 .. 10
%]
Multiple ranges in same constructor [% a =
[1..10, 21..30] %]
Construtor types can call virtual methods
[% a = [1..10].reverse %]
[% "$foo".length %]
[% 123.length %] # = 3
[% 123.4.length %] # = 5
[% -123.4.length %] # = -5 ("."
binds more tightly than "-")
[% (a ~ b).length %]
[% "hi".repeat(3) %]
[% {a => b}.size %]
Reserved names are less reserved
[% GET GET %] # gets the variable named
"GET"
[% GET $GET %] # gets the variable who’s
name is stored in "GET"
Filters and SCALAR_OPS are interchangeable.
[% a │ length %]
[% b . lower %]
Pipe "│" can be used anywhere dot
"." can be and means to call the
virtual method.
[% a = {size => "foo"} %][%
a.size %] # = foo
[% a = {size => "foo"} %][%
a│size %] # = 1 (size of hash)
Pipe "│" and "." can be
mixed.
[% "aa" │ repeat(2) . length %]
# = 4
Whitespace is less meaningful.
[% 2-1 %] # = 1 (fails in TT)
Added pow operator.
[% 2 ** 3 %] [% 2 pow 3 %] # = 8 8
FOREACH variables can be nested
[% FOREACH f.b = [1..10] ; f.b ; END %]
Note that nested variables are subject to
scoping issues.
f.b will not be reset to its value before the
FOREACH.
Post operative directives can be nested.
[% one IF two IF three %]
same as
[% IF three %][% IF two %][% one %][% END %][%
END %]
[% a = [[1..3], [5..7]] %][% i FOREACH i = j
FOREACH j = a %] # = 123567
CATCH blocks can be empty.
CHOMP at the end of a string.
CET will replace "[% 1 =%]\n" with
"1 "
TT will replace "[% 1 =%]\n" with
"1"
This is an internal one-off exception in TT
that may or may not DWIM.
In CET - it always means to always replace
whitespace on the line with
a space. The template can be modified to
either not have space - or
to end with ~%] which will remove any following
space.
CET does not generate Perl code. It generates an
"opcode" tree.
CET uses storable for its compiled templates. If
EVAL_PERL is off,
CET will not eval_string on ANY piece of
information.
There is no context. CET provides a context
object that mimics the
Template::Context interface for use by some TT
filters, eval perl
blocks, and plugins.
There is no stash. CET only supports the
variables passed in VARI‐
ABLES, PRE_DEFINE, and those passed to the
process method. CET
provides a stash object that mimics the
Template::Stash interface
for use by some TT filters, eval perl blocks, and
plugins.
There is no provider. CET uses the
load_parsed_tree method to get
and cache templates.
There is no grammar. CET has its own built in
grammar system.
There is no service.
References are less interpolated. TT partially
resolves some of the
names filter keys and other elements rather than
wait until the
reference is actually used. CET only resolves
interpolated values
and arguments to subroutines. All other
resolution is delayed
until the reference is actually used.
The DEBUG directive only understands DEBUG_DIRS
(8) and DEBUG_UNDEF
(2).
When debug dirs is on, directives on different
lines separated by
colons show the line they are on rather than a
general line range.
DIRECTIVES
This section containts the alphabetical list of
DIRECTIVES available in
the TT language. DIRECTIVES are the
"functions" that implement the
Template Toolkit mini-language. For further
discussion and examples,
please refer to the TT directives documentation.
BLOCK
Saves a block of text under a name for later use
in PROCESS,
INCLUDE, and WRAPPER directives. Blocks may be
placed anywhere
within the template being processed including
after where they are
used.
[% BLOCK foo %]Some text[% END %]
[% PROCESS foo %]
Would print
Some text
[% INCLUDE foo %]
[% BLOCK foo %]Some text[% END %]
Would print
Some text
Anonymous BLOCKS can be used for capturing.
[% a = BLOCK %]Some text[% END %][% a %]
Would print
Some text
Anonymous BLOCKS can be used with macros.
BREAK
Alias for LAST. Used for exiting FOREACH and
WHILE loops.
CALL
Calls the variable (and any underlying coderefs)
as in the GET
method, but always returns an empty string.
CASE
Used with the SWITCH directive. See the
"SWITCH" directive.
CATCH
Used with the TRY directive. See the
"TRY" directive.
CLEAR
Clears any of the content currently generated in
the innermost
block or template. This can be useful when used
in conjuction with
the TRY statement to clear generated content if
an error occurs
later.
DEBUG
Used to reset the DEBUG_FORMAT configuration
variable, or to turn
DEBUG statements on or off. This only has effect
if the DEBUG_DIRS
or DEBUG_ALL flags were passed to the DEBUG
configuration variable.
[% DEBUG format ’($file) (line $line)
($text)’ %]
[% DEBUG on %]
[% DEBUG off %]
DEFAULT
Similar to SET, but only sets the value if a
previous value was not
defined or was zero length.
[% DEFAULT foo = ’bar’ %][% foo %] =>
’bar’
[% foo = ’baz’ %][% DEFAULT foo =
’bar’ %][% foo %] => ’baz’
ELSE
Used with the IF directive. See the
"IF" directive.
ELSIF
Used with the IF directive. See the
"IF" directive.
END Used to end a block directive.
FILTER
Used to apply different treatments to blocks of
text. It may oper‐
ate as a BLOCK directive or as a post operative
directive. CET
supports all of the filters in Template::Filters.
The lines
between scalar virtual methods and filters is
blurred (or non-exis‐
tent) in CET. Anything that is a scalar virtual
method may be used
as a FILTER.
TODO - enumerate the at least 7 ways to pass and
use filters.
’│’ Alias for the FILTER directive. Note that
│ is similar to the ’.’
in CGI::Ex::Template. Therefore a pipe cannot be
used directly
after a variable name in some situations (the
pipe will act only on
that variable). This is the behavior employed by
TT3.
FINAL
Used with the TRY directive. See the
"TRY" directive.
FOR Alias for FOREACH
FOREACH
GET Return the value of a variable.
[% GET a %]
The GET keyword may be omitted.
[% a %]
IF (IF / ELSIF / ELSE)
Allows for conditional testing. Expects an
expression as its only
argument. If the expression is true, the
contents of its block are
processed. If false, the processor looks for an
ELSIF block. If
an ELSIF’s expression is true then it is
processed. Finally it
looks for an ELSE block which is processed if
none of the IF or
ELSIF’s expressions were true.
[% IF a == b %]A equaled B[% END %]
[% IF a == b -%]
A equaled B
[%- ELSIF a == c -%]
A equaled C
[%- ELSE -%]
Couldn’t determine that A equaled
anything.
[%- END %]
If may also be used as a post operative
directive.
[% ’A equaled B’ IF a == b %]
INCLUDE
INSERT
LAST
Used to exit out of a WHILE or FOREACH loop.
MACRO
META
NEXT
Used to go to the next iteration of a WHILE or
FOREACH loop.
PERL
PROCESS
RAWPERL
RETURN
Used to exit the innermost block or template and
continue process‐
ing in the surrounding block or template.
SET Used to set variables.
[% SET a = 1 %][% a %] =>
"1"
[% a = 1 %][% a %] =>
"1"
[% b = 1 %][% SET a = b %][% a %] =>
"1"
[% a = 1 %][% SET a %][% a %] =>
""
[% SET a = [1, 2, 3] %][% a.1 %] =>
"2"
[% SET a = {b => ’c’} %][% a.b %]
=> "c"
STOP
Used to exit the entire process method (out of
all blocks and tem‐
plates). No content will be processed beyond
this point.
SWITCH
TAGS
Change the type of enclosing braces used to
delineate template
tags. This remains in effect until the end of
the enclosing block
or template or until the next TAGS directive.
Either a named set
of tags must be supplied, or two tags themselves
must be supplied.
[% TAGS html %]
[% TAGS <!-- --> %]
The named tags are (duplicated from TT):
template => [’[%’, ’%]’], #
default
metatext => [’%%’, ’%%’], #
Text::MetaText
star => [’[*’, ’*]’], # TT
alternate
php => [’<?’, ’?>’],
# PHP
asp => [’<%’, ’%>’],
# ASP
mason => [’<%’, ’>’ ],
# HTML::Mason
html => [’<!--’, ’-->’],
# HTML comments
THROW
TRY
UNLESS
USE
WHILE
WRAPPER
Block directive. Processes contents of its block
and then passes
them in the [% content %] variable to the block
or filename listed
in the WRAPPER tag.
[% WRAPPER foo %]
My content to be processed.[% a = 2 %]
[% END %]
[% BLOCK foo %]
A header ([% a %]).
[% content %]
A footer ([% a %]).
[% END %]
This would print.
A header (2).
My content to be processed.
A footer (2).
The WRAPPER directive may also be used as a post
directive.
[% BLOCK baz %]([% content %])[% END -%]
[% "foobar" WRAPPER baz %]
Would print
(foobar)’);
OPERATORS
The following operators are available in
CGI::Ex::Template. Except
where noted these are the same operators available in
TT. They are
listed in the order of their precedence (the higher
the precedence the
tighter it binds).
"." Binary. The dot operator. Allows
for accessing sub-members, meth‐
ods, or virtual methods of nested data
structures.
my $obj->process(\$content, {a => {b
=> [0, {c => [34, 57]}]}}, \$output);
[% a.b.1.c.0 %] => 34
Note: on access to hashrefs, any hash keys that
match the sub key
name will be used before a virtual method of the
same name. For
example if a passed hash contained pair with a
keyname "defined"
and a value of "2", then any calls to
hash.defined(subkeyname)
would always return 2 rather than using the
vmethod named
"defined." To get around this
limitation use the "│" operator
(listed next).
"│" Binary. The pipe operator.
Similar to the dot operator. Allows
for explicit calling of virtual methods and
filters (filters are
"merged" with virtual methods in
CGI::Ex::Template and TT3) when
accessing hashrefs. See the note for the
"." operator.
The pipe character is similar to TT2 in that it
can be used in
place of a directive as an alias for FILTER. It
similar to TT3 in
that it can be used for virtual method access.
This duality is one
source of difference between CGI::Ex::Template
and TT2 compatibil‐
ity. Templates that have directives that end
with a variable name
that then use the "│" directive to
apply a filter will be broken as
the "│" will be applied to the
variable name.
The following two cases will do the same thing.
[% foo │ html %]
[% foo FILTER html %]
Though they do the same thing, internally,
foo│html is stored as a
single variable while "foo FILTER
html" is stored as the variable
foo which is then passed to the the FILTER html.
A TT2 sample that would break in
CGI::Ex::Template or TT3 is:
[% PROCESS foo a = b │ html %]
Under TT2 the content returned by "PROCESS
foo a = b" would all be
passed to the html filter. Under
CGI::Ex::Template and TT3, b
would be passed to the html filter before
assigning it to the vari‐
able "a" before the template foo was
processed.
A simple fix is to do any of the following:
[% PROCESS foo a = b FILTER html %]
[% │ html %][% PROCESS foo a = b %][% END
%]
[% FILTER html %][% PROCESS foo a = b %][%
END %]
This shouldn’t be too much hardship and offers
the great return of
disambiguating virtual method access.
"\" Unary. The reference operator. Not
well publicized in TT. Stores
a reference to a variable for use later. Can
also be used to
"alias" long names. Note that a
minimum of name resolution occurs
at reference creation time (including resolving
any arguments to
functions or variable name interpolation).
[% f = 7 ; foo = \f ; f = 8 ; foo %] => 8
[% foo = \f.g.h.i.j.k; f.g.h.i.j.k = 7; foo
%] => 7
[% f = "abcd"; foo =
\f.replace("ab", "-AB-") ; foo %]
=> -AB-cd
[% f = "abcd"; foo =
\f.replace("bc") ; foo("-BC-") %]
=> a-BC-d
[% f = "abcd"; foo = \f.replace
; foo("cd", "-CD-") %] => ab-CD-
"** ^ pow"
Binary. X raised to the Y power. This isn’t
available in TT 2.14.
[% 2 ** 3 %] => 8
"!" Unary not. Negation of the value.
"- unary_minus"
Unary minus. Returns the value multiplied by -1.
The operator
"unary_minus" is used internally by
CGI::Ex::Template to provide
for - to be listed in the precedence table twice.
[% a = 1 ; b = -a ; b %] => -1
"*" Binary. Multiplication.
"/ div DIV"
Binary. Division. Note that / is floating point
division, but div
and DIV are integer division.
[% 10 / 4 %] => 2.5
[% 10 div 4 %] => 2
"% mod MOD"
Binary. Modulus.
[% 15 % 8 %] => 7
"+" Binary. Addition.
"-" Binary. Minus.
"_ ~"
Binary. String concatenation.
[% "a" ~ "b" %] =>
ab
"< > <= >="
Binary. Numerical comparators.
"lt gt le ge"
Binary. String comparators.
"== eq"
Binary. Equality test. TT chose to use Perl’s
eq for both opera‐
tors. There is no test for numeric equality.
"!= ne"
Binary. Non-equality test. TT chose to use
Perl’s ne for both
operators. There is no test for numeric
non-equality.
"&&"
Multiple arity. And. All values must be true.
If all values are
true, the last value is returned as the truth
value.
[% 2 && 3 && 4 %] => 4
"││"
Multiple arity. Or. The first true value is
returned.
[% 0 ││ ’’ ││ 7 %] => 7
".."
Binary. Range creator. Returns an arrayref
containing the values
between and including the first and last
arguments.
[% t = [1 .. 5] %] => variable t contains
an array with 1,2,3,4, and 5
In CGI::Ex::Template, because .. is an operator
that returns an
array, you may also specify the previous example
as the following
(this does not work in TT):
[% t = 1 .. 5 %] => variable t contains an
array with 1,2,3,4, and 5
CGI::Ex::Template provides special functionality
to allow the
arrayref returned by .. to be expanded fully into
a [] construtor
as in "[1 .. 5]". Because of this it
is possible to place multiple
ranges in the same [] constructor. This is not
available in TT.
[% t = [1..3, 6..8] %] => variable t
contains an array with 1,2,3,6,7,8
"? :"
Trinary. Can be nested with other ?: pairs.
[% 1 ? 2 : 3 %] => 2
[% 0 ? 2 : 3 %] => 3
"=" Assignment. Sets the lefthand side
to the value of the righthand
side. In order to not conflict with SET, FOREACH
and other opera‐
tions, this operator is only available in
parenthesis. Returns the
value of the righthand side.
[% (a = 1) %] --- [% a %] => 1 --- 1
"not NOT"
Lower precedence version of the ’!’ operator.
"and AND"
Lower precedence version of the ’&&’
operator.
"or OR"
Lower precedence version of the ’││’
operator.
"hashref"
Multiple arity. This operator is not used in TT.
It is used
internally by CGI::Ex::Template to delay the
creation of a hashref
until the execution of the compiled template.
"arrayref"
Multiple arity. This operator is not used in TT.
It is used
internally by CGI::Ex::Template to delay the
creation of an
arrayref until the execution of the compiled
template.
CONFIGURATION
The following TT2 configuration variables are
supported (in alphabeti‐
cal order). Note: for further discussion you can
refer to the TT con‐
fig documentation.
These variables should be passed to the
"new" constructor.
my $obj = CGI::Ex::Template->new(
VARIABLES => \%hash_of_variables,
AUTO_RESET => 0,
TRIM => 1,
POST_CHOMP => 2,
PRE_CHOMP => 1,
);
ABSOLUTE
Boolean. Default false. Are absolute paths
allowed for included
files.
AUTO_RESET
Boolean. Default 1. Clear blocks that were set
during the process
method.
BLOCKS
A hashref of blocks that can be used by the
process method.
BLOCKS => {
block_1 => sub { ... }, # coderef that
returns a block
block_2 => ’A String’, # simple
string
},
Note that a Template: ocument
cannot be supplied as a value (TT
supports this). However, it is possible to
supply a value that is
equal to the hashref returned by the
load_parsed_tree method.
CACHE_SIZE
Number of compiled templates to keep in memory.
Default undef.
Undefined means to allow all templates to cache.
A value of 0 will
force no caching. The cache mechanism will clear
templates that
have not been used recently.
COMPILE_DIR
Base directory to store compiled templates.
Default undef. Com‐
piled templates will only be stored if one of
COMPILE_DIR and COM‐
PILE_EXT is set.
COMPILE_EXT
Extension to add to stored compiled template
filenames. Default
undef.
CONSTANTS
Hashref. Used to define variables that will be
"folded" into the
compiled template. Variables defined here cannot
be overridden.
CONSTANTS => {my_constant => 42},
A template containing:
[% constants.my_constant %]
Will have the value 42 compiled in.
Constants defined in this way can be chained as
in [% con‐
stant.foo.bar.baz %] but may only interpolate
values that are set
before the compile process begins. This goes one
step beyond TT in
that any variable set in VARIABLES, or
PRE_DEFINE, or passed to the
process method are allowed - they are not in TT.
Variables defined
in the template are not available during the
compile process.
GOOD:
CONSTANTS => {
foo => {
bar => {baz => 42},
bim => 57,
},
bing => ’baz’,
bang => ’bim’,
},
VARIABLES => {
bam => ’bar’,
},
In the template
[% constants.foo.${constants.bang} %]
Will correctly print 57.
GOOD (non-tt behavior)
[% constants.foo.$bam.${constants.bing} %]
CGI::Ex::Template will print 42 because the
value of bam is
known at compile time. TT would print ’’
because the value of $bam
is not yet defined in the TT engine.
BAD:
In the template:
[% bam = ’somethingelse’ %]
[% constants.foo.$bam.${constants.bing} %]
Will still print 42 because the value of bam
used comes from
variables defined before the template was
compiled. TT will still print ’’.
CONSTANT_NAMESPACE
DEBUG
Takes a list of constants │’ed together
which enables different
debugging modes. Alternately the lowercase
names may be used (multiple
values joined by a ",".
The only supported TT values are:
DEBUG_UNDEF (2) - debug when an undefined
value is used.
DEBUG_DIRS (8) - debug when a directive
is used.
DEBUG_ALL (2047) - turn on all debugging.
Either of the following would turn on undef
and directive debugging:
DEBUG => ’undef, dirs’, #
preferred
DEBUG => 2 │ 8,
DEBUG => DEBUG_UNDEF │ DEBUG_DIRS, #
constants from Template::Constants
DEBUG_FORMAT
DEFAULT
DELIMITER
END_TAG
EVAL_PERL
Boolean. Default false. If set to a true value,
PERL and RAWPERL
blocks will be allowed to run. This is a
potential security hole,
as arbitrary perl can be included in the
template. If Tem‐
plate::Toolkit is installed, a true EVAL_PERL
value also allows the
perl and evalperl filters to be used.
FILTERS
INCLUDE_PATH
A string or an array containing directories too
look for files
included by processed templates.
INTERPOLATE
LOAD_PERL
NAMESPACE - no Template::Namespace::Constants support
OUTPUT
OUTPUT_PATH
PLUGINS
PLUGIN_BASE
POST_CHOMP
POST_PROCESS
PRE_CHOMP
PRE_DEFINE
PRE_PROCESS
PROCESS
RECURSION
RELATIVE
START_TAG
TAG_STYLE
TRIM
Remove leading and trailing whitespace from
blocks and templates.
This operation is performed after all enclosed
template tags have
been executed.
UNDEFINED_ANY
This is not a TT configuration option. This
option expects to be a
code ref that will be called if a variable is
undefined during a
call to get_variable. It is passed the variable
identity array as
a single argument. This is most similar to the
"undefined" method
of Template::Stash. It allows for the
"auto-defining" of a vari‐
able for use in the template. It is suggested
that UNDEFINED_GET
be used instead as UNDEFINED_ANY is a little to
general in defining
variables.
You can also sub class the module and override
the undefined_any
method.
UNDEFINED_GET
This is not a TT configuration option. This
option expects to be a
code ref that will be called if a variable is
undefined during a
call to GET. It is passed the variable identity
array as a single
argument. This is more useful than UNDEFINED_ANY
in that it is
only called during a GET directive rather than in
embedded expres‐
sions (such as [% a ││ b ││ c %]).
You can also sub class the module and override
the undefined_get
method.
VARIABLES
A hashref of variables to initialize the template
stash with.
These variables are available for use in any of
the executed tem‐
plates.
UNSUPPORTED TT CONFIGURATION
ANYCASE
This will not be supported. You will have to use
the full case
directive names. (It was in the beta code but
was removed prior to
release).
WRAPPER
This will be supported - just not done yet.
ERROR
This will be supported - just not done yet.
V1DOLLAR
This will not be supported.
LOAD_TEMPLATES
CGI::Ex::Template has its own mechanism for
loading and storing
compiled templates. TT would use a
Template::Provider that would
return a Template: ocument.
The closest thing in CGI::Ex::Tem‐
plate is the load_parsed_template method. There
is no immediate
plan to support the TT behavior.
LOAD_PLUGINS
CGI::Ex::Template uses its own mechanism for
loading plugins. TT
would use a Template::Plugins object to load
plugins requested via
the USE directive. The functionality for doing
this in
CGI::Ex::Template is contained in the
list_plugins method and the
play_USE method. There is no immediate plan to
support the TT
behavior.
Full support is offered for the PLUGINS and
LOAD_PERL configuration
items.
Also note that CGI::Ex::Template only natively
supports the Itera‐
tor plugin. Any of the other plugins requested
will need to pro‐
vided by installing Template::Toolkit or the
appropriate plugin
module.
LOAD_FILTERS
CGI::Ex::Template uses its own mechanism for
loading filters. TT
would use the Template::Filters object to load
filters requested
via the FILTER directive. The functionality for
doing this in
CGI::Ex::Template is contained in the
list_filters method and the
get_variable method.
Full support is offered for the FILTERS
configuration item.
TOLERANT
This option is used by the LOAD_TEMPLATES and
LOAD_PLUGINS options
and is not applicable in CGI::Ex::Template.
SERVICE
CGI::Ex::Template has no concept of service
(theoretically the
CGI::Ex::Template is the "service").
CONTEXT
CGI::Ex::Template provides its own pseudo context
object to plug‐
ins, filters, and perl blocks. The
CGI::Ex::Template model doesn’t
really allow for a separate context.
CGI::Ex::Template IS the con‐
text.
STASH
CGI::Ex::Template manages its own stash of
variables. A pseudo
stash object is available via the pseudo context
object for use in
plugins, filters, and perl blocks.
PARSER
CGI::Ex::Template has its own built in parser.
The closest simi‐
larity is the parse_tree method. The output of
parse_tree is an
optree that is later run by execute_tree.
GRAMMAR
CGI::Ex::Template maintains its own grammar. The
grammar is
defined in the parse_tree method and the
callbacks listed in the
global $DIRECTIVES hashref.
VARIABLE PARSE TREE
CGI::Ex::Template parses templates into an tree of
operations. Even
variable access is parsed into a tree. This is done
in a manner some‐
what similar to the way that TT operates except that
nested variables
such as foo.bar│baz contain the ’.’ or
’│’ in between each name level.
Opererators are parsed and stored as part of the
variable (it may be
more appropriate to say we are parsing a term or an
expression).
The following table shows a variable or expression
and the correspond‐
ing parsed tree (this is what the parse_variable
method would return).
one [ ’one’, 0 ]
one() [ ’one’, [] ]
one.two [ ’one’, 0, ’.’,
’two’, 0 ]
one│two [ ’one’, 0, ’│’,
’two’, 0 ]
one.$two [ ’one’, 0, ’.’,
[’two’, 0 ], 0 ]
one(two) [ ’one’, [ [’two’, 0]
] ]
one.${two().three} [ ’one’, 0, ’.’,
[’two’, [], ’.’, ’three’, 0], 0]
2.34 2.34
"one" "one"
"one"│length [
\"one", 0, ’│’, ’length’, 0 ]
"one $a two" [ \ [ ’~’,
’one ’, [’a’, 0], ’ two’ ], 0 ]
[0, 1, 2] [ \ [ ’arrayref’, 0, 1, 2
], 0 ]
[0, 1, 2].size [ \ [ ’arrayref’, 0, 1, 2
], 0, ’.’, ’size’, 0 ]
[’a’, a, $a ] [ \ [ ’arrayref’,
’a’, [’a’, 0], [[’a’, 0], 0] ], 0]
{a => ’b’} [ \ [ ’hashref’,
’a’, ’b’ ], 0 ]
{a => ’b’}.size [ \ [ ’hashref’,
’a’, ’b’ ], 0, ’.’, ’size’, 0 ]
{$a => b} [ \ [ ’hashref’,
[’a’, 0], [’b’, 0] ], 0 ]
1 + 2 + 3 + 4 [ \ [ ’+’, 1, 2, 3, 4 ],
0]
a + b [ \ [ ’+’, [’a’, 0],
[’b’, 0] ], 0 ]
a * (b + c) [ \ [ ’*’, [’a’, 0],
[ \ [’+’, [’b’, 0], [’c’, 0]], 0 ]], 0 ]
(a + b) [ \ [ ’+’, [’a’, 0],
[’b’, 0] ]], 0 ]
(a + b) * c [ \ [ ’*’, [ \ [
’+’, [’a’, 0], [’b’, 0] ], 0 ], [’c’, 0] ],
0 ]
a ? b : c [ \ [ ’?’, [’a’, 0],
[’b’, 0], [’c’, 0] ], 0 ]
a ││ b ││ c [ \ [ ’││’,
[’a’, 0], [’b’, 0], [’c’, 0] ], 0 ]
! a [ \ [ ’!’, [’a’, 0]
], 0 ]
Some notes on the parsing.
Operators are parsed as part of the variable and
become part of the variable tree.
Operators are stored in the variable tree using a
reference to the arrayref - which
allows for quickly decending the parsed variable
tree and determining that the next
node is an operator.
Parens () can be used at any point in an
expression to disambiguate precedence.
"Variables" that appear to be literal
strings or literal numbers
are returned as the literal (no operator tree).
The following perl can be typed at the command line
to view the parsed
variable tree.
perl -e ’my $a = "\"one \$a
two\"";
use CGI::Ex::Template;
use Data: umper;
print
Dumper(CGI::Ex::Template->new->parse_variable(\$a));
AUTHOR
Paul Seamons <mail at seamons dot com>
perl v5.8.7 2006-05-04
.::CGI::Ex::Template(3)
# -*- Mode: Perl; -*-
use vars qw($module $is_tt);
BEGIN {
$module = 'CGI::Ex::Template'; #real 0m1.243s #user
0m0.695s #sys 0m0.018s
#$module = 'Template'; #real 0m2.329s #user
0m1.466s #sys 0m0.021s
$is_tt = $module eq 'Template';
};
use strict;
use Test::More tests => 474 - ($is_tt ? 58 : 0);
use Data: umper
qw(Dumper);
use constant test_taint => 0 && eval { require
Taint::Runtime };
use_ok($module);
Taint::Runtime::taint_start() if test_taint;
###---------------------------------------------------------
-------###
sub process_ok { # process the value and say if it was ok
my $str = shift;
my $test = shift;
my $vars = shift;
my $obj = shift || $module->new( {
$vars-> || [] }); # new object each time
my $out = '';
Taint::Runtime::taint(\$str) if test_taint;
$obj->process(\$str, $vars, \$out);
my $ok = ref($test) ? $out =~ $test : $out eq $test;
ok($ok, "\"$str\" =>
\"$out\"" . ($ok ? '' : " -
should've been \"$test\""));
my $line = (caller)[2];
warn "# process_ok called at line
$line.\n" if ! $ok;
print $obj->error if ! $ok &&
$obj->can('error');
print Dumper $obj->parse_tree(\$str) if ! $ok
&& $obj->can('parse_tree');
exit if ! $ok;
}
###---------------------------------------------------------
-------###
### set up some dummy packages for various tests
{
package MyTestPlugin::Foo;
$INC{'MyTestPlugin/Foo.pm'} = $0;
sub load { $_[0] }
sub new {
my $class = shift;
my $context = shift; # note the plugin style object
that needs to shift off context
my $args = shift || {};
return bless $args, $class;
}
sub bar { my $self = shift; return join('', map
{"$_$self->{$_}"} sort keys %$self) }
sub seven
sub many { return 1, 2, 3 }
sub echo { my $self = shift; $_[0] }
}
{
package Foo2;
$INC{'Foo2.pm'} = $0;
use base qw(MyTestPlugin::Foo);
use vars qw($AUTOLOAD);
sub new {
my $class = shift;
my $args = shift || {}; # note - no plugin
context
return bless $args, $class;
}
sub leave {} # hacks to allow tt to do the plugins
passed via PLUGINS
sub delocalise {} # hacks to allow tt to do the plugins
passed via PLUGINS
}
my $obj = Foo2->new;
###---------------------------------------------------------
-------###
### variable GETting
process_ok("[% foo %]" => "");
process_ok("[% foo %]" => "7",
{foo => 7});
process_ok("[% foo %]" => "7",
{tt_config => [VARIABLES => {foo => 7}]});
process_ok("[% foo %]" => "7",
{tt_config => [PRE_DEFINE => {foo => 7}]});
process_ok("[% foo %][% foo %][% foo %]" =>
"777", {foo => 7});
process_ok("[% foo() %]" => "7",
{foo => 7});
process_ok("[% foo.bar %]" =>
"");
process_ok("[% foo.bar %]" => "",
{foo => {}});
process_ok("[% foo.bar %]" =>
"7", {foo => {bar => 7}});
process_ok("[% foo().bar %]" =>
"7", {foo => {bar => 7}});
process_ok("[% foo.0 %]" => "7",
{foo => [7, 2, 3]});
process_ok("[% foo.10 %]" => "",
{foo => [7, 2, 3]});
process_ok("[% foo %]" => 7, {foo
=> sub });
process_ok("[% foo(7) %]" => 7, {foo
=> sub { $_[0] }});
process_ok("[% foo.length %]" => 1, {foo
=> sub });
process_ok("[% foo.0 %]" => 7, {foo
=> sub { return 7, 2, 3 }});
process_ok("[% foo(bar) %]" => 7, {foo
=> sub { $_[0] }, bar => 7});
process_ok("[% foo.seven %]" => 7, {foo
=> $obj});
process_ok("[% foo.seven() %]" => 7, {foo
=> $obj});
process_ok("[% foo.seven.length %]" => 1,
{foo => $obj});
process_ok("[% foo.echo(7) %]" => 7, {foo
=> $obj});
process_ok("[% foo.many.0 %]" => 1, {foo
=> $obj});
process_ok("[% foo.many.10 %]" => '',{foo
=> $obj});
process_ok("[% foo.nomethod %]" => '',{foo
=> $obj});
process_ok("[% foo.nomethod.0 %]" =>
'',{foo => $obj});
process_ok("[% GET foo %]" =>
"");
process_ok("[% GET foo %]" =>
"7", {foo => 7});
process_ok("[% GET foo.bar %]" =>
"");
process_ok("[% GET foo.bar %]" =>
"", {foo => {}});
process_ok("[% GET foo.bar %]" =>
"7", {foo => {bar => 7}});
process_ok("[% GET foo.0 %]" =>
"7", {foo => [7, 2, 3]});
process_ok("[% GET foo %]" => 7, {foo
=> sub });
process_ok("[% GET foo(7) %]" => 7, {foo
=> sub { $_[0] }});
process_ok("[% \$name %]" => "",
{name => 'foo'});
process_ok("[% \$name %]" =>
"7", {name => 'foo', foo => 7});
process_ok("[% \$name.bar %]" =>
"", {name => 'foo'});
process_ok("[% \$name.bar %]" =>
"", {name => 'foo', foo => {}});
process_ok("[% \$name.bar %]" =>
"7", {name => 'foo', foo => {bar =>
7}});
process_ok("[% \$name().bar %]" =>
"7", {name => 'foo', foo => {bar =>
7}});
process_ok("[% \$name.0 %]" =>
"7", {name => 'foo', foo => [7, 2,
3]});
process_ok("[% \$name %]" => 7,
{name => 'foo', foo => sub });
process_ok("[% \$name(7) %]" => 7,
{name => 'foo', foo => sub { $_[0] }});
process_ok("[% GET \$name %]" =>
"", {name => 'foo'});
process_ok("[% GET \$name %]" =>
"7", {name => 'foo', foo => 7});
process_ok("[% GET \$name.bar %]" =>
"", {name => 'foo'});
process_ok("[% GET \$name.bar %]" =>
"", {name => 'foo', foo => {}});
process_ok("[% GET \$name.bar %]" =>
"7", {name => 'foo', foo => {bar =>
7}});
process_ok("[% GET \$name.0 %]" =>
"7", {name => 'foo', foo => [7, 2,
3]});
process_ok("[% GET \$name %]" => 7,
{name => 'foo', foo => sub });
process_ok("[% GET \$name(7) %]" => 7,
{name => 'foo', foo => sub { $_[0] }});
process_ok("[% \$name %]" => "",
{name => 'foo foo', foo => 7});
process_ok("[% GET \$name %]" =>
"", {name => 'foo foo', foo => 7});
process_ok("[% \$ %]" =>
"", {name => 'foo'});
process_ok("[% \$ %]" =>
"7", {name => 'foo', foo => 7});
process_ok("[% \$.bar %]" =>
"", {name => 'foo'});
process_ok("[% \$.bar %]" =>
"", {name => 'foo', foo => {}});
process_ok("[% \$.bar %]" =>
"7", {name => 'foo', foo => {bar =>
7}});
process_ok("[% \$().bar %]" =>
"7", {name => 'foo', foo => {bar =>
7}});
process_ok("[% \$.0 %]" =>
"7", {name => 'foo', foo => [7, 2,
3]});
process_ok("[% \$ %]" => 7,
{name => 'foo', foo => sub });
process_ok("[% \$(7) %]" => 7,
{name => 'foo', foo => sub { $_[0] }});
process_ok("[% GET \$ %]" =>
"", {name => 'foo'});
process_ok("[% GET \$ %]" =>
"7", {name => 'foo', foo => 7});
process_ok("[% GET \$.bar %]" =>
"", {name => 'foo'});
process_ok("[% GET \$.bar %]" =>
"", {name => 'foo', foo => {}});
process_ok("[% GET \$.bar %]" =>
"7", {name => 'foo', foo => {bar =>
7}});
process_ok("[% GET \$.0 %]" =>
"7", {name => 'foo', foo => [7, 2,
3]});
process_ok("[% GET \$ %]" => 7,
{name => 'foo', foo => sub });
process_ok("[% GET \$(7) %]" => 7,
{name => 'foo', foo => sub { $_[0] }});
process_ok("[% \$ %]" =>
"", {name => 'foo foo', foo => 7});
process_ok("[% GET \$ %]" =>
"", {name => 'foo foo', foo => 7});
process_ok("[% GET \${'foo'} %]" =>
'bar', {foo => 'bar'});
process_ok("[% foo.\$name %]" => '', {name
=> 'bar'});
process_ok("[% foo.\$name %]" => 7, {name
=> 'bar', foo => {bar => 7}});
process_ok("[% foo.\$name.baz %]" => '',
{name => 'bar', bar => {baz => 7}});
process_ok("[% \"hi\" %]" =>
'hi');
process_ok("[% 'hi' %]" => 'hi');
process_ok("[% \"\$foo\" %]"
=> '7', {foo => 7});
process_ok("[% \"hi \$foo\" %]"
=> 'hi 7', {foo => 7});
process_ok("[% \"hi \$\" %]"
=> 'hi 7', {foo => 7});
process_ok("[% 'hi \$foo' %]" => 'hi
$foo', {foo => 7});
process_ok("[% 'hi \$' %]" => 'hi
$', {foo => 7});
process_ok("[% \"hi \${foo.seven}\"
%]" => 'hi 7', {foo => $obj});
process_ok("[% \"hi \${foo.echo(7)}\"
%]" => 'hi 7', {foo => $obj});
process_ok("[% _foo %]2" => '2', {_foo
=> 1});
process_ok("[% \$bar %]2" => '2', {_foo
=> 1, bar => '_foo'});
process_ok("[% __foo %]2" => '2', {__foo
=> 1});
process_ok("[% _foo = 1 %][% _foo %]2" =>
'2');
process_ok("[% foo._bar %]2" => '2', {foo
=> {_bar =>1}});
###---------------------------------------------------------
-------###
### variable SETting
process_ok("[% SET foo bar %][% foo %]" =>
'');
process_ok("[% SET foo = 1 %][% foo %]" =>
'1');
process_ok("[% SET foo = 1 bar = 2 %][% foo %][% bar
%]" => '12');
process_ok("[% SET foo bar = 1 %][% foo %]"
=> '');
process_ok("[% SET foo = 1 ; bar = 1 %][% foo
%]" => '1');
process_ok("[% SET foo = 1 %][% SET foo %][% foo
%]" => '');
process_ok("[% SET foo = [] %][% foo.0 %]" =>
"");
process_ok("[% SET foo = [1, 2, 3] %][% foo.1
%]" => 2);
process_ok("[% SET foo = {} %][% foo.0 %]" =>
"");
process_ok("[% SET foo = {1 => 2} %][% foo.1
%]" => "2") if ! $is_tt;
process_ok("[% SET foo = {'1' => 2} %][% foo.1
%]" => "2");
process_ok("[% SET name = 1 %][% SET foo = name %][%
foo %]" => "1");
process_ok("[% SET name = 1 %][% SET foo = \$name
%][% foo %]" => "");
process_ok("[% SET name = 1 %][% SET foo = \$
%][% foo %]" => "");
process_ok("[% SET name = 1 %][% SET foo =
\"\$name\" %][% foo %]" =>
"1");
process_ok("[% SET name = 1 foo = name %][% foo
%]" => '1');
process_ok("[% SET name = 1 %][% SET foo = {\$name
=> 2} %][% foo.1 %]" => "2");
process_ok("[% SET name = 1 %][% SET foo =
{\"\$name\" => 2} %][% foo.1 %]"
=> "2") if ! $is_tt;
process_ok("[% SET name = 1 %][% SET foo = {\$
=> 2} %][% foo.1 %]" => "2");
process_ok("[% SET name = 7 %][% SET foo = {'2'
=> name} %][% foo.2 %]" => "7");
process_ok("[% SET name = 7 %][% SET foo = {'2'
=> \"\$name\"} %][% foo.2 %]" =>
"7");
process_ok("[% SET name = 7 %][% SET foo = [1, name,
3] %][% foo.1 %]" => "7");
process_ok("[% SET name = 7 %][% SET foo = [1,
\"\$name\", 3] %][% foo.1 %]" =>
"7");
process_ok("[% SET foo = { bar => { baz => [0,
7, 2] } } %][% foo.bar.baz.1 %]" =>
"7");
process_ok("[% SET foo.bar = 1 %][% foo.bar %]"
=> '1');
process_ok("[% SET foo.bar.baz.bing = 1 %][%
foo.bar.baz.bing %]" => '1');
process_ok("[% SET foo.bar.2 = 1 %][% foo.bar.2 %] [%
foo.bar.size %]" => '1 1');
process_ok("[% SET foo.bar = [] %][% SET foo.bar.2 = 1
%][% foo.bar.2 %] [% foo.bar.size %]" => '1 3');
process_ok("[% SET name = 'two' %][% SET \$name = 3
%][% two %]" => 3);
process_ok("[% SET name = 'two' %][% SET \$ =
3 %][% two %]" => 3);
process_ok("[% SET name = 2 %][% SET foo.\$name = 3
%][% foo.2 %]" => 3);
process_ok("[% SET name = 2 %][% SET foo.\$name = 3
%][% foo.\$name %]" => 3);
process_ok("[% SET name = 2 %][% SET foo.\$ = 3
%][% foo.2 %]" => 3);
process_ok("[% SET name = 2 %][% SET foo.\$ = 3
%][% foo.2 %]" => 3);
process_ok("[% SET name = 'two' %][% SET \$name.foo
= 3 %][% two.foo %]" => 3);
process_ok("[% SET name = 'two' %][% SET
\$.foo = 3 %][% two.foo %]" => 3);
process_ok("[% SET name = 'two' %][% SET
foo.\$name.foo = 3 %][% foo.two.foo %]" => 3);
process_ok("[% SET name = 'two' %][% SET
foo.\$.foo = 3 %][% foo.two.foo %]" => 3);
process_ok("[% SET foo = [1..10] %][% foo.6 %]"
=> 7);
process_ok("[% SET foo = [10..1] %][% foo.6 %]"
=> '');
process_ok("[% SET foo = 1 .. 10 %][% foo.6 %]"
=> 7) if ! $is_tt;
process_ok("[% SET foo = [-10..-1] %][% foo.6
%]" => -4);
process_ok("[% SET foo = [1..3..10] %][% foo.6
%]" => '7') if ! $is_tt;
process_ok("[% SET foo = [1..2..10] %][% foo.6
%]" => '7') if ! $is_tt;
process_ok("[% SET foo = [1,1..0..10] %][% foo.6
%]" => '6') if ! $is_tt;
process_ok("[% SET foo = [1..10, 21..30] %][% foo.12
%]" => 23) if ! $is_tt;
process_ok("[% SET foo = [..100] bar = 7 %][% bar %][%
foo.0 %]" => '');
process_ok("[% SET foo = [100..] bar = 7 %][% bar %][%
foo.0 %]" => 7) if ! $is_tt;
process_ok("[% SET foo = ['a'..'z'] %][% foo.6
%]" => 'g');
process_ok("[% SET foo = ['z'..'a'] %][% foo.6
%]" => '');
process_ok("[% SET foo = ['a'..'z'].reverse %][%
foo.6 %]" => 't') if ! $is_tt;
process_ok("[% foo = 1 %][% foo %]" =>
'1');
process_ok("[% foo = 1 bar = 2 %][% foo %][% bar
%]" => '12');
process_ok("[% foo = 1 ; bar = 2 %][% foo %][% bar
%]" => '12');
process_ok("[% foo.bar = 2 %][% foo.bar %]"
=> '2');
process_ok('[% a = "a" %][% (b = a) %][% a %][%
b %]' => 'aaa');
process_ok('[% a = "a" %][% (c = (b = a)) %][%
a %][% b %][% c %]' => 'aaaa');
###---------------------------------------------------------
-------###
### Reserved words
my $vars = {
GET => 'named_get',
get => 'lower_named_get',
named_get => 'value of named_get',
hold_get => 'GET',
};
process_ok("[% GET %]" => '', $vars);
process_ok("[% GET GET %]" => 'named_get',
$vars) if ! $is_tt;
process_ok("[% GET get %]" =>
'lower_named_get', $vars);
process_ok("[% GET \${'GET'} %]" =>
'bar', {GET => 'bar'});
process_ok("[% GET = 1 %][% GET GET %]" =>
'', $vars);
process_ok("[% SET GET = 1 %][% GET GET %]"
=> '1', $vars) if ! $is_tt;
process_ok("[% GET \$hold_get %]" =>
'named_get', $vars);
process_ok("[% GET \$GET %]" => 'value of
named_get', $vars) if ! $is_tt;
process_ok("[% BLOCK GET %]hi[% END %][% PROCESS GET
%]" => 'hi') if ! $is_tt;
process_ok("[% BLOCK foo %]hi[% END %][% PROCESS foo a
= GET %]" => 'hi', $vars) if ! $is_tt;
process_ok("[% BLOCK foo %]hi[% END %][% PROCESS foo
GET = 1 %]" => '');
process_ok("[% BLOCK foo %]hi[% END %][% PROCESS foo
IF GET %]" => 'hi', $vars) if ! $is_tt;
###---------------------------------------------------------
-------###
### CALL and DEFAULT
process_ok("[% DEFAULT foo = 7 %][% foo %]"
=> 7);
process_ok("[% SET foo = 5 %][% DEFAULT foo = 7 %][%
foo %]" => 5);
process_ok("[% DEFAULT foo.bar.baz.bing = 6 %][%
foo.bar.baz.bing %]" => 6);
my $t = 0;
process_ok("[% foo %]" => 'hi', {foo
=> sub {$t++; 'hi'}});
process_ok("[% GET foo %]" => 'hi', {foo
=> sub {$t++; 'hi'}});
process_ok("[% CALL foo %]" => '', {foo
=> sub {$t++; 'hi'}});
ok($t == 3, "CALL method actually called var");
###---------------------------------------------------------
-------###
### virtual methods / filters
process_ok("[% [0 .. 10].reverse.1 %]" => 9)
if ! $is_tt;
process_ok("[% {a => 'A'}.a %]" =>
'A') if ! $is_tt;
process_ok("[% 'This is a string'.length %]"
=> 16) if ! $is_tt;
process_ok("[% 123.length %]" => 3) if !
$is_tt;
process_ok("[% 123.2.length %]" => 5) if !
$is_tt;
process_ok("[% -123.2.length %]" => -5) if !
$is_tt; # the - doesn't bind as tight as the dot methods
process_ok("[% (-123.2).length %]" => 6) if !
$is_tt;
process_ok("[% n.repeat %]" => '1', {n
=> 1}) if ! $is_tt; # tt2 virtual method defaults to 0
process_ok("[% n.repeat(0) %]" => '', {n
=> 1});
process_ok("[% n.repeat(1) %]" => '1', {n
=> 1});
process_ok("[% n.repeat(2) %]" => '11', {n
=> 1});
process_ok("[% n.repeat(2,'|') %]" =>
'1|1', {n => 1}) if ! $is_tt;
process_ok("[% n.size %]", => 'SIZE', {n
=> {size => 'SIZE', a => 'A'}});
process_ok("[% n|size %]", => '2', {n
=> {size => 'SIZE', a => 'A'}}) if ! $is_tt; #
tt2 | is alias for FILTER
process_ok('[% foo | eval %]' => 'baz', {foo =>
'[% bar %]', bar => 'baz'});
process_ok('[% "1" | indent(2) %]' => '
1');
process_ok("[% n.replace('foo', 'bar') %]"
=> 'barbar', {n => 'foofoo'});
process_ok("[% n.replace('(foo)', 'bar\$1')
%]" => 'barfoobarfoo', {n => 'foofoo'}) if !
$is_tt;
process_ok("[% n.replace('foo', 'bar', 0)
%]" => 'barfoo', {n => 'foofoo'}) if !
$is_tt;
process_ok("[% n FILTER size %]", => '1',
{n => {size => 'SIZE', a => 'A'}}) if ! $is_tt;
# tt2 doesn't have size
process_ok("[% n FILTER repeat %]" => '1',
{n => 1});
process_ok("[% n FILTER repeat(0) %]" =>
'', {n => 1});
process_ok("[% n FILTER repeat(1) %]" =>
'1', {n => 1});
process_ok("[% n FILTER repeat(2) %]" =>
'11', {n => 1});
process_ok("[% n FILTER repeat(2,'|') %]"
=> '1|1', {n => 1}) if ! $is_tt;
process_ok("[% n FILTER echo = repeat(2) %][% n FILTER
echo %]" => '1111', {n => 1});
process_ok("[% n FILTER echo = repeat(2) %][% n | echo
%]" => '1111', {n => 1});
process_ok("[% n FILTER echo = repeat(2) %][%
n|echo.length %]" => '112', {n => 1}) if !
$is_tt;
process_ok("[% n FILTER echo = repeat(2) %][% n FILTER
\$foo %]" => '1111', {n => 1, foo =>
'echo'});
process_ok("[% n FILTER echo = repeat(2) %][% n |
\$foo %]" => '1111', {n => 1, foo =>
'echo'});
process_ok("[% n FILTER echo = repeat(2) %][%
n|\$foo.length %]" => '112', {n => 1, foo
=> 'echo'}) if ! $is_tt;
process_ok('[% "hi" FILTER $foo %]' =>
'hihi', {foo => sub {sub {$_[0]x2}}}); # filter via a
passed var
process_ok('[% FILTER $foo %]hi[% END %]' => 'hihi',
{foo => sub {sub {$_[0]x2}}}); # filter via a passed var
process_ok('[% "hi" FILTER foo %]' =>
'hihi', {tt_config => [FILTERS => {foo => sub
{$_[0]x2}}]});
process_ok('[% "hi" FILTER foo %]' =>
'hihi', {tt_config => [FILTERS => {foo => [sub
{$_[0]x2},0]}]});
process_ok('[% "hi" FILTER foo(2) %]' =>
'hihi', {tt_config => [FILTERS => {foo => [sub
{my$a=$_[1];sub{$_[0]x$a}},1]}]});
### this does work - but requires that Template::Filters is
installed
#process_ok("[% ' ' | uri %]" => '%20');
###---------------------------------------------------------
-------###
### chomping
process_ok(" [% foo %]" => ' ');
process_ok(" [%- foo %]" => '');
process_ok("\n[%- foo %]" => '');
process_ok("\n [%- foo %]" => '');
process_ok("\n\n[%- foo %]" =>
"\n");
process_ok(" \n\n[%- foo %]" => "
\n");
process_ok(" \n[%- foo %]" => "
") if ! $is_tt;
process_ok(" \n \n[%- foo %]" => "
\n ") if ! $is_tt;
process_ok("[% foo %] " => ' ');
process_ok("[% foo -%] " => ' ');
process_ok("[% foo -%]\n" => '');
process_ok("[% foo -%] \n" => '');
process_ok("[% foo -%]\n " => ' ');
process_ok("[% foo -%]\n\n\n" =>
"\n\n");
process_ok("[% foo -%] \n " => ' ');
###---------------------------------------------------------
-------###
### math operations
process_ok("[% 1 + 2 %]" => 3);
process_ok("[% 1 + 2 + 3 %]" => 6);
process_ok("[% (1 + 2) %]" => 3);
process_ok("[% 2 - 1 %]" => 1);
process_ok("[% -1 + 2 %]" => 1);
process_ok("[% -1+2 %]" => 1);
process_ok("[% 2 - 1 %]" => 1);
process_ok("[% 2-1 %]" => 1) if ! $is_tt;
process_ok("[% 2 - -1 %]" => 3);
process_ok("[% 4 * 2 %]" => 8);
process_ok("[% 4 / 2 %]" => 2);
process_ok("[% 10 / 3 %]" => qr/^3.333/);
process_ok("[% 10 div 3 %]" => '3');
process_ok("[% 2 ** 3 %]" => 8) if ! $is_tt;
process_ok("[% 1 + 2 * 3 %]" => 7);
process_ok("[% 3 * 2 + 1 %]" => 7);
process_ok("[% (1 + 2) * 3 %]" => 9);
process_ok("[% 3 * (1 + 2) %]" => 9);
process_ok("[% 1 + 2 ** 3 %]" => 9) if !
$is_tt;
process_ok("[% 2 * 2 ** 3 %]" => 16) if !
$is_tt;
process_ok("[% SET foo = 1 %][% foo + 2 %]"
=> 3);
process_ok("[% SET foo = 1 %][% (foo + 2) %]"
=> 3);
###---------------------------------------------------------
-------###
### boolean operations
process_ok("[% 5 && 6 %]" => 6);
process_ok("[% 5 || 6 %]" => 5);
process_ok("[% 0 || 6 %]" => 6);
process_ok("[% 0 && 6 %]" => 0);
process_ok("[% 0 && 0 %]" => 0);
process_ok("[% 5 + (0 || 5) %]" => 10);
process_ok("[% 1 ? 2 : 3 %]" => '2');
process_ok("[% 0 ? 2 : 3 %]" => '3');
process_ok("[% 0 ? (1 ? 2 : 3) : 4 %]" =>
'4');
process_ok("[% 0 ? 1 ? 2 : 3 : 4 %]" =>
'4');
process_ok("[% t = 1 || 0 ? 3 : 4 %][% t %]"
=> 3);
process_ok("[% t = 0 or 1 ? 3 : 4 %][% t %]"
=> 3);
process_ok("[% t = 1 or 0 ? 3 : 4 %][% t %]"
=> 1) if ! $is_tt;
process_ok("[% 0 ? 2 : 3 %]" => '3');
process_ok("[% 1 ? 2 : 3 %]" => '2');
process_ok("[% 0 ? 1 ? 2 : 3 : 4 %]" =>
'4');
process_ok("[% t = 0 ? 1 ? [1..4] : [2..4] : [3..4]
%][% t.0 %]" => '3');
process_ok("[% t = 1 || 0 ? 0 : 1 || 2 ? 2 : 3 %][% t
%]" => '0');
process_ok("[% t = 0 or 0 ? 0 : 1 or 2 ? 2 : 3 %][% t
%]" => '1') if ! $is_tt;
process_ok("[% t = 0 or 0 ? 0 : 0 or 2 ? 2 : 3 %][% t
%]" => '2');
process_ok("[% 0 ? 1 ? 1 + 2 * 3 : 1 + 2 * 4 : 1 + 2 *
5 %]" => '11');
###---------------------------------------------------------
-------###
### blocks
process_ok("[% PROCESS foo %]" => '');
process_ok("[% BLOCK foo %]" => '');
process_ok("[% BLOCK foo %][% END %]" =>
'');
process_ok("[% BLOCK %][% END %]one" =>
'one');
process_ok("[% BLOCK foo %]hi there[% END %]"
=> '');
process_ok("[% BLOCK foo %][% BLOCK foo %][% END %][%
END %]" => '');
process_ok("[% BLOCK foo %]hi there[% END %][% PROCESS
foo %]" => 'hi there');
process_ok("[% PROCESS foo %][% BLOCK foo %]hi there[%
END %]" => 'hi there');
process_ok("[% BLOCK foo %]hi [% one %] there[% END
%][% PROCESS foo %]" => 'hi ONE there', {one
=> 'ONE'});
process_ok("[% BLOCK foo %]hi [% IF 1 %]Yes[% END %]
there[% END %]<<[% PROCESS foo %]>>" =>
'<<hi Yes there>>');
process_ok("[% BLOCK foo %]hi [% one %] there[% END
%][% PROCESS foo one = 'two' %]" => 'hi two
there');
process_ok("[% BLOCK foo %]hi [% one.two %] there[%
END %][% PROCESS foo one.two = 'two' %]" => 'hi
two there');
process_ok("[% BLOCK foo %]hi [% one.two %] there[%
END %][% PROCESS foo + foo one.two = 'two' %]" =>
'hi two there'x2);
process_ok("[% BLOCK foo %]hi [% one %] there[% END
%][% PROCESS foo one = 'two' %][% one %]" => 'hi
two theretwo');
process_ok("[% BLOCK foo %]hi [% one %] there[% END
%][% INCLUDE foo one = 'two' %][% one %]" => 'hi
two there');
###---------------------------------------------------------
-------###
### if/unless/elsif/else
process_ok("[% IF 1 %]Yes[% END %]" =>
'Yes');
process_ok("[% IF 0 %]Yes[% END %]" => '');
process_ok("[% IF 0 %]Yes[% ELSE %]No[% END %]"
=> 'No');
process_ok("[% IF 0 %]Yes[% ELSIF 1 %]No[% END
%]" => 'No');
process_ok("[% IF 0 %]Yes[% ELSIF 0 %]No[% END
%]" => '');
process_ok("[% IF 0 %]Yes[% ELSIF 0 %]No[% ELSE
%]hmm[% END %]" => 'hmm');
process_ok("[% UNLESS 1 %]Yes[% END %]" =>
'');
process_ok("[% UNLESS 0 %]Yes[% END %]" =>
'Yes');
process_ok("[% UNLESS 0 %]Yes[% ELSE %]No[% END
%]" => 'Yes');
process_ok("[% UNLESS 1 %]Yes[% ELSIF 1 %]No[% END
%]" => 'No');
process_ok("[% UNLESS 1 %]Yes[% ELSIF 0 %]No[% END
%]" => '');
process_ok("[% UNLESS 1 %]Yes[% ELSIF 0 %]No[% ELSE
%]hmm[% END %]" => 'hmm');
###---------------------------------------------------------
-------###
### comments
process_ok("[%# one %]" => '', {one =>
'ONE'});
process_ok("[%#\n one %]" => '', {one
=> 'ONE'});
process_ok("[%-#\n one %]" => '', {one
=> 'ONE'}) if ! $is_tt;
process_ok("[% #\n one %]" => 'ONE', {one
=> 'ONE'});
process_ok("[%# BLOCK one %]" => '');
process_ok("[%# BLOCK one %]two" => 'two');
process_ok("[%# BLOCK one %]two[% END %]" =>
'');
process_ok("[%# BLOCK one %]two[% END %]three"
=> '');
###---------------------------------------------------------
-------###
### foreach, next, last
process_ok("[% FOREACH foo %]" => '');
process_ok("[% FOREACH foo %][% END %]" =>
'');
process_ok("[% FOREACH foo %]bar[% END %]" =>
'');
process_ok("[% FOREACH foo %]bar[% END %]" =>
'bar', {foo => 1});
process_ok("[% FOREACH f IN foo %]bar[% f %][% END
%]" => 'bar1bar2', {foo => [1,2]});
process_ok("[% FOREACH f = foo %]bar[% f %][% END
%]" => 'bar1bar2', {foo => [1,2]});
process_ok("[% FOREACH f = [1,2] %]bar[% f %][% END
%]" => 'bar1bar2');
process_ok("[% FOREACH f = [1..3] %]bar[% f %][% END
%]" => 'bar1bar2bar3');
process_ok("[% FOREACH f =
[{a=>'A'},{a=>'B'}] %]bar[% f.a %][% END %]"
=> 'barAbarB');
process_ok("[% FOREACH [{a=>'A'},{a=>'B'}]
%]bar[% a %][% END %]" => 'barAbarB');
process_ok("[% FOREACH [{a=>'A'},{a=>'B'}]
%]bar[% a %][% END %][% a %]" => 'barAbarB');
process_ok("[% FOREACH f = [1..3] %][% loop.count
%]/[% loop.size %] [% END %]" => '1/3 2/3 3/3 ');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first
%][% f %][% END %][% END %]" => '1');
process_ok("[% FOREACH f = [1..3] %][% IF loop.last
%][% f %][% END %][% END %]" => '3');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first
%][% NEXT %][% END %][% f %][% END %]" => '23');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first
%][% LAST %][% END %][% f %][% END %]" => '');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF
loop.first %][% NEXT %][% END %][% END %]" =>
'123');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF
loop.first %][% LAST %][% END %][% END %]" =>
'1');
### TT is not consistent in what is localized - well it is
documented
### if you set a variable in the FOREACH tag, then nothing
in the loop gets localized
### if you don't set a variable - everything gets localized
process_ok("[% foo = 1 %][% FOREACH [1..10] %][% foo
%][% foo = 2 %][% END %]" => '1222222222');
process_ok("[% f = 1 %][% FOREACH i = [1..10] %][% i
%][% f = 2 %][% END %][% f %]" =>
'123456789102');
process_ok("[% f = 1 %][% FOREACH [1..10] %][% f = 2
%][% END %][% f %]" => '1');
process_ok("[% f = 1 %][% FOREACH f = [1..10] %][% f
%][% END %][% f %]" => '1234567891010');
process_ok("[% FOREACH [1] %][% SET a = 1 %][% END
%][% a %]" => '');
process_ok("[% a %][% FOREACH [1] %][% SET a = 1 %][%
END %][% a %]" => '');
process_ok("[% a = 2 %][% FOREACH [1] %][% SET a = 1
%][% END %][% a %]" => '2');
process_ok("[% a = 2 %][% FOREACH [1] %][% a = 1 %][%
END %][% a %]" => '2');
process_ok("[% a = 2 %][% FOREACH i = [1] %][% a = 1
%][% END %][% a %]" => '1');
process_ok("[% FOREACH i = [1] %][% SET a = 1 %][% END
%][% a %]" => '1');
process_ok("[% f.b = 1 %][% FOREACH f.b = [1..10] %][%
f.b %][% END %][% f.b %]" => '1234567891010') if
! $is_tt;
process_ok("[% a = 1 %][% FOREACH
[{a=>'A'},{a=>'B'}] %]bar[% a %][% END %][% a
%]" => 'barAbarB1');
process_ok("[% FOREACH [1..3] %][% loop.size %][% END
%][% loop.size %]" => '333');
process_ok("[% FOREACH i = [1..3] %][% loop.size %][%
END %][% loop.size %]" => '333') if ! $is_tt;
process_ok("[% FOREACH i = [1..3] %][% loop.size %][%
END %][% loop.size %]" => '3331') if $is_tt;
###---------------------------------------------------------
-------###
### while
process_ok("[% WHILE foo %]" => '');
process_ok("[% WHILE foo %][% END %]" =>
'');
process_ok("[% WHILE (foo = foo - 1) %][% END
%]" => '');
process_ok("[% WHILE (foo = foo - 1) %][% foo %][% END
%]" => '21', {foo => 3});
process_ok("[% WHILE foo %][% foo %][% foo = foo - 1
%][% END %]" => '321', {foo => 3});
process_ok("[% WHILE 1 %][% foo %][% foo = foo - 1
%][% LAST IF foo == 1 %][% END %]" => '32', {foo
=> 3});
process_ok("[% f = 10; WHILE f; f = f - 1 ; f ; END
%]" => '9876543210');
process_ok("[% f = 10; WHILE f; f = f - 1 ; f ; END ;
f %]" => '98765432100');
process_ok("[% f = 10 a = 2; WHILE f; f = f - 1 ; f ;
a=3; END ; a%]" => '98765432103');
process_ok("[% f = 10; WHILE (g=f); f = f - 1 ; f ;
END %]" => '9876543210');
process_ok("[% f = 10; WHILE (g=f); f = f - 1 ; f ;
END ; f %]" => '98765432100');
process_ok("[% f = 10 a = 2; WHILE (g=f); f = f - 1 ;
f ; a=3; END ; a%]" => '98765432103');
process_ok("[% f = 10 a = 2; WHILE (a=f); f = f - 1 ;
f ; a=3; END ; a%]" => '98765432100');
###---------------------------------------------------------
-------###
### stop, return, clear
process_ok("[% STOP %]" => '');
process_ok("One[% STOP %]Two" => 'One');
process_ok("[% BLOCK foo %]One[% STOP %]Two[% END
%]First[% PROCESS foo %]Last" => 'FirstOne');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF
loop.first %][% STOP %][% END %][% END %]" =>
'1');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first
%][% STOP %][% END %][% f %][% END %]" => '');
process_ok("[% RETURN %]" => '');
process_ok("One[% RETURN %]Two" => 'One');
process_ok("[% BLOCK foo %]One[% RETURN %]Two[% END
%]First[% PROCESS foo %]Last" => 'FirstOneLast');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF
loop.first %][% RETURN %][% END %][% END %]" =>
'1');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first
%][% RETURN %][% END %][% f %][% END %]" => '');
process_ok("[% CLEAR %]" => '');
process_ok("One[% CLEAR %]Two" => 'Two');
process_ok("[% BLOCK foo %]One[% CLEAR %]Two[% END
%]First[% PROCESS foo %]Last" => 'FirstTwoLast');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF
loop.first %][% CLEAR %][% END %][% END %]" =>
'23');
process_ok("[% FOREACH f = [1..3] %][% IF loop.first
%][% CLEAR %][% END %][% f %][% END %]" =>
'123');
process_ok("[% FOREACH f = [1..3] %][% f %][% IF
loop.last %][% CLEAR %][% END %][% END %]" =>
'');
process_ok("[% FOREACH f = [1..3] %][% IF loop.last
%][% CLEAR %][% END %][% f %][% END %]" => '3');
###---------------------------------------------------------
-------###
### multiple-directives
process_ok("[% GET foo; GET foo %]" =>
'11', {foo => 1});
process_ok('[% FOREACH f = [1..3]; 1; END %]' =>
'111');
process_ok('[% FOREACH f = [1..3]; f; END %]' =>
'123');
process_ok('[% FOREACH f = [1..3]; "$f"; END
%]' => '123');
process_ok('[% FOREACH f = [1..3]; f + 1; END %]' =>
'234');
###---------------------------------------------------------
-------###
### post opererator
process_ok("[% GET foo IF 1 %]" => '1',
{foo => 1});
process_ok("[% f FOREACH f = [1..3] %]" =>
'123');
process_ok("2[% GET foo IF 1 IF 2 %]" =>
'21', {foo => 1}) if ! $is_tt;
process_ok("2[% GET foo IF 1 IF 0 %]" =>
'2', {foo => 1}) if ! $is_tt;
process_ok("[% f FOREACH f = [1..3] IF 1 %]"
=> '123') if ! $is_tt;
process_ok("[% f FOREACH f = [1..3] IF 0 %]"
=> '') if ! $is_tt;
process_ok("[% f FOREACH f = g FOREACH g = [1..3]
%]" => '123') if ! $is_tt;
process_ok("[% f FOREACH f = g.a FOREACH g =
[{a=>1}, {a=>2}, {a=>3}] %]" => '123')
if ! $is_tt;
process_ok("[% f FOREACH f = a FOREACH [{a=>1},
{a=>2}, {a=>3}] %]" => '123') if !
$is_tt;
process_ok("[% FOREACH f = [1..3] IF 1 %]([% f %])[%
END %]" => '(1)(2)(3)') if ! $is_tt;
process_ok("[% FOREACH f = [1..3] IF 0 %]([% f %])[%
END %]" => '') if ! $is_tt;
process_ok("[% BLOCK bar %][% foo %][% foo = foo - 1
%][% END %][% PROCESS bar WHILE foo %]" => '321',
{foo => 3});
###---------------------------------------------------------
-------###
### capturing
process_ok("[% foo = BLOCK %]Hi[% END %][% foo %][%
foo %]" => 'HiHi');
process_ok("[% BLOCK foo %]Hi[% END %][% bar = PROCESS
foo %]-[% bar %]" => '-Hi');
process_ok("[% foo = IF 1 %]Hi[% END %][% foo
%]" => 'Hi');
###---------------------------------------------------------
-------###
### tags
process_ok("[% TAGS html %]<!-- 1 + 2 -->"
=> '3');
process_ok("[% TAGS <!-- --> %]<!-- 1 + 2
-->" => '3');
process_ok("[% TAGS html %] <!--- 1 + 2
-->" => '3');
process_ok("[% TAGS html %]<!-- 1 + 2
--->" => '3') if ! $is_tt;
process_ok("[% TAGS html %]<!-- 1 + 2
--->\n" => '3');
process_ok("[% BLOCK foo %][% TAGS html %]<!-- 1 +
2 -->[% END %][% PROCESS foo %] [% 1 + 2 %]" =>
'');
###---------------------------------------------------------
-------###
### switch
process_ok("[% SWITCH 1 %][% END %]hi" =>
'hi');
process_ok("[% SWITCH 1 %][% CASE %]bar[% END
%]hi" => 'barhi');
process_ok("[% SWITCH 1 %]Pre[% CASE %]bar[% END
%]hi" => 'barhi');
process_ok("[% SWITCH 1 %][% CASE DEFAULT %]bar[% END
%]hi" => 'barhi');
process_ok("[% SWITCH 1 %][% CASE 0 %]bar[% END
%]hi" => 'hi');
process_ok("[% SWITCH 1 %][% CASE 1 %]bar[% END
%]hi" => 'barhi');
process_ok("[% SWITCH 1 %][% CASE foo %][% CASE 1
%]bar[% END %]hi" => 'barhi');
process_ok("[% SWITCH 1 %][% CASE [1..10] %]bar[% END
%]hi" => 'barhi');
process_ok("[% SWITCH 11 %][% CASE [1..10] %]bar[% END
%]hi" => 'hi');
process_ok("[% SWITCH 1.0 %][% CASE [1..10] %]bar[%
END %]hi" => 'barhi');
process_ok("[% SWITCH '1.0' %][% CASE [1..10]
%]bar[% END %]hi" => 'barhi') if ! $is_tt;
###---------------------------------------------------------
-------###
### try/throw/catch/final
process_ok("[% TRY %][% END %]hi" => 'hi');
process_ok("[% TRY %]Foo[% END %]hi" =>
'Foohi');
process_ok("[% TRY %]Foo[% THROW foo 'for fun'
%]bar[% END %]hi" => '');
process_ok("[% TRY %]Foo[% THROW foo 'for fun'
%]bar[% CATCH %][% END %]hi" => 'Foohi') if !
$is_tt;
process_ok("[% TRY %]Foo[% THROW foo 'for fun'
%]bar[% CATCH %]there[% END %]hi" =>
'Footherehi');
process_ok("[% TRY %]Foo[% THROW foo 'for fun'
%]bar[% CATCH foo %]there[% END %]hi" =>
'Footherehi');
process_ok("[% TRY %]Foo[% TRY %]Foo[% THROW foo 'for
fun' %][% CATCH bar %]one[% END %][% CATCH %]two[% END
%]hi" => 'FooFootwohi');
process_ok("[% TRY %]Foo[% TRY %]Foo[% THROW foo 'for
fun' %][% CATCH bar %]one[% END %][% CATCH s %]two[% END
%]hi" => '');
process_ok("[% TRY %]Foo[% THROW foo.bar 'for fun'
%][% CATCH foo %]one[% CATCH foo.bar %]two[% END %]hi"
=> 'Footwohi');
process_ok("[% TRY %]Foo[% FINAL %]Bar[% END
%]hi" => 'FooBarhi');
process_ok("[% TRY %]Foo[% THROW foo %][% FINAL
%]Bar[% CATCH %]one[% END %]hi" => '');
process_ok("[% TRY %]Foo[% THROW foo %][% CATCH
%]one[% FINAL %]Bar[% END %]hi" =>
'FoooneBarhi');
process_ok("[% TRY %]Foo[% THROW foo %][% CATCH bar
%]one[% FINAL %]Bar[% END %]hi" => '');
process_ok("[% TRY %][% THROW foo 'bar' %][% CATCH
%][% error %][% END %]" => 'foo error - bar');
process_ok("[% TRY %][% THROW foo 'bar' %][% CATCH
%][% error.type %][% END %]" => 'foo');
process_ok("[% TRY %][% THROW foo 'bar' %][% CATCH
%][% error.info %][% END %]" => 'bar');
process_ok("[% TRY %][% THROW foo %][% CATCH %][%
error.type %][% END %]" => 'undef');
process_ok("[% TRY %][% THROW foo %][% CATCH %][%
error.info %][% END %]" => 'foo');
###---------------------------------------------------------
-------###
### named args
process_ok("[% foo(bar = 'one', baz = 'two')
%]" => "baronebaztwo",
{foo=>sub{my
$n=$_[-1];join('',map{"$_$n->{$_}"} sort
keys %$n)}});
process_ok("[%bar='ONE'%][% foo(\$bar = 'one')
%]" => "ONEone",
{foo=>sub{my
$n=$_[-1];join('',map{"$_$n->{$_}"} sort
keys %$n)}});
###---------------------------------------------------------
-------###
### use
my config_p = (PLUGIN_BASE => 'MyTestPlugin',
LOAD_PERL => 1);
process_ok("[% USE son_of_gun_that_does_not_exist
%]one" => '', {tt_config => \ config_p});
process_ok("[% USE Foo %]one" => 'one',
{tt_config => \ config_p});
process_ok("[% USE Foo2 %]one" => 'one',
{tt_config => \ config_p});
process_ok("[% USE Foo(bar = 'baz') %]one[% Foo.bar
%]" => 'onebarbaz', {tt_config => \ config_p});
process_ok("[% USE Foo2(bar = 'baz') %]one[%
Foo2.bar %]" => 'onebarbaz', {tt_config =>
\ config_p});
process_ok("[% USE Foo(bar = 'baz') %]one[% Foo.bar
%]" => 'onebarbaz', {tt_config => \ config_p});
process_ok("[% USE d = Foo(bar = 'baz') %]one[%
d.bar %]" => 'onebarbaz', {tt_config => \ config_p});
process_ok("[% USE d.d = Foo(bar = 'baz') %]one[%
d.d.bar %]" => '', {tt_config => \ config_p});
process_ok("[% USE a(bar = 'baz') %]one[% a.seven
%]" => '', {tt_config => [ config_p,
PLUGINS => {a=>'Foo'}, ]});
process_ok("[% USE a(bar = 'baz') %]one[% a.seven
%]" => 'one7', {tt_config => [ config_p,
PLUGINS => {a=>'Foo2'},]});
###---------------------------------------------------------
-------###
### macro
process_ok("[% MACRO foo PROCESS bar %][% BLOCK bar
%]Hi[% END %][% foo %]" => 'Hi');
process_ok("[% MACRO foo BLOCK %]Hi[% END %][% foo
%]" => 'Hi');
process_ok("[% MACRO foo BLOCK %]Hi[% END %][% foo
%]" => 'Hi');
process_ok("[% MACRO foo(n) BLOCK %]Hi[% n %][% END
%][% foo(2) %]" => 'Hi2');
process_ok("[%n=1%][% MACRO foo(n) BLOCK %]Hi[% n %][%
END %][% foo(2) %][%n%]" => 'Hi21');
process_ok("[%n=1%][% MACRO foo BLOCK %]Hi[% n = 2%][%
END %][% foo %][%n%]" => 'Hi1');
process_ok("[% MACRO foo(n) FOREACH i=[1..n] %][% i
%][% END %][% foo(3) %]" => '123');
###---------------------------------------------------------
-------###
### debug;
process_ok("\n\n[% one %]" =>
"\n\n\n## input text line 3 : [% one %]
##\nONE", {one=>'ONE', tt_config =>
['DEBUG' => 8]});
process_ok("[% one %]" => "\n## input
text line 1 : [% one %] ##\nONE", {one=>'ONE',
tt_config => ['DEBUG' => 8]});
process_ok("[% one %]\n\n" =>
"(1)ONE\n\n", {one=>'ONE', tt_config
=> ['DEBUG' => 8, 'DEBUG_FORMAT' =>
'($line)']});
process_ok("1\n2\n3[% one %]" =>
"1\n2\n3(3)ONE", {one=>'ONE', tt_config
=> ['DEBUG' => 8, 'DEBUG_FORMAT' =>
'($line)']});
process_ok("[% one;\n one %]" =>
"(1)ONE(2)ONE", {one=>'ONE', tt_config
=> ['DEBUG' => 8,
'DEBUG_FORMAT' => '($line)']}) if !
$is_tt;
process_ok("[% DEBUG format '(\$line)' %][% one
%]" => qr/\(1\)/, {one=>'ONE', tt_config
=> ['DEBUG' => 8]});
process_ok("[% TRY %][% abc %][% CATCH %][% error %][%
END %]" => "undef error - abc is
undefined\n", {tt_config => ['DEBUG' => 2]});
process_ok("[% TRY %][% abc.def %][% CATCH %][% error
%][% END %]" => "undef error - def is
undefined\n", {abc => {}, tt_config =>
['DEBUG' => 2]});
###---------------------------------------------------------
-------###
### constants
my config_c = (
CONSTANTS => {
harry => sub {'do_this_once'},
foo => {
bar => {baz => 42},
bim => 57,
},
bing => 'baz',
bang => 'bim',
},
VARIABLES => {
bam => 'bar',
},
);
process_ok("[% constants.harry %]" =>
'do_this_once', {tt_config => \ config_c});
process_ok("[% constants.harry.length %]" =>
'12', {tt_config => \ config_c});
process_ok("[% SET constants.something = 1 %][%
constants.something %]one" => '1one', {tt_config
=> \ config_c});
process_ok("[% SET constants.harry = 1 %][%
constants.harry %]one" => 'do_this_onceone',
{tt_config => \ config_c});
process_ok("[% constants.foo.\${constants.bang}
%]" => '57', {tt_config => [ config_c]});
process_ok("[%
constants.foo.\$bam.\${constants.bing} %]" =>
'42', {tt_config => [ config_c]}) if ! $is_tt;
process_ok("[% bam = 'somethingelse' %][%
constants.foo.\$bam.\${constants.bing} %]" =>
'42', {tt_config => [ config_c]}) if ! $is_tt;
###---------------------------------------------------------
-------###
### interpolate / anycase / trim
process_ok("Foo \$one Bar" => 'Foo ONE
Bar', {one => 'ONE', tt_config => ['INTERPOLATE'
=> 1]});
process_ok("[% PERL %] my \$n=7; print \$n [% END
%]" => '7', {tt_config => ['INTERPOLATE'
=> 1, 'EVAL_PERL' => 1]});
process_ok("[% TRY ; PERL %] my \$n=7; print \$n [%
END ; END %]" => '7', {tt_config =>
['INTERPOLATE' => 1, 'EVAL_PERL' => 1]});
process_ok("[% GET %]" => '', {GET =>
'ONE'});
process_ok("[% GET GET %]" => 'ONE', {GET
=> 'ONE'}) if ! $is_tt;
process_ok("[% BLOCK foo %]\nhi\n[% END %][% PROCESS
foo %]" => "\nhi\n");
process_ok("[% BLOCK foo %]\nhi[% END %][% PROCESS
foo %]" => "hi", {tt_config => [TRIM
=> 1]});
process_ok("[% BLOCK foo %]hi\n[% END %][% PROCESS
foo %]" => "hi", {tt_config => [TRIM
=> 1]});
process_ok("[% BLOCK foo %]hi[% nl %][% END %][%
PROCESS foo %]" => "hi", {nl =>
"\n", tt_config => [TRIM => 1]});
process_ok("[% BLOCK foo %][% nl %]hi[% END %][%
PROCESS foo %]" => "hi", {nl =>
"\n", tt_config => [TRIM => 1]});
process_ok("A[% TRY %]\nhi\n[% END %]" =>
"A\nhi", {tt_config => [TRIM => 1]});
###---------------------------------------------------------
-------###
### perl
process_ok("[% TRY %][% PERL %][% END %][% CATCH ;
error; END %]" => 'perl error - EVAL_PERL not
set');
process_ok("[% PERL %] print \"[% one
%]\" [% END %]" => 'ONE', {one =>
'ONE', tt_config => ['EVAL_PERL' => 1]});
process_ok("[% PERL %] print \$stash->get('one')
[% END %]" => 'ONE', {one => 'ONE',
tt_config => ['EVAL_PERL' => 1]});
process_ok("[% PERL %] print
\$stash->set('a.b.c', 7) [% END %][% a.b.c %]"
=> '77', {tt_config => ['EVAL_PERL' => 1]});
###---------------------------------------------------------
-------###
### recursion prevention
process_ok("[% BLOCK foo %][% PROCESS bar %][% END
%][% BLOCK bar %][% PROCESS foo %][% END %][% PROCESS foo
%]" => '') if ! $is_tt;
###---------------------------------------------------------
-------###
### refs
process_ok("[% b=\\a; b %]" => 'a sub
[]', {a => sub { return "a sub [ _]"
} });
process_ok("[% \\a %]" => qr/^CODE/, {a
=> sub { return "a sub [ _]" } });
process_ok("[% b=\\a(1); b %]" => 'a sub
[1]', {a => sub { return "a sub [ _]"
} });
process_ok("[% b=\\a; b(2) %]" => 'a sub
[2]', {a => sub { return "a sub [ _]"
} });
process_ok("[% b=\\a(1); b(2) %]" => 'a
sub [1 2]', {a => sub { return "a sub [ _]"
} });
process_ok("[% f=\\j.k; j.k=7; f %]" =>
'7', {j => {k => 3}});
process_ok('[% a = "a" ; f =
{a=>"A",b=>"B"} ; foo = \f.$a
; foo %]' => 'A');
process_ok('[% a = "a" ; f =
{a=>"A",b=>"B"} ; foo = \f.$a
; a = "b" ; foo %]' => 'A');
process_ok('[% a = "ab" ; f =
"abcd"; foo = \f.replace(a, "-AB-")
; a = "cd"; foo %]' => '-AB-cd');
process_ok('[% a = "ab" ; f =
"abcd"; foo = \f.replace(a,
"-AB-").replace("-AB-",
"*") ; a = "cd"; foo %]' =>
'*cd');
### tt does odd things with partially vivifying methods
process_ok('[% a = "ab" ; f =
"abcd"; foo = \f.replace(a, "-AB-")
; f = "ab"; foo %]' => '-AB-cd') if
$is_tt;
process_ok('[% a = "ab" ; f =
"abcd"; foo = \f.replace(a,
"-AB-").replace("-AB-",
"*") ; f = "ab"; foo %]' =>
'*cd') if $is_tt;
process_ok('[% a = "ab" ; f =
"abcd"; foo = \f.replace(a, "-AB-")
; f = "ab"; foo %]' => '-AB-') if !
$is_tt;
process_ok('[% a = "ab" ; f =
"abcd"; foo = \f.replace(a,
"-AB-").replace("-AB-",
"*") ; f = "ab"; foo %]' =>
'*') if ! $is_tt;
|