]> Dogcows Code - chaz/p5-CGI-Ex/blobdiff - t/7_template_00_base.t
CGI::Ex 2.11
[chaz/p5-CGI-Ex] / t / 7_template_00_base.t
index 9481952e0730f39e60e6939ae8707db3e8744b74..b5813fc4b5f90be8b5c015c286cad8c548a4d4e3 100644 (file)
@@ -8,13 +8,13 @@
 
 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
+    $module = 'CGI::Ex::Template'; #real    0m0.885s #user    0m0.432s #sys     0m0.004s
+#    $module = 'Template';         #real    0m2.133s #user    0m1.108s #sys     0m0.024s
     $is_tt = $module eq 'Template';
 };
 
 use strict;
-use Test::More tests => 514 - ($is_tt ? 103 : 0);
+use Test::More tests => ! $is_tt ? 806 : 599;
 use Data::Dumper qw(Dumper);
 use constant test_taint => 0 && eval { require Taint::Runtime };
 
@@ -27,20 +27,27 @@ 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->{tt_config} || [] }); # new object each time
+    my $vars = shift || {};
+    my $conf = local $vars->{'tt_config'} = $vars->{'tt_config'} || [];
+    my $obj  = shift || $module->new(@$conf); # new object each time
     my $out  = '';
+    my $line = (caller)[2];
+    delete $vars->{'tt_config'};
 
     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;
+    if ($ok) {
+        ok(1, "Line $line   \"$str\" => \"$out\"");
+        return $obj;
+    } else {
+        ok(0, "Line $line   \"$str\"");
+        warn "# Was:\n$out\n# Should've been:\n$test\n";
+        print $obj->error if $obj->can('error');
+        print Dumper $obj->parse_tree(\$str) if $obj->can('parse_tree');
+        exit;
+    }
 }
 
 ###----------------------------------------------------------------###
@@ -76,10 +83,10 @@ sub process_ok { # process the value and say if it was ok
 }
 
 my $obj = Foo2->new;
-
+my $vars;
 
 ###----------------------------------------------------------------###
-### variable GETting
+print "### GET ##############################################################\n";
 
 process_ok("[% foo %]" => "");
 process_ok("[% foo %]" => "7",       {foo => 7});
@@ -98,6 +105,7 @@ process_ok("[% foo(7) %]" => 7,      {foo => sub { $_[0] }});
 process_ok("[% foo.length %]" => 1,  {foo => sub { 7 }});
 process_ok("[% foo.0 %]" => 7,       {foo => sub { return 7, 2, 3 }});
 process_ok("[% foo(bar) %]" => 7,    {foo => sub { $_[0] }, bar => 7});
+process_ok("[% foo(bar.baz) %]" => 7,{foo => sub { $_[0] }, bar => {baz => 7}});
 process_ok("[% foo.seven %]" => 7,   {foo => $obj});
 process_ok("[% foo.seven() %]" => 7, {foo => $obj});
 process_ok("[% foo.seven.length %]" => 1, {foo => $obj});
@@ -166,12 +174,16 @@ 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 %]" => '');
 process_ok("[% 'hi' %]" => 'hi');
+process_ok("[% 'hi %]"  => '');
 process_ok("[% \"\$foo\" %]"   => '7', {foo => 7});
 process_ok("[% \"hi \$foo\" %]"   => 'hi 7', {foo => 7});
 process_ok("[% \"hi \${foo}\" %]" => 'hi 7', {foo => 7});
 process_ok("[% 'hi \$foo' %]"   => 'hi $foo', {foo => 7});
 process_ok("[% 'hi \${foo}' %]" => 'hi ${foo}', {foo => 7});
+process_ok("[% 7 %]" => 7);
+process_ok("[% -7 %]" => -7);
 
 process_ok("[% \"hi \${foo.seven}\" %]"   => 'hi 7', {foo => $obj});
 process_ok("[% \"hi \${foo.echo(7)}\" %]" => 'hi 7', {foo => $obj});
@@ -179,15 +191,19 @@ 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}});
 
 process_ok("[% qw/Foo Bar Baz/.0 %]" => 'Foo') if ! $is_tt;
 process_ok('[% [0..10].-1 %]' => '10') if ! $is_tt;
 process_ok('[% [0..10].${ 2.3 } %]' => '2') if ! $is_tt;
 
+process_ok("[% (1 + 2)() %]" => ''); # parse error
+process_ok("[% (1 + 2) %]" => '3');
+process_ok("[% (a) %]" => '2', {a => 2});
+process_ok("[% ('foo') %]" => 'foo');
+process_ok("[% (a(2)) %]" => '2', {a => sub { $_[0] }});
+
 ###----------------------------------------------------------------###
-### variable SETting
+print "### SET ##############################################################\n";
 
 process_ok("[% SET foo bar %][% foo %]" => '');
 process_ok("[% SET foo = 1 %][% foo %]" => '1');
@@ -240,7 +256,7 @@ process_ok("[% SET foo = [10..1] %][% foo.6 %]" => '');
 process_ok("[% SET foo = [-10..-1] %][% foo.6 %]" => -4);
 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 = [100..] bar = 7 %][% bar %][% foo.0 %]" => '');
 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;
@@ -249,7 +265,7 @@ process_ok("[% foo = 1 %][% foo %]" => '1');
 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" %]|[% (b = a) %]|[% a %]|[% b %]' => '|a|a|a');
 process_ok('[% a = "a" %][% (c = (b = a)) %][% a %][% b %][% c %]' => 'aaaa');
 
 process_ok("[% a = qw{Foo Bar Baz} ; a.2 %]" => 'Baz') if ! $is_tt;
@@ -258,32 +274,18 @@ process_ok("[% foo = 1 bar = 2 %][% foo %][% bar %]" => '12');
 process_ok("[% foo = 1 bar = 2 %][% foo = 3 bar %][% foo %][% bar %]" => '232') if ! $is_tt;
 process_ok("[% a = 1 a = a + 2 a %]" => 3) if ! $is_tt;
 
-###----------------------------------------------------------------###
-### 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("[% _foo = 1 %][% _foo %]2" => '2');
+process_ok("[% foo._bar %]2" => '2', {foo => {_bar =>1}});
 
-process_ok("[% GET = 1 %][% GET GET %]" => '', $vars);
-process_ok("[% SET GET = 1 %][% GET GET %]" => '1', $vars) if ! $is_tt;
+###----------------------------------------------------------------###
+print "### multiple statements in same tag ##################################\n";
 
-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;
+process_ok("[% foo; %]" => '1', {foo => 1});
+process_ok("[% GET foo; %]" => '1', {foo => 1});
+process_ok("[% GET foo; GET foo %]" => '11', {foo => 1});
 
 ###----------------------------------------------------------------###
