List Info

Thread: Can there be two?




Can there be two?
user name
2006-05-04 22:12:14
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;

Can there be two?
user name
2006-05-19 14:18:26
Sorry about this one.  This was the original message I tried
sending.  It was 
so large that it didn't make it through and I got a
notification saying it 
hadn't gone through.  I broke it into smaller segments
after that.

We've already covered this thread so I don't think anybody
needs to respond to 
this one.

Paul

_______________________________________________
templates mailing list
templatestemplate-toolkit.org
http://lists.template-toolkit.org/mailman/listinfo/t
emplates
[1-2]

about | contact  Other archives ( Real Estate discussion Medical topics )