-### CALL and DEFAULT
+print "### CALL / DEFAULT ###################################################\n";
 
 process_ok("[% DEFAULT foo = 7 %][% foo %]" => 7);
 process_ok("[% SET foo = 5 %][% DEFAULT foo = 7 %][% foo %]" => 5);
@@ -296,7 +298,162 @@ process_ok("[% CALL foo %]" => '',   {foo => sub {$t++; 'hi'}});
 ok($t == 3, "CALL method actually called var");
 
 ###----------------------------------------------------------------###
-### virtual methods / filters
+print "### scalar vmethods ##################################################\n";
+
+process_ok("[% n.0 %]" => '7', {n => 7}) if ! $is_tt;
+process_ok("[% n.chunk(3).join %]" => 'abc def g', {n => 'abcdefg'});
+process_ok("[% n.chunk(-3).join %]" => 'a bcd efg', {n => 'abcdefg'});
+process_ok("[% n|collapse %]" => "a b", {n => '  a  b  '}); # TT2 filter
+process_ok("[% n.defined %]" => "1", {n => ''});
+process_ok("[% n.defined %]" => "", {n => undef});
+process_ok("[% n.defined %]" => "1", {n => '1'});
+process_ok("[% n|indent %]" => "    a\n    b", {n => "a\nb"}); # TT2 filter
+process_ok("[% n|indent(2) %]" => "  a\n  b", {n => "a\nb"}); # TT2 filter
+process_ok("[% n|indent('wow ') %]" => "wow a\nwow b", {n => "a\nb"}); # TT2 filter
+process_ok("[% n.int %]" => "123", {n => "123.234"}) if ! $is_tt;
+process_ok("[% n.int %]" => "123", {n => "123gggg"}) if ! $is_tt;
+process_ok("[% n.int %]" => "0", {n => "ff123.234"}) if ! $is_tt;
+process_ok("[% n.fmt %]" => '7', {n => 7}) if ! $is_tt;
+process_ok("[% n.fmt('%02d') %]" => '07', {n => 7}) if ! $is_tt;
+process_ok("[% n.fmt('%0*d', 3) %]" => '007', {n => 7}) if ! $is_tt;
+process_ok("[% n.fmt('(%s)') %]" => "(a\nb)", {n => "a\nb"}) if ! $is_tt;
+process_ok("[% n|format('%02d') %]" => '07', {n => 7}); # TT2 filter
+process_ok("[% n|format('%0*d', 3) %]" => '007', {n => 7}) if ! $is_tt;
+process_ok("[% n|format('(%s)') %]" => "(a)\n(b)", {n => "a\nb"}); # TT2 filter
+process_ok("[% n.hash.items.1 %]" => "b", {n => {a => "b"}});
+process_ok("[% n|html %]" => "&", {n => '&'}); # TT2 filter
+process_ok("[% n.item %]" => '7', {n => 7});
+process_ok("[% n|lcfirst %]" => 'fOO', {n => "FOO"}); # TT2 filter
+process_ok("[% n.length %]" => 3, {n => "abc"});
+process_ok("[% n.list.0 %]" => 'abc', {n => "abc"});
+process_ok("[% n|lower %]" => 'abc', {n => "ABC"}); # TT2 filter
+process_ok("[% n.match('foo').join %]" => '', {n => "bar"});
+process_ok("[% n.match('foo').join %]" => '1', {n => "foo"});
+process_ok("[% n.match('foo',1).join %]" => 'foo', {n => "foo"});
+process_ok("[% n.match('(foo)').join %]" => 'foo', {n => "foo"});
+process_ok("[% n.match('(foo)').join %]" => 'foo', {n => "foofoo"});
+process_ok("[% n.match('(foo)',1).join %]" => 'foo foo', {n => "foofoo"});
+process_ok("[% n.null %]" => '', {n => "abc"});
+process_ok("[% n.rand %]" => qr{^\d+\.\d+}, {n => "2"}) if ! $is_tt;
+process_ok("[% n.rand %]" => qr{^\d+\.\d+}, {n => "ab"}) if ! $is_tt;
+process_ok("[% n.remove('bc') %]" => "a", {n => "abc"});
+process_ok("[% n.remove('bc') %]" => "aa", {n => "abcabc"});
+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.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.search('foo') %]" => '', {n => "bar"});
+process_ok("[% n.search('foo') %]" => '1', {n => "foo"});
+process_ok("[% n.size %]" => '1', {n => "foo"});
+process_ok("[% n.split.join('|') %]" => "abc", {n => "abc"});
+process_ok("[% n.split.join('|') %]" => "a|b|c", {n => "a b c"});
+process_ok("[% n.split.join('|') %]" => "a|b|c", {n => "a b c"});
+process_ok("[% n.split(u,2).join('|') %]" => "a|b c", {n => "a b c", u => undef}) if ! $is_tt;
+process_ok("[% n.split(u,2).join('|') %]" => "a| b c", {n => "a b c", u => undef}) if $is_tt;
+process_ok("[% n.split('/').join('|') %]" => "a|b|c", {n => "a/b/c"});
+process_ok("[% n.split('/', 2).join('|') %]" => "a|b/c", {n => "a/b/c"});
+process_ok("[% n.stderr %]" => "", {n => "# testing stderr ... ok\r"});
+process_ok("[% n|trim %]" => "a  b", {n => '  a  b  '}); # TT2 filter
+process_ok("[% n|ucfirst %]" => 'Foo', {n => "foo"}); # TT2 filter
+process_ok("[% n|upper %]" => 'FOO', {n => "foo"}); # TT2 filter
+process_ok("[% n|uri %]" => 'a%20b', {n => "a b"}); # TT2 filter
+
+###----------------------------------------------------------------###
+print "### list vmethods ####################################################\n";
+
+process_ok("[% a.defined %]" => '1', {a => [2,3]});
+process_ok("[% a.defined(1) %]" => '1', {a => [2,3]});
+process_ok("[% a.defined(3) %]" => '', {a => [2,3]});
+process_ok("[% a.first %]" => '2', {a => [2..10]});
+process_ok("[% a.first(3).join %]" => '2 3 4', {a => [2..10]});
+process_ok("[% a.fmt %]" => '2 3', {a => [2,3]}) if ! $is_tt;
+process_ok("[% a.fmt('%02d') %]" => '02 03', {a => [2,3]}) if ! $is_tt;
+process_ok("[% a.fmt('%02d',' ') %]" => '02 03', {a => [2,3]}) if ! $is_tt;
+process_ok("[% a.fmt('%02d','|') %]" => '02|03', {a => [2,3]}) if ! $is_tt;
+process_ok("[% a.fmt('%0*d','|', 3) %]" => '002|003', {a => [2,3]}) if ! $is_tt;
+process_ok("[% a.grep.join %]" => '2 3', {a => [2,3]});
+process_ok("[% a.grep(2).join %]" => '2', {a => [2,3]});
+process_ok("[% a.hash.items.join %]" => '2 3', {a => [2,3]});
+process_ok("[% a.hash(5).items.sort.join %]" => '2 3 5 6', {a => [2,3]});
+process_ok("[% a.import(5) %]|[% a.join %]" => '|2 3', {a => [2,3]}) if ! $is_tt;
+process_ok("[% a.import(5) %]|[% a.join %]" => qr{^ARRAY.+|2 3$ }x, {a => [2,3]}) if $is_tt;
+process_ok("[% a.import([5]) %]|[% a.join %]" => '|2 3 5', {a => [2,3]}) if ! $is_tt;
+process_ok("[% a.import([5]) %]|[% a.join %]" => qr{ARRAY.+|2 3 5$ }x, {a => [2,3]}) if $is_tt;
+process_ok("[% a.item %]" => '2', {a => [2,3]});
+process_ok("[% a.item(1) %]" => '3', {a => [2,3]});
+process_ok("[% a.join %]" => '2 3', {a => [2,3]});
+process_ok("[% a.join('|') %]" => '2|3', {a => [2,3]});
+process_ok("[% a.last %]" => '10', {a => [2..10]});
+process_ok("[% a.last(3).join %]" => '8 9 10', {a => [2..10]});
+process_ok("[% a.list.join %]" => '2 3', {a => [2, 3]});
+process_ok("[% a.max %]" => '1', {a => [2, 3]});
+process_ok("[% a.merge(5).join %]" => '2 3', {a => [2,3]});
+process_ok("[% a.merge([5]).join %]" => '2 3 5', {a => [2,3]});
+process_ok("[% a.merge([5]).null %][% a.join %]" => '2 3', {a => [2,3]});
+process_ok("[% a.nsort.join %]" => '1 2 3', {a => [2, 3, 1]});
+process_ok("[% a.nsort('b').0.b %]" => '7', {a => [{b => 23}, {b => 7}]});
+process_ok("[% a.pop %][% a.join %]" => '32', {a => [2, 3]});
+process_ok("[% a.push(3) %][% a.join %]" => '2 3 3', {a => [2, 3]});
+process_ok("[% a.pick %]" => qr{ ^[23]$ }x, {a => [2, 3]}) if ! $is_tt;
+process_ok("[% a.pick(5).join('') %]" => qr{ ^[23]{5}$ }x, {a => [2, 3]}) if ! $is_tt;
+process_ok("[% a.reverse.join %]" => '3 2', {a => [2, 3]});
+process_ok("[% a.shift %][% a.join %]" => '23', {a => [2, 3]});
+process_ok("[% a.size %]" => '2', {a => [2, 3]});
+process_ok("[% a.slice.join %]" => '2 3 4 5', {a => [2..5]});
+process_ok("[% a.slice(2).join %]" => '4 5', {a => [2..5]});
+process_ok("[% a.slice(0,2).join %]" => '2 3 4', {a => [2..5]});
+process_ok("[% a.sort.join %]" => '1 2 3', {a => [2, 3, 1]});
+process_ok("[% a.sort('b').0.b %]" => 'wee', {a => [{b => "wow"}, {b => "wee"}]});
+process_ok("[% a.splice.join %]|[% a.join %]" => '2 3 4 5|', {a => [2..5]});
+process_ok("[% a.splice(2).join %]|[% a.join %]" => '4 5|2 3', {a => [2..5]});
+process_ok("[% a.splice(0,2).join %]|[% a.join %]" => '2 3|4 5', {a => [2..5]});
+process_ok("[% a.splice(0,2,'hrm').join %]|[% a.join %]" => '2 3|hrm 4 5', {a => [2..5]});
+process_ok("[% a.unique.join %]" => '2 3', {a => [2,3,3,3,2]});
+process_ok("[% a.unshift(3) %][% a.join %]" => '3 2 3', {a => [2, 3]});
+
+###----------------------------------------------------------------###
+print "### hash vmethods ####################################################\n";
+
+process_ok("[% h.defined %]" => "1", {h => {}});
+process_ok("[% h.defined('a') %]" => "1", {h => {a => 1}});
+process_ok("[% h.defined('b') %]" => "", {h => {a => 1}});
+process_ok("[% h.defined('a') %]" => "", {h => {a => undef}});
+process_ok("[% h.delete('a') %]|[% h.keys.0 %]" => "|b", {h => {a => 1, b=> 2}});
+process_ok("[% h.delete('a', 'b').join %]|[% h.keys.0 %]" => "|", {h => {a => 1, b=> 2}});
+process_ok("[% h.delete('a', 'c').join %]|[% h.keys.0 %]" => "|b", {h => {a => 1, b=> 2}});
+process_ok("[% h.each.sort.join %]" => "1 2 a b", {h => {a => 1, b=> 2}});
+process_ok("[% h.exists('a') %]" => "1", {h => {a => 1}});
+process_ok("[% h.exists('b') %]" => "", {h => {a => 1}});
+process_ok("[% h.exists('a') %]" => "1", {h => {a => undef}});
+process_ok("[% h.fmt %]" => "b\tB\nc\tC", {h => {b => "B", c => "C"}}) if ! $is_tt;
+process_ok("[% h.fmt('%s => %s') %]" => "b => B\nc => C", {h => {b => "B", c => "C"}}) if ! $is_tt;
+process_ok("[% h.fmt('%s => %s', '|') %]" => "b => B|c => C", {h => {b => "B", c => "C"}}) if ! $is_tt;
+process_ok("[% h.fmt('%*s=>%s', '|', 3) %]" => "  b=>B|  c=>C", {h => {b => "B", c => "C"}}) if ! $is_tt;
+process_ok("[% h.fmt('%*s=>%*s', '|', 3, 4) %]" => "  b=>   B|  c=>   C", {h => {b => "B", c => "C"}}) if ! $is_tt;
+process_ok("[% h.hash.fmt %]" => "b\tB\nc\tC", {h => {b => "B", c => "C"}}) if ! $is_tt;
+process_ok("[% h.import('a') %]|[% h.items.sort.join %]" => "|b B c C", {h => {b => "B", c => "C"}});
+process_ok("[% h.import({'b' => 'boo'}) %]|[% h.items.sort.join %]" => "|b boo c C", {h => {b => "B", c => "C"}});
+process_ok("[% h.item('a') %]" => 'A', {h => {a => 'A'}});
+process_ok("[% h.item('_a') %]" => '', {h => {_a => 'A'}}) if ! $is_tt;
+process_ok("[% h.items.sort.join %]" => "1 2 a b", {h => {a => 1, b=> 2}});
+process_ok("[% h.keys.sort.join %]" => "a b", {h => {a => 1, b=> 2}});
+process_ok("[% h.list('each').sort.join %]" => "1 2 a b", {h => {a => 1, b=> 2}});
+process_ok("[% h.list('keys').sort.join %]" => "a b", {h => {a => 1, b=> 2}});
+process_ok("[% h.list('pairs').0.items.sort.join %]" => "1 a key value", {h => {a => 1, b=> 2}});
+process_ok("[% h.list('values').sort.join %]" => "1 2", {h => {a => 1, b=> 2}});
+process_ok("[% h.null %]" => "", {h => {}});
+process_ok("[% h.nsort.join %]" => "b a", {h => {a => 7, b => 2}});
+process_ok("[% h.pairs.0.items.sort.join %]" => "1 a key value", {h => {a => 1, b=> 2}});
+process_ok("[% h.size %]" => "2", {h => {a => 1, b=> 2}});
+process_ok("[% h.sort.join %]" => "b a", {h => {a => "BBB", b => "A"}});
+process_ok("[% h.values.sort.join %]" => "1 2", {h => {a => 1, b=> 2}});
+
+###----------------------------------------------------------------###
+print "### more virtual methods / filters ###################################\n";
 
 process_ok("[% [0 .. 10].reverse.1 %]" => 9) if ! $is_tt;
 process_ok("[% {a => 'A'}.a %]" => 'A') if ! $is_tt;
@@ -308,21 +465,12 @@ process_ok("[% (-123.2).length %]" => 6) if ! $is_tt;
 process_ok("[% a = 23; a.0 %]" => 23) if ! $is_tt; # '0' is a scalar_op
 process_ok('[% 1.rand %]' => qr/^0\.\d+(?:e-?\d+)?$/) 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
 
@@ -345,8 +493,7 @@ process_ok('[% "hi" FILTER foo %]' => 'hihi', {tt_config => [FILTERS => {foo =>
 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]}]});
 
-process_ok('[% ["a".."z"].random %]' => qr/^[a-z]/) if ! $is_tt;
-process_ok('[% ["a".."z"].${ 26.rand } %]' => qr/^[a-z]/) if ! $is_tt;
+process_ok('[% ["a".."z"].pick %]' => qr/^[a-z]/) if ! $is_tt;
 
 process_ok("[% ' ' | uri %]" => '%20');
 
@@ -361,8 +508,16 @@ process_ok('[% {a => "B", c => "D"}.fmt %]' => "a\tB\nc\tD") if ! $is_tt;
 process_ok('[% {a => "B", c => "D"}.fmt("%s:%s") %]' => "a:B\nc:D") if ! $is_tt;
 process_ok('[% {a => "B", c => "D"}.fmt("%s:%s", "; ") %]' => "a:B; c:D") if ! $is_tt;
 
+process_ok('[% 1|format("%s") %]' => '1') if ! $is_tt;
+process_ok('[% 1|format("%*s", 6) %]' => '     1') if ! $is_tt;
+process_ok('[% 1|format("%-*s", 6) %]' => '1     ') if ! $is_tt;
+
+process_ok('[% 1.fmt("%-*s", 6) %]' => '1     ') if ! $is_tt;
+process_ok('[% [1,2].fmt("%-*s", "|", 6) %]' => '1     |2     ') if ! $is_tt;
+process_ok('[% {1=>2,3=>4}.fmt("%*s:%*s", "|", 3, 3) %]' => '  1:  2|  3:  4') if ! $is_tt;
+
 ###----------------------------------------------------------------###
-### virtual objects
+print "### virtual objects ##################################################\n";
 
 process_ok('[% a = "foobar" %][% Text.length(a) %]' => 6) if ! $is_tt;
 process_ok('[% a = [1 .. 10] %][% List.size(a) %]' => 10) if ! $is_tt;
@@ -374,10 +529,10 @@ process_ok('[% a = Hash.new("one", "ONE") %][% a.one %]' => 'ONE') if ! $is_tt;
 process_ok('[% a = Hash.new(one = "ONE") %][% a.one %]' => 'ONE') if ! $is_tt;
 process_ok('[% a = Hash.new(one => "ONE") %][% a.one %]' => 'ONE') if ! $is_tt;
 
-process_ok('[% {a => 1, b => 2} | Hash.keys | List.join(", ") %]' => 'a, b');
+process_ok('[% {a => 1, b => 2} | Hash.keys | List.join(", ") %]' => 'a, b') if ! $is_tt;
 
 ###----------------------------------------------------------------###
-### chomping
+print "### chomping #########################################################\n";
 
 process_ok(" [% foo %]" => ' ');
 process_ok(" [%- foo %]" => '');
@@ -388,24 +543,23 @@ 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 " => ' ');
-
+process_ok("[% 7 %] " => '7 ');
+process_ok("[% 7 -%] " => '7 ');
+process_ok("[% 7 -%]\n" => '7');
+process_ok("[% 7 -%] \n" => '7');
+process_ok("[% 7 -%]\n " => '7 ');
+process_ok("[% 7 -%]\n\n\n" => "7\n\n");
+process_ok("[% 7 -%] \n " => '7 ');
 
 ###----------------------------------------------------------------###
-### concat
+print "### string operators #################################################\n";
 
 process_ok('[% a = "foo"; a _ "bar" %]' => 'foobar');
 process_ok('[% a = "foo"; a ~ "bar" %]' => 'foobar') if ! $is_tt;
 process_ok('[% a = "foo"; a ~= "bar"; a %]' => 'foobar') if ! $is_tt;
 
 ###----------------------------------------------------------------###
-### math operations
+print "### math operators ###################################################\n";
 
 process_ok("[% 1 + 2 %]" => 3);
 process_ok("[% 1 + 2 + 3 %]" => 6);
@@ -454,7 +608,7 @@ process_ok('[% a++ FOR [1..3] %]' => '012') if ! $is_tt;
 process_ok('[% --a FOR [1..3] %]' => '-1-2-3') if ! $is_tt;
 
 ###----------------------------------------------------------------###
-### boolean operations
+print "### boolean operators ################################################\n";
 
 process_ok("[% 5 && 6 %]" => 6);
 process_ok("[% 5 || 6 %]" => 5);
@@ -487,27 +641,47 @@ 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
+print "### regex ############################################################\n";
+
+process_ok("[% /foo/ %]"     => '(?-xism:foo)') if ! $is_tt;
+process_ok("[% /foo %]"      => '') if ! $is_tt;
+process_ok("[% /foo/x %]"    => '(?-xism:(?x:foo))') if ! $is_tt;
+process_ok("[% /foo/xi %]"   => '(?-xism:(?xi:foo))') if ! $is_tt;
+process_ok("[% /foo/xis %]"  => '(?-xism:(?xis:foo))') if ! $is_tt;
+process_ok("[% /foo/xism %]" => '(?-xism:(?xism:foo))') if ! $is_tt;
+process_ok("[% /foo/e %]"    => '') if ! $is_tt;
+process_ok("[% /foo/g %]"    => '') if ! $is_tt;
+process_ok("[% /foo %]"      => '') if ! $is_tt;
+process_ok("[% /foo**/ %]"   => '') if ! $is_tt;
+process_ok("[% /fo\\/o/ %]"     => '(?-xism:fo/o)') if ! $is_tt;
+process_ok("[% 'foobar'.match(/(f\\w\\w)/).0 %]" => 'foo') if ! $is_tt;
 
-process_ok("[% PROCESS foo %]" => '');
-process_ok("[% BLOCK foo %]" => '');
-process_ok("[% BLOCK foo %][% END %]" => '');
+###----------------------------------------------------------------###
+print "### BLOCK / PROCESS / INCLUDE#########################################\n";
+
+process_ok("[% PROCESS foo %]one" => '');
+process_ok("[% BLOCK foo %]one" => '');
+process_ok("[% BLOCK foo %][% END %]one" => 'one');
 process_ok("[% BLOCK %][% END %]one" => 'one');
-process_ok("[% BLOCK foo %]hi there[% END %]" => '');
+process_ok("[% BLOCK foo %]hi there[% END %]one" => 'one');
 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 there[% END %][% PROCESS foo foo %]" => 'hi therehi there') if ! $is_tt;
+process_ok("[% BLOCK foo %]hi there[% END %][% PROCESS foo, foo %]" => 'hi therehi there') if ! $is_tt;
+process_ok("[% BLOCK foo %]hi there[% END %][% PROCESS foo + foo %]" => 'hi therehi 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 %][% BLOCK bar %]hi [% one %] there[% END %][% END %][% PROCESS foo/bar one => 'two' %]" => 'hi two there');
 
 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
+print "### IF / UNLESS / ELSIF / ELSE #######################################\n";
 
 process_ok("[% IF 1 %]Yes[% END %]" => 'Yes');
 process_ok("[% IF 0 %]Yes[% END %]" => '');
@@ -524,7 +698,7 @@ process_ok("[% UNLESS 1 %]Yes[% ELSIF 0 %]No[% END %]" => '');
 process_ok("[% UNLESS 1 %]Yes[% ELSIF 0 %]No[% ELSE %]hmm[% END %]" => 'hmm');
 
 ###----------------------------------------------------------------###
-### comments
+print "### comments #########################################################\n";
 
 process_ok("[%# one %]" => '', {one => 'ONE'});
 process_ok("[%#\n one %]" => '', {one => 'ONE'});
@@ -534,9 +708,13 @@ process_ok("[%# BLOCK one %]" => '');
 process_ok("[%# BLOCK one %]two" => 'two');
 process_ok("[%# BLOCK one %]two[% END %]" => '');
 process_ok("[%# BLOCK one %]two[% END %]three" => '');
+process_ok("[%
+#
+-%]
+foo" => "foo");
 
 ###----------------------------------------------------------------###
-### foreach, next, last
+print "### FOREACH / NEXT / LAST ############################################\n";
 
 process_ok("[% FOREACH foo %]" => '');
 process_ok("[% FOREACH foo %][% END %]" => '');
@@ -578,8 +756,13 @@ 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;
 
+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');
+
 ###----------------------------------------------------------------###
-### while
+print "### WHILE ############################################################\n";
 
 process_ok("[% WHILE foo %]" => '');
 process_ok("[% WHILE foo %][% END %]" => '');
@@ -598,7 +781,7 @@ process_ok("[% f = 10 a = 2; WHILE (g=f); f = f - 1 ; f ; a=3; END ; a%]" => '98
 process_ok("[% f = 10 a = 2; WHILE (a=f); f = f - 1 ; f ; a=3; END ; a%]" => '98765432100');
 
 ###----------------------------------------------------------------###
-### stop, return, clear
+print "### STOP / RETURN / CLEAR ############################################\n";
 
 process_ok("[% STOP %]" => '');
 process_ok("One[% STOP %]Two" => 'One');
@@ -621,16 +804,7 @@ process_ok("[% FOREACH f = [1..3] %][% f %][% IF loop.last %][% CLEAR %][% 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
+print "### post opererative directives ######################################\n";
 
 process_ok("[% GET foo IF 1 %]" => '1', {foo => 1});
 process_ok("[% f FOREACH f = [1..3] %]" => '123');
@@ -649,24 +823,48 @@ process_ok("[% FOREACH f = [1..3] IF 0 %]([% f %])[% END %]" => '')
 process_ok("[% BLOCK bar %][% foo %][% foo = foo - 1 %][% END %][% PROCESS bar WHILE foo %]" => '321', {foo => 3});
 
 ###----------------------------------------------------------------###
-### capturing
+print "### capturing ########################################################\n";
 
 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
+print "### TAGS #############################################################\n";
+
+process_ok("[% TAGS asp       %]<% 1 + 2 %>" => 3);
+process_ok("[% TAGS default   %][% 1 + 2 %]" => 3);
+process_ok("[% TAGS html      %]<!-- 1 + 2 -->" => '3');
+process_ok("[% TAGS mason     %]<% 1 + 2 >"  => 3);
+process_ok("[% TAGS metatext  %]%% 1 + 2 %%" => 3);
+process_ok("[% TAGS php       %]<? 1 + 2 ?>" => 3);
+process_ok("[% TAGS star      %][* 1 + 2 *]" => 3);
+process_ok("[% TAGS template  %][% 1 + 2 %]" => 3);
+process_ok("[% TAGS template1 %][% 1 + 2 %]" => 3);
+process_ok("[% TAGS template1 %]%% 1 + 2 %%" => 3);
+process_ok("[% TAGS tt2       %][% 1 + 2 %]" => 3);
 
-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 -->" => '3 3');
 process_ok("[% BLOCK foo %][% TAGS html %]<!-- 1 + 2 -->[% END %][% PROCESS foo %] [% 1 + 2 %]" => '');
 
+process_ok("[% TAGS <!-- --> %]<!-- 1 + 2 -->" => '3');
+
+process_ok("[% TAGS [<] [>]          %][<] 1 + 2 [>]" => 3);
+process_ok("[% TAGS '[<]' '[>]'      %][<] 1 + 2 [>]" => 3) if ! $is_tt;
+process_ok("[% TAGS /[<]/ /[>]/      %]<   1 + 2 >"  => 3) if ! $is_tt;
+process_ok("[% TAGS ** **            %]**  1 + 2 **" => 3);
+process_ok("[% TAGS '**' '**'        %]**  1 + 2 **" => 3) if ! $is_tt;
+process_ok("[% TAGS /**/ /**/        %]**  1 + 2 **" => "") if ! $is_tt;
+
+process_ok("[% TAGS html --><!-- 1 + 2 -->" => '3') if ! $is_tt;
+process_ok("[% TAGS html ; 7 --><!-- 1 + 2 -->" => '73') if ! $is_tt;
+process_ok("[% TAGS html ; 7 %]<!-- 1 + 2 -->" => '') if ! $is_tt; # error - the old closing tag must come next
+
 ###----------------------------------------------------------------###
-### switch
+print "### SWITCH / CASE ####################################################\n";
 
 process_ok("[% SWITCH 1 %][% END %]hi" => 'hi');
 process_ok("[% SWITCH 1 %][% CASE %]bar[% END %]hi" => 'barhi');
@@ -682,7 +880,7 @@ 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
+print "### TRY / THROW / CATCH / FINAL ######################################\n";
 
 process_ok("[% TRY %][% END %]hi" => 'hi');
 process_ok("[% TRY %]Foo[% END %]hi" => 'Foohi');
@@ -706,7 +904,7 @@ process_ok("[% TRY %][% THROW foo %][% CATCH %][% error.type %][% END %]" => 'un
 process_ok("[% TRY %][% THROW foo %][% CATCH %][% error.info %][% END %]" => 'foo');
 
 ###----------------------------------------------------------------###
-### named args
+print "### named args #######################################################\n";
 
 process_ok("[% foo(bar = 'one', baz = 'two') %]" => "baronebaztwo",
                {foo=>sub{my $n=$_[-1];join('',map{"$_$n->{$_}"} sort keys %$n)}});
@@ -714,7 +912,7 @@ process_ok("[%bar='ONE'%][% foo(\$bar = 'one') %]" => "ONEone",
                {foo=>sub{my $n=$_[-1];join('',map{"$_$n->{$_}"} sort keys %$n)}});
 
 ###----------------------------------------------------------------###
-### use
+print "### USE ##############################################################\n";
 
 my @config_p = (PLUGIN_BASE => 'MyTestPlugin', LOAD_PERL => 1);
 process_ok("[% USE son_of_gun_that_does_not_exist %]one" => '', {tt_config => \@config_p});
@@ -729,8 +927,11 @@ process_ok("[% USE d.d = Foo(bar = 'baz') %]one[% d.d.bar %]" => '', {tt_config
 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'},]});
 
+@config_p = (PLUGIN_BASE => ['NonExistant', 'MyTestPlugin'], LOAD_PERL => 1);
+process_ok("[% USE Foo %]one" => 'one', {tt_config => \@config_p});
+
 ###----------------------------------------------------------------###
-### macro
+print "### MACRO ############################################################\n";
 
 process_ok("[% MACRO foo PROCESS bar %][% BLOCK bar %]Hi[% END %][% foo %]" => 'Hi');
 process_ok("[% MACRO foo BLOCK %]Hi[% END %][% foo %]" => 'Hi');
@@ -740,8 +941,10 @@ process_ok("[%n=1%][% MACRO foo(n) BLOCK %]Hi[% n %][% END %][% foo(2) %][%n%]"
 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');
 
+process_ok('[% MACRO f BLOCK %]>[% TRY; f ; CATCH ;  "caught" ; END %][% END %][% f %]' => '>>>caught', {tt_config => [MAX_MACRO_RECURSE => 3]}) if ! $is_tt;
+
 ###----------------------------------------------------------------###
-### debug;
+print "### DEBUG ############################################################\n";
 
 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]});
@@ -755,7 +958,7 @@ process_ok("[% TRY %][% abc %][% CATCH %][% error %][% END %]" => "undef error -
 process_ok("[% TRY %][% abc.def %][% CATCH %][% error %][% END %]" => "undef error - def is undefined\n", {abc => {}, tt_config => ['DEBUG' => 2]});
 
 ###----------------------------------------------------------------###
-### constants
+print "### constants ########################################################\n";
 
 my @config_c = (
     CONSTANTS => {
@@ -771,7 +974,7 @@ my @config_c = (
         bam  => 'bar',
     },
 );
-process_ok("[% constants.harry %]" => 'do_this_once', {tt_config => \@config_c});
+process_ok("[% constants.harry %]" => 'do_this_once', {constants => {harry => 'foo'}, 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});
@@ -779,8 +982,13 @@ process_ok("[% constants.foo.\${constants.bang} %]" => '57', {tt_config => [@con
 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;
 
+process_ok('[% constants.${"harry"} %]' => 'do_this_once', {constants => {harry => 'foo'}, tt_config => \@config_c});
+process_ok('[% ${"constants"}.harry %]' => 'foo', {constants => {harry => 'foo'}, tt_config => \@config_c}) if ! $is_tt;
+process_ok('[% ${"constants"}.harry %]' => 'do_this_once', {constants => {harry => 'foo'}, tt_config => \@config_c}) if $is_tt;
+process_ok('[% ${"con${\\"s\\"}tants"}.harry %]' => 'foo', {constants => {harry => 'foo'}, tt_config => \@config_c}) if ! $is_tt;
+
 ###----------------------------------------------------------------###
-### interpolate / anycase / trim
+print "### INTERPOLATE / ANYCASE / TRIM #####################################\n";
 
 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]});
@@ -788,6 +996,9 @@ process_ok("[% TRY ; PERL %] my \$n=7; print \$n [% END ; END %]" => '7', {tt_co
 
 process_ok("[% GET %]" => '', {GET => 'ONE'});
 process_ok("[% GET GET %]" => 'ONE', {GET => 'ONE'}) if ! $is_tt;
+process_ok("[% get one %]" => 'ONE', {one => 'ONE', tt_config => ['ANYCASE' => 1]});
+process_ok("[% get %]" => '', {get => 'ONE', tt_config => ['ANYCASE' => 1]});
+process_ok("[% get get %]" => 'ONE', {get => 'ONE', tt_config => ['ANYCASE' => 1]}) 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]});
@@ -797,7 +1008,42 @@ process_ok("[% BLOCK foo %][% nl %]hi[% END %][% PROCESS foo %]" => "hi", {nl =>
 process_ok("A[% TRY %]\nhi\n[% END %]" => "A\nhi", {tt_config => [TRIM => 1]});
 
 ###----------------------------------------------------------------###
-### perl
+print "### V1DOLLAR #########################################################\n";
+
+process_ok('[% a %]|[% $a %]|[% ${ a } %]|[% ${ "a" } %]' => 'A|bar|bar|A', {a => 'A', A => 'bar'});
+process_ok('[% a %]|[% $a %]|[% ${ a } %]|[% ${ "a" } %]' => 'A|A|bar|A', {a => 'A', A => 'bar', tt_config => [V1DOLLAR => 1]});
+
+$vars = {a => {b => {c=>'Cb'}, B => {c=>'CB'}}, b => 'B', Cb => 'bar', CB => 'Bar'};
+process_ok('[% a.b.c %]|[% $a.b.c %]|[% a.$b.c %]|[% ${ a.b.c } %]' => 'Cb||CB|bar', $vars);
+process_ok('[% a.b.c %]|[% $a.b.c %]|[% a.$b.c %]|[% ${ a.b.c } %]' => 'Cb|Cb|Cb|bar', {%$vars, tt_config => [V1DOLLAR => 1]});
+
+process_ok('[% "$a" %]|$a|[% "${a}" %]|${a}' => 'A|$a|A|${a}', {a => 'A', A => 'bar'});
+process_ok('[% "$a" %]|$a|[% "${a}" %]|${a}' => 'A|$a|A|${a}', {a => 'A', A => 'bar', tt_config => [V1DOLLAR => 1]});
+process_ok('[% "$a" %]|$a|[% "${a}" %]|${a}' => 'A|A|A|A',     {a => 'A', A => 'bar', tt_config => [INTERPOLATE => 1]});
+process_ok('[% "$a" %]|$a|[% "${a}" %]|${a}' => 'A|A|A|A',     {a => 'A', A => 'bar', tt_config => [V1DOLLAR => 1, INTERPOLATE => 1]});
+
+process_ok('[% constants.a %]|[% $constants.a %]|[% constants.$a %]' => 'A|A|A', {tt_config => [V1DOLLAR => 1, CONSTANTS => {a => 'A'}]});
+
+###----------------------------------------------------------------###
+print "### V2PIPE ###########################################################\n";
+
+process_ok("[%- BLOCK a %]b is [% b %]
+[% END %]
+[%- PROCESS a b => 237 | repeat(2) %]" => "b is 237
+b is 237\n", {tt_config => [V2PIPE => 1]});
+
+process_ok("[%- BLOCK a %]b is [% b %]
+[% END %]
+[%- PROCESS a b => 237 | repeat(2) %]" => "b is 237237\n") if ! $is_tt;
+
+###----------------------------------------------------------------###
+print "### configuration ####################################################\n";
+
+process_ok('[% a = 7 %]$a' => 7, {tt_config => ['INTERPOLATE' => 1]});
+process_ok('[% a = 7 %]$a' => 7, {tt_config => ['interpolate' => 1]}) if ! $is_tt;
+
+###----------------------------------------------------------------###
+print "### PERL #############################################################\n";
 
 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]});
@@ -805,13 +1051,162 @@ process_ok("[% PERL %] print \$stash->get('one') [% END %]" => 'ONE', {one => 'O
 process_ok("[% PERL %] print \$stash->set('a.b.c', 7) [% END %][% a.b.c %]" => '77', {tt_config => ['EVAL_PERL' => 1]});
 
 ###----------------------------------------------------------------###
-### recursion prevention
+print "### recursion prevention #############################################\n";
 
 process_ok("[% BLOCK foo %][% PROCESS bar %][% END %][% BLOCK bar %][% PROCESS foo %][% END %][% PROCESS foo %]" => '') if ! $is_tt;
 
 ###----------------------------------------------------------------###
-### META
+print "### META #############################################################\n";
 
 process_ok("[% template.name %]" => 'input text');
 process_ok("[% META foo = 'bar' %][% template.foo %]" => 'bar');
+process_ok("[% META name = 'bar' %][% template.name %]" => 'bar');
 process_ok("[% META foo = 'bar' %][% component.foo %]" => 'bar');
+process_ok("[% META foo = 'bar' %][% component = '' %][% component.foo %]|foo" => '|foo');
+process_ok("[% META foo = 'bar' %][% template = '' %][% template.foo %]|foo" => '|foo');
+
+###----------------------------------------------------------------###
+print "### references #######################################################\n";
+
+process_ok("[% a=3; b=\\a; b; a %]" => 33);
+process_ok("[% a=3; b=\\a; a=7; b; a %]" => 77);
+
+process_ok("[% a={}; a.1=7; b=\\a.1; b; a.1 %]" => '77');
+process_ok("[% a={}; a.1=7; b=\\a.20; a.20=7; b; a.20 %]" => '77');
+
+process_ok("[% a=[]; a.1=7; b=\\a.1; b; a.1 %]" => '77');
+process_ok("[% a=[]; a.1=7; b=\\a.20; a.20=7; b; a.20 %]" => '77');
+
+process_ok("[% \\a %]" => qr/^CODE/, {a => sub { return "a sub [@_]" } });
+process_ok("[% b=\\a; b %]" => 'a sub []', {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');
+
+process_ok('[% a = "ab" ; f = "abcd"; foo = \f.replace(a, "-AB-") ; f = "ab"; foo %]' => '-AB-cd');
+process_ok('[% a = "ab" ; f = "abcd"; foo = \f.replace(a, "-AB-").replace("-AB-", "*") ; f = "ab"; foo %]' => '*cd');
+
+###----------------------------------------------------------------###
+print "### reserved words ###################################################\n";
+
+$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;
+
+###----------------------------------------------------------------###
+print "### embedded items ###################################################\n";
+
+process_ok('[% " \" " %]' => ' " ');
+process_ok('[% " \$foo " %]' => ' $foo ');
+process_ok('[% " \${foo} " %]' => ' ${foo} ');
+process_ok('[% " \n " %]' => " \n ");
+process_ok('[% " \t " %]' => " \t ");
+process_ok('[% " \r " %]' => " \r ");
+
+process_ok("[% 'foo\\'bar' %]"  => "foo'bar");
+process_ok('[% "foo\\"bar" %]'  => 'foo"bar');
+process_ok('[% qw(foo \)).1 %]' => ')') if ! $is_tt;
+process_ok('[% qw|foo \||.1 %]' => '|') if ! $is_tt;
+
+process_ok("[% ' \\' ' %]" => " ' ");
+process_ok("[% ' \\r ' %]" => ' \r ');
+process_ok("[% ' \\n ' %]" => ' \n ');
+process_ok("[% ' \\t ' %]" => ' \t ');
+process_ok("[% ' \$foo ' %]" => ' $foo ');
+
+process_ok('[% A = "bar" ; ${ "A" } %]' => 'bar');
+process_ok('[% A = "bar" ; "(${ A })" %]' => '(bar)');
+process_ok('[% A = "bar" ; ${ {a => "A"}.a } %]' => 'bar') if ! $is_tt;
+process_ok('[% A = "bar" ; "(${ {a => \"A\"\\}.a })" %]' => '(A)') if ! $is_tt;
+process_ok('[% A = "bar" ; "(${ \\${ {a => \"A\"\\}.a \\} })" %]' => '(bar)') if ! $is_tt;
+process_ok('[% A = "bar" %](${ {a => \"A\"\\}.a })' => '(A)', {tt_config => [INTERPOLATE => 1]}) if ! $is_tt;
+process_ok('[% A = "bar" %](${ \\${ {a => \"A\"\\}.a \\} })' => '(bar)', {tt_config => [INTERPOLATE => 1]}) if ! $is_tt;
+
+process_ok('[% "[%" %]' => '[%') if ! $is_tt;
+process_ok('[% "%]" %]' => '%]') if ! $is_tt;
+process_ok('[% a = "[%  %]" %][% a %]' => '[%  %]') if ! $is_tt;
+process_ok('[% "[% 1 + 2 %]" | eval %]' => '3') if ! $is_tt;
+
+process_ok('[% qw([%  1  +  2  %]).join %]' => '[% 1 + 2 %]') if ! $is_tt;
+process_ok('[% qw([%  1  +  2  %]).join.eval %]' => '3') if ! $is_tt;
+
+process_ok('[% f = ">[% TRY; f.eval ; CATCH; \'caught\' ; END %]"; f.eval %]' => '>>>>>caught', {tt_config => [MAX_EVAL_RECURSE => 5]}) if ! $is_tt;
+process_ok('[% f = ">[% TRY; f.eval ; CATCH; \'foo\' ; END %]"; f.eval;f.eval %]' => '>>foo>>foo', {tt_config => [MAX_EVAL_RECURSE => 2]}) if ! $is_tt;
+
+###----------------------------------------------------------------###
+print "### DUMP #############################################################\n";
+
+if (! $is_tt) {
+local $ENV{'REQUEST_METHOD'} = 0;
+process_ok("[% DUMP a %]" => "DUMP: File \"input text\" line 1\n    a = undef;\n");
+process_ok("[% p = DUMP a; p.collapse %]" => 'DUMP: File "input text" line 1 a = undef;');
+process_ok("[% p = DUMP a; p.collapse %]" => 'DUMP: File "input text" line 1 a = \'s\';', {a => "s"});
+process_ok("[%\n p = DUMP a; p.collapse %]" => 'DUMP: File "input text" line 2 a = \'s\';', {a => "s"});
+process_ok("[% p = DUMP a, b; p.collapse %]" => 'DUMP: File "input text" line 1 a, b = [ \'s\', undef ];', {a => "s"});
+process_ok("[% p = DUMP a Useqq => 'b'; p.collapse %]" => 'DUMP: File "input text" line 1 a Useqq => \'b\' = [ \'s\', { \'Useqq\' => \'b\' } ];', {a => "s"});
+process_ok("[% p = DUMP a; p.collapse %]" => 'DUMP: File "input text" line 1 a = "s";', {a => "s", tt_config => [DUMP => {Useqq => 1}]});
+process_ok("[% p = DUMP a; p.collapse %]|foo" => '|foo', {a => "s", tt_config => [DUMP => 0]});
+process_ok("[% p = DUMP _a, b; p.collapse %]" => 'DUMP: File "input text" line 1 _a, b = [ undef, \'c\' ];', {_a => "s", b=> "c"});
+process_ok("[% p = DUMP {a => 'b'}; p.collapse %]" => 'DUMP: File "input text" line 1 {a => \'b\'} = { \'a\' => \'b\' };');
+process_ok("[% p = DUMP _a; p.collapse %]" => 'DUMP: File "input text" line 1 _a = undef;', {_a => "s"});
+process_ok("[% p = DUMP a; p.collapse %]" => 'DUMP: File "input text" line 1 a = { \'b\' => \'c\' };', {a => {b => 'c'}});
+process_ok("[% p = DUMP a; p.collapse %]" => 'DUMP: File "input text" line 1 a = {};', {a => {_b => 'c'}});
+process_ok("[% p = DUMP a; p.collapse %]" => 'DUMP: File "input text" line 1 a = {};', {a => {_b => 'c'}, tt_config => [DUMP => {Sortkeys => 1}]});
+process_ok("[% p = DUMP a; p.collapse %]" => 'DUMP: File "input text" line 1 Dump(7)', {a => 7, tt_config => [DUMP => {handler=>sub {"Dump(@_)"}}]});
+process_ok("[% p = DUMP a; p.collapse %]" => 'a = \'s\';', {a => "s", tt_config => [DUMP => {header => 0}]});
+process_ok("[% p = DUMP a; p.collapse %]" => '<pre>a = &apos;s&apos;; </pre>', {a => "s", tt_config => [DUMP => {header => 0, html => 1}]});
+local $ENV{'REQUEST_METHOD'} = 1;
+process_ok("[% p = DUMP a; p.collapse %]" => '<pre>a = &apos;s&apos;; </pre>', {a => "s", tt_config => [DUMP => {header => 0}]});
+process_ok("[% p = DUMP a; p.collapse %]" => 'a = \'s\';', {a => "s", tt_config => [DUMP => {header => 0, html => 0}]});
+local $ENV{'REQUEST_METHOD'} = 0;
+process_ok("[% SET global; p = DUMP; p.collapse %]" => "DUMP: File \"input text\" line 1 EntireStash = { 'a' => 'b', 'global' => '' };", {a => 'b', tt_config => [DUMP => {Sortkeys => 1}]});
+process_ok("[% SET global; p = DUMP; p.collapse %]" => "DUMP: File \"input text\" line 1 EntireStash = { 'a' => 'b', 'global' => '' };", {a => 'b', tt_config => [DUMP => {Sortkeys => 1, EntireStash => 1}]});
+process_ok("[% SET global; p = DUMP; p.collapse %]" => "DUMP: File \"input text\" line 1", {a => 'b', tt_config => [DUMP => {Sortkeys => 1, EntireStash => 0}]});
+}
+
+###----------------------------------------------------------------###
+print "### CONFIG ############################################################\n";
+
+if (! $is_tt) {
+process_ok("[% CONFIG ANYCASE     => 1   %][% get 234 %]" => 234);
+process_ok("[% CONFIG anycase     => 1   %][% get 234 %]" => 234);
+process_ok("[% CONFIG PRE_CHOMP   => '-' %]\n[% 234 %]" => 234);
+process_ok("[% CONFIG POST_CHOMP  => '-' %][% 234 %]\n" => 234);
+process_ok("[% CONFIG INTERPOLATE => '-' %]\${ 234 }"   => 234);
+process_ok("[% CONFIG V1DOLLAR    => 1   %][% a = 234 %][% \$a %]"   => 234);
+process_ok("[% CONFIG V2PIPE => 1 %][% BLOCK a %]b is [% b %][% END %][% PROCESS a b => 234 | repeat(2) %]"   => "b is 234b is 234");
+
+process_ok("[% CONFIG BOGUS => 2 %]bar" => '');
+
+process_ok("[% CONFIG ANYCASE %]|[% CONFIG ANYCASE => 1 %][% CONFIG ANYCASE %]" => 'CONFIG ANYCASE = undef|CONFIG ANYCASE = 1');
+process_ok("[% CONFIG ANYCASE %]|[% CONFIG ANYCASE => 1 %][% CONFIG ANYCASE %]" => 'CONFIG ANYCASE = undef|CONFIG ANYCASE = 1');
+
+process_ok("[% CONFIG DUMP    %]|[% CONFIG DUMP    => 0 %][% DUMP           %]bar" => 'CONFIG DUMP = undef|bar');
+process_ok("[% CONFIG DUMP => {Useqq=>1, header=>0, html=>0} %][% DUMP 'foo' %]" => "'foo' = \"foo\";\n");
+}
+
+###----------------------------------------------------------------###
+print "### DONE #############################################################\n";
This page took 0.045344 seconds and 4 git commands to generate.