diff --git a/Changes b/Changes index 00d839e..4cdcefc 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,10 @@ +--- +version: 0.24_4 +date: Saturday January 09 17:06:40 PST 2010 +changes: +- added .pairs (VMethod) functionality with tests +- fixed quoted.t failing test + --- version: 0.24_3 date: Mon Jun 8 2009 diff --git a/Makefile.PL b/Makefile.PL index 728ca7f..423d3c4 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -18,6 +18,10 @@ all_from 'lib/Jemplate.pm'; #build_requires 'Directory::Scratch'; +if (-e 'inc/.author') { + my $all_from = join '/', 'lib', split m/-/, name . '.pm'; + `perldoc -tF $all_from > README` if ! -e 'README' || (stat $all_from)[9] > (stat 'README')[9]; +} requires 'Template' => '2.19'; requires 'File::Find::Rule' => '0.30'; diff --git a/README b/README index c4e7eda..0a21aca 100644 --- a/README +++ b/README @@ -1,3 +1,6 @@ +VERSION + Version 0.24_4 + NAME Jemplate - JavaScript Templating with Template Toolkit @@ -161,12 +164,13 @@ CURRENT SUPPORT * [% LAST %] * [% CLEAR %] * [%# this is a comment %] + * [% MACRO name(param1, param2) BLOCK %] ... [% END %] ALL of the string virtual functions are supported. ALL of the array virtual functions are supported: - ALL of the hash virtual functions are supported (except for import): + ALL of the hash virtual functions are supported: MANY of the standard filters are implemented. @@ -185,8 +189,8 @@ BROWSER SUPPORT All tests run 100% successful in the above browsers. DEVELOPMENT - The bleeding edge code is available via Subversion at - http://svn.jemplate.net/repo/trunk/ + The bleeding edge code is available via Git at + git://github.com/ingydotnet/jemplate.git You can run the runtime tests directly from http://svn.jemplate.net/repo/trunk/tests/run/index.html or from the @@ -229,6 +233,8 @@ AUTHORS Robert Krimen + Nickolay Platonov + COPYRIGHT Copyright (c) 2006-2008. Ingy döt Net. diff --git a/bin/jemplate b/bin/jemplate index dcdb0bf..3dd6ed4 100755 --- a/bin/jemplate +++ b/bin/jemplate @@ -182,15 +182,13 @@ BEGIN { package File::Find::Rule; use strict; -use vars qw/$VERSION $AUTOLOAD/; use File::Spec; use Text::Glob 'glob_to_regex'; use Number::Compare; use Carp qw/croak/; use File::Find (); # we're only wrapping for now -use Cwd; # 5.00503s File::Find goes screwy with max_depth == 0 -$VERSION = '0.30'; +our $VERSION = '0.32'; sub import { my $pkg = shift; @@ -245,8 +243,8 @@ sub new { my $referent = shift; my $class = ref $referent || $referent; bless { - rules => [], # [0] - subs => [], # [1] + rules => [], + subs => {}, iterator => [], extras => {}, maxdepth => undef, @@ -349,15 +347,15 @@ use vars qw( @stat_tests ); sub any { my $self = _force_object shift; - my @rulesets = @_; - + # compile all the subrules to code fragments push @{ $self->{rules} }, { - rule => 'any', - code => '(' . join( ' || ', map { - "( " . $_->_compile( $self->{subs} ) . " )" - } @_ ) . ")", + rule => "any", + code => '(' . join( ' || ', map '( ' . $_->_compile . ' )', @_ ). ')', args => \@_, }; + + # merge all the subs hashes of the kids into ourself + %{ $self->{subs} } = map { %{ $_->{subs} } } $self, @_; $self; } @@ -366,15 +364,15 @@ sub any { sub not { my $self = _force_object shift; - my @rulesets = @_; push @{ $self->{rules} }, { rule => 'not', - args => \@rulesets, - code => '(' . join ( ' && ', map { - "!(". $_->_compile( $self->{subs} ) . ")" - } @_ ) . ")", + args => \@_, + code => '(' . join ( ' && ', map { "!(". $_->_compile . ")" } @_ ) . ")", }; + + # merge all the subs hashes into us + %{ $self->{subs} } = map { %{ $_->{subs} } } $self, @_; $self; } @@ -465,6 +463,7 @@ sub relative () { sub DESTROY {} sub AUTOLOAD { + our $AUTOLOAD; $AUTOLOAD =~ /::not_([^:]*)$/ or croak "Can't locate method $AUTOLOAD"; my $method = $1; @@ -485,8 +484,8 @@ sub in { my $self = _force_object shift; my @found; - my $fragment = $self->_compile( $self->{subs} ); - my @subs = @{ $self->{subs} }; + my $fragment = $self->_compile; + my %subs = %{ $self->{subs} }; warn "relative mode handed multiple paths - that's a bit silly\n" if $self->{relative} && @_ > 1; @@ -526,11 +525,10 @@ sub in { }'; #use Data::Dumper; - #print Dumper \@subs; + #print Dumper \%subs; #warn "Compiled sub: '$code'\n"; my $sub = eval "$code" or die "compile error '$code' $@"; - my $cwd = getcwd; for my $path (@_) { # $topdir is used for relative and maxdepth $topdir = $path; @@ -540,7 +538,6 @@ sub in { unless $topdir eq '/'; $self->_call_find( { %{ $self->{extras} }, wanted => $sub }, $path ); } - chdir $cwd; return @found; } @@ -552,19 +549,20 @@ sub _call_find { sub _compile { my $self = shift; - my $subs = shift; # [1] return '1' unless @{ $self->{rules} }; my $code = join " && ", map { if (ref $_->{code}) { - push @$subs, $_->{code}; - "\$subs[$#{$subs}]->(\@args) # $_->{rule}\n"; + my $key = "$_->{code}"; + $self->{subs}{$key} = $_->{code}; + "\$subs{'$key'}->(\@args) # $_->{rule}\n"; } else { "( $_->{code} ) # $_->{rule}\n"; } } @{ $self->{rules} }; + #warn $code; return $code; } @@ -597,15 +595,14 @@ BEGIN { package Template::Constants; require Exporter; - use strict; use warnings; -use base 'Exporter'; - +use Exporter; use vars qw( @EXPORT_OK %EXPORT_TAGS ); -use vars qw( $DEBUG_OPTIONS @STATUS @ERROR @CHOMP @DEBUG); +use vars qw( $DEBUG_OPTIONS @STATUS @ERROR @CHOMP @DEBUG @ISA ); +@ISA = qw( Exporter ); -our $VERSION = 2.74; +our $VERSION = 2.75; @@ -739,7 +736,7 @@ use strict; use warnings; use Template::Constants; -our $VERSION = 2.77; +our $VERSION = 2.78; @@ -747,7 +744,8 @@ sub new { my $class = shift; my ($argnames, @args, $arg, $cfg); - { no strict qw( refs ); + { no strict 'refs'; + no warnings 'once'; $argnames = \@{"$class\::BASEARGS"} || [ ]; } @@ -759,10 +757,10 @@ sub new { } # fold all remaining args into a hash, or use provided hash ref - $cfg = defined $_[0] && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ }; + $cfg = defined $_[0] && ref($_[0]) eq 'HASH' ? shift : { @_ }; my $self = bless { - map { ($_ => shift @args) } @$argnames, + (map { ($_ => shift @args) } @$argnames), _ERROR => '', DEBUG => 0, }, $class; @@ -797,11 +795,6 @@ sub _init { } -sub DEBUG { - my $self = shift; - print STDERR "DEBUG: ", @_; -} - sub debug { my $self = shift; my $msg = join('', @_); @@ -847,7 +840,7 @@ use vars qw( $VERSION $DEBUG $ERROR $INSTDIR $LATEX_PATH $PDFLATEX_PATH $DVIPS_PATH $STASH $SERVICE $CONTEXT $CONSTANTS @PRELOAD ); -$VERSION = 2.74; +$VERSION = 2.75; $DEBUG = 0 unless defined $DEBUG; $ERROR = ''; $CONTEXT = 'Template::Context'; @@ -883,9 +876,7 @@ sub load { my ($class, $module) = @_; $module =~ s[::][/]g; $module .= '.pm'; - eval { - require $module; - }; + eval { require $module; }; return $@ ? $class->error("failed to load $module: $@") : 1; } @@ -893,20 +884,20 @@ sub load { sub parser { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($PARSER); return $PARSER->new($params) - || $class->error("failed to create parser: ", $PARSER->error); + || $class->error("failed to create parser: ", $PARSER->error); } sub provider { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($PROVIDER); return $PROVIDER->new($params) @@ -918,26 +909,26 @@ sub provider { sub plugins { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($PLUGINS); return $PLUGINS->new($params) - || $class->error("failed to create plugin provider: ", - $PLUGINS->error); + || $class->error("failed to create plugin provider: ", + $PLUGINS->error); } sub filters { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($FILTERS); return $FILTERS->new($params) - || $class->error("failed to create filter provider: ", - $FILTERS->error); + || $class->error("failed to create filter provider: ", + $FILTERS->error); } @@ -948,56 +939,56 @@ sub iterator { return undef unless $class->load($ITERATOR); return $ITERATOR->new($list, @_) - || $class->error("failed to create iterator: ", $ITERATOR->error); + || $class->error("failed to create iterator: ", $ITERATOR->error); } sub stash { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($STASH); return $STASH->new($params) - || $class->error("failed to create stash: ", $STASH->error); + || $class->error("failed to create stash: ", $STASH->error); } sub context { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($CONTEXT); return $CONTEXT->new($params) - || $class->error("failed to create context: ", $CONTEXT->error); + || $class->error("failed to create context: ", $CONTEXT->error); } sub service { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($SERVICE); return $SERVICE->new($params) - || $class->error("failed to create context: ", $SERVICE->error); + || $class->error("failed to create context: ", $SERVICE->error); } sub constants { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($CONSTANTS); return $CONSTANTS->new($params) - || $class->error("failed to create constants namespace: ", - $CONSTANTS->error); + || $class->error("failed to create constants namespace: ", + $CONSTANTS->error); } @@ -1005,7 +996,7 @@ sub constants { sub instdir { my ($class, $dir) = @_; my $inst = $INSTDIR - || return $class->error("no installation directory"); + || return $class->error("no installation directory"); $inst =~ s[/$][]g; $inst .= "/$dir" if $dir; return $inst; @@ -1143,7 +1134,7 @@ sub process { die $context->catch($@) if $@; - + return $output; } @@ -1170,9 +1161,9 @@ sub _dump { $output .= "BLOCK: $self->{ _BLOCK }\nDEFBLOCKS:\n"; if ($dblks = $self->{ _DEFBLOCKS }) { - foreach my $b (keys %$dblks) { - $output .= " $b: $dblks->{ $b }\n"; - } + foreach my $b (keys %$dblks) { + $output .= " $b: $dblks->{ $b }\n"; + } } return $output; @@ -1185,12 +1176,12 @@ sub as_perl { my ($class, $content) = @_; my ($block, $defblocks, $metadata) = @$content{ qw( BLOCK DEFBLOCKS METADATA ) }; - $block =~ s/\n/\n /g; + $block =~ s/\n(?!#line)/\n /g; $block =~ s/\s+$//; $defblocks = join('', map { my $code = $defblocks->{ $_ }; - $code =~ s/\n/\n /g; + $code =~ s/\n(?!#line)/\n /g; $code =~ s/\s*$//; " '$_' => $code,\n"; } keys %$defblocks); @@ -1224,7 +1215,7 @@ sub write_perl_file { my ($fh, $tmpfile); return $class->error("invalid filename: $file") - unless $file =~ /^(.+)$/s; + unless $file =~ /^(.+)$/s; eval { require File::Temp; @@ -1273,7 +1264,7 @@ use constant INFO => 1; use constant TEXT => 2; use overload q|""| => "as_string", fallback => 1; -our $VERSION = 2.69; +our $VERSION = 2.70; @@ -1303,16 +1294,15 @@ sub text { my $textref = $self->[ TEXT ]; if ($newtextref) { - $$newtextref .= $$textref if $textref && $textref ne $newtextref; - $self->[ TEXT ] = $newtextref; - return ''; - + $$newtextref .= $$textref if $textref && $textref ne $newtextref; + $self->[ TEXT ] = $newtextref; + return ''; } elsif ($textref) { - return $$textref; + return $$textref; } else { - return ''; + return ''; } } @@ -1332,11 +1322,11 @@ sub select_handler { @hlut{ @options } = (1) x @options; while ($type) { - return $type if $hlut{ $type }; + return $type if $hlut{ $type }; - # strip .element from the end of the exception type to find a - # more generic handler - $type =~ s/\.?[^\.]*$//; + # strip .element from the end of the exception type to find a + # more generic handler + $type =~ s/\.?[^\.]*$//; } return undef; } @@ -1360,6 +1350,9 @@ use base 'Template::Base'; use Template::Config; use Template::Exception; use Template::Constants; +use Scalar::Util 'blessed'; + +use constant EXCEPTION => 'Template::Exception'; our $VERSION = 2.80; our $DEBUG = 0 unless defined $DEBUG; @@ -1390,6 +1383,7 @@ sub process { # localise the variable stash with any parameters passed # and set the 'template' variable $params ||= { }; + # TODO: change this to C<||=> so we can use a template parameter $params->{ template } = $template unless ref $template eq 'CODE'; $context->localise($params); @@ -1500,7 +1494,7 @@ sub _recover { # point... unless a module like CGI::Carp messes around with the # DIE handler. return undef - unless UNIVERSAL::isa($$error, 'Template::Exception'); + unless blessed($$error) && $$error->isa(EXCEPTION); # a 'stop' exception is thrown by [% STOP %] - we return the output # buffer stored in the exception object @@ -1602,7 +1596,7 @@ use constant LOAD => 3; # mtime of template use constant NEXT => 4; # link to next item in cache linked list use constant STAT => 5; # Time last stat()ed -our $VERSION = 2.93; +our $VERSION = 2.94; our $DEBUG = 0 unless defined $DEBUG; our $ERROR = ''; @@ -1711,7 +1705,7 @@ sub load { || return ($self->error(), Template::Constants::STATUS_ERROR); foreach my $dir (@$paths) { - $path = "$dir/$name"; + $path = File::Spec->catfile($dir, $name); last INCPATH if $self->_template_modified($path); } @@ -1768,7 +1762,7 @@ sub paths { unshift(@ipaths, @$dpaths); next; } - elsif (UNIVERSAL::can($dir, 'paths')) { + elsif (ref($dir) && UNIVERSAL::can($dir, 'paths')) { $dpaths = $dir->paths() || return $self->error($dir->error()); unshift(@ipaths, @$dpaths); @@ -1852,8 +1846,8 @@ sub _init { my $wdir = $dir; $wdir =~ s[:][]g if $^O eq 'MSWin32'; $wdir =~ /(.*)/; # untaint - $wdir = $1; - $wdir = File::Spec->catfile($cdir, $1); + $wdir = "$1"; # quotes work around bug in Strawberry Perl + $wdir = File::Spec->catfile($cdir, $wdir); File::Path::mkpath($wdir) unless -d $wdir; } } @@ -1927,19 +1921,26 @@ sub _fetch { warn($self->error(), "\n"); } - # Now fetch template from source, compile, and cache. + # load template from source my ($template, $error) = $self->_load($name, $t_name); - unless ($error) { - ($template, $error) = $self->_compile($template, $self->_compiled_filename($name) ); - # Store compiled template and return it - return $self->store( $name, $template->{data} ) unless $error; + if ($error) { + # Template could not be fetched. Add to the negative/notfound cache. + $self->{ NOTFOUND }->{ $name } = time; + return ( $template, $error ); } - # Template could not be fetched. Add to the negative/notfound cache. - $self->{ NOTFOUND }->{ $name } = time; + # compile template source + ($template, $error) = $self->_compile($template, $self->_compiled_filename($name) ); - return ( $template, $error ); + if ($error) { + # return any compile time error + return ($template, $error); + } + else { + # Store compiled template and return it + return $self->store($name, $template->{data}) ; + } } @@ -2334,6 +2335,7 @@ sub _template_content { local *FH; if (open(FH, "< $path")) { local $/; + binmode(FH); $data = ; $mod_date = (stat($path))[9]; close(FH); @@ -2428,6 +2430,7 @@ sub _dump_cache { sub _decode_unicode { my $self = shift; my $string = shift; + return undef unless defined $string; use bytes; require Encode; @@ -2471,6 +2474,7 @@ package Template; use strict; use warnings; +use 5.006; use base 'Template::Base'; use Template::Config; @@ -2479,8 +2483,9 @@ use Template::Provider; use Template::Service; use File::Basename; use File::Path; +use Scalar::Util qw(blessed); -our $VERSION = '2.19'; +our $VERSION = '2.22'; our $ERROR = ''; our $DEBUG = 0; our $BINMODE = 0 unless defined $BINMODE; @@ -2493,7 +2498,7 @@ Template::Config->preload() if $ENV{ MOD_PERL }; sub process { my ($self, $template, $vars, $outstream, @opts) = @_; my ($output, $error); - my $options = (@opts == 1) && UNIVERSAL::isa($opts[0], 'HASH') + my $options = (@opts == 1) && ref($opts[0]) eq 'HASH' ? shift(@opts) : { @opts }; $options->{ binmode } = $BINMODE @@ -2510,7 +2515,7 @@ sub process { unless (ref $outstream) { my $outpath = $self->{ OUTPUT_PATH }; $outstream = "$outpath/$outstream" if $outpath; - } + } # send processed template to output stream, checking for error return ($self->error($error)) @@ -2591,7 +2596,7 @@ sub _output { } # call the print() method on an object that implements the method # (e.g. IO::Handle, Apache::Request, etc) - elsif (UNIVERSAL::can($where, 'print')) { + elsif (blessed($where) && $where->can('print')) { $where->print($$textref); } # a simple string is taken as a filename @@ -7952,18 +7957,14 @@ sub [#Rule 39 'atomdir', 1, sub -{ $_[0]->{ INFOR } || $_[0]->{ INWHILE } - ? 'last LOOP;' - : 'last;' } +{ $_[0]->block_label('last ', ';') } ], [#Rule 40 'atomdir', 1, sub -{ $_[0]->{ INFOR } - ? $factory->next() - : ($_[0]->{ INWHILE } - ? 'next LOOP;' - : 'next;') } +{ $_[0]->in_block('FOR') + ? $factory->next($_[0]->block_label) + : $_[0]->block_label('next ', ';') } ], [#Rule 41 'atomdir', 2, @@ -8048,13 +8049,12 @@ sub [#Rule 56 '@1-3', 0, sub -{ $_[0]->{ INFOR }++ } +{ $_[0]->enter_block('FOR') } ], [#Rule 57 'loop', 6, sub -{ $_[0]->{ INFOR }--; - $factory->foreach(@{$_[2]}, $_[5]) } +{ $factory->foreach(@{$_[2]}, $_[5], $_[0]->leave_block) } ], [#Rule 58 'loop', 3, @@ -8064,18 +8064,17 @@ sub [#Rule 59 '@2-3', 0, sub -{ $_[0]->{ INWHILE }++ } +{ $_[0]->enter_block('WHILE') } ], [#Rule 60 'loop', 6, sub -{ $_[0]->{ INWHILE }--; - $factory->while(@_[2, 5]) } +{ $factory->while(@_[2, 5], $_[0]->leave_block) } ], [#Rule 61 'loop', 3, sub -{ $factory->while(@_[3, 1]) } +{ $factory->while(@_[3, 1]) } ], [#Rule 62 'loopvar', 4, @@ -8648,17 +8647,6 @@ sub 1; - - - - - - - - - - - } # # Inline include of Template/Directive.pm @@ -8709,14 +8697,14 @@ sub { my \$context = shift || die "template sub called without context\\n"; my \$stash = \$context->stash; my \$output = ''; - my \$error; + my \$_tt_error; eval { BLOCK: { $block } }; if (\$@) { - \$error = \$context->catch(\$@, \\\$output); - die \$error unless \$error->type eq 'return'; + \$_tt_error = \$context->catch(\$@, \\\$output); + die \$_tt_error unless \$_tt_error->type eq 'return'; } return \$output; @@ -8734,14 +8722,14 @@ sub anon_block { $OUTPUT do { my \$output = ''; - my \$error; + my \$_tt_error; eval { BLOCK: { $block } }; if (\$@) { - \$error = \$context->catch(\$@, \\\$output); - die \$error unless \$error->type eq 'return'; + \$_tt_error = \$context->catch(\$@, \\\$output); + die \$_tt_error unless \$_tt_error->type eq 'return'; } \$output; @@ -8793,13 +8781,13 @@ sub ident { # does the first element of the identifier have a NAMESPACE # handler defined? if (ref $class && @$ident > 2 && ($ns = $class->{ NAMESPACE })) { - my $key = $ident->[0]; - $key =~ s/^'(.+)'$/$1/s; - if ($ns = $ns->{ $key }) { - return $ns->ident($ident); - } + my $key = $ident->[0]; + $key =~ s/^'(.+)'$/$1/s; + if ($ns = $ns->{ $key }) { + return $ns->ident($ident); + } } - + if (scalar @$ident <= 2 && ! $ident->[1]) { $ident = $ident->[0]; } @@ -8959,20 +8947,21 @@ sub if { sub foreach { - my ($class, $target, $list, $args, $block) = @_; + my ($class, $target, $list, $args, $block, $label) = @_; $args = shift @$args; $args = @$args ? ', { ' . join(', ', @$args) . ' }' : ''; + $label ||= 'LOOP'; my ($loop_save, $loop_set, $loop_restore, $setiter); if ($target) { - $loop_save = 'eval { $oldloop = ' . &ident($class, ["'loop'"]) . ' }'; - $loop_set = "\$stash->{'$target'} = \$value"; - $loop_restore = "\$stash->set('loop', \$oldloop)"; + $loop_save = 'eval { $_tt_oldloop = ' . &ident($class, ["'loop'"]) . ' }'; + $loop_set = "\$stash->{'$target'} = \$_tt_value"; + $loop_restore = "\$stash->set('loop', \$_tt_oldloop)"; } else { $loop_save = '$stash = $context->localise()'; - $loop_set = "\$stash->get(['import', [\$value]]) " - . "if ref \$value eq 'HASH'"; + $loop_set = "\$stash->get(['import', [\$_tt_value]]) " + . "if ref \$_tt_value eq 'HASH'"; $loop_restore = '$stash = $context->delocalise()'; } $block = pad($block, 3) if $PRETTY; @@ -8980,37 +8969,39 @@ sub foreach { return <iterator(\$list) + unless (UNIVERSAL::isa(\$_tt_list, 'Template::Iterator')) { + \$_tt_list = Template::Config->iterator(\$_tt_list) || die \$Template::Config::ERROR, "\\n"; } - (\$value, \$error) = \$list->get_first(); + (\$_tt_value, \$_tt_error) = \$_tt_list->get_first(); $loop_save; - \$stash->set('loop', \$list); + \$stash->set('loop', \$_tt_list); eval { -LOOP: while (! \$error) { +$label: while (! \$_tt_error) { $loop_set; $block; - (\$value, \$error) = \$list->get_next(); + (\$_tt_value, \$_tt_error) = \$_tt_list->get_next(); } }; $loop_restore; die \$@ if \$@; - \$error = 0 if \$error && \$error eq Template::Constants::STATUS_DONE; - die \$error if \$error; + \$_tt_error = 0 if \$_tt_error && \$_tt_error eq Template::Constants::STATUS_DONE; + die \$_tt_error if \$_tt_error; }; EOF } sub next { + my ($class, $label) = @_; + $label ||= 'LOOP'; return <get_next(); -next LOOP; +(\$_tt_value, \$_tt_error) = \$_tt_list->get_next(); +next $label; EOF } @@ -9067,19 +9058,20 @@ EOF sub while { - my ($class, $expr, $block) = @_; + my ($class, $expr, $block, $label) = @_; $block = pad($block, 2) if $PRETTY; + $label ||= 'LOOP'; return < $WHILE_MAX iterations)\\n" - unless \$failsafe; + unless \$_tt_failsafe; }; EOF } @@ -9099,9 +9091,9 @@ sub switch { $block = $case->[1]; $block = pad($block, 1) if $PRETTY; $caseblock .= <catch(\$@, \\\$output); - die \$error if \$error->type =~ /^return|stop\$/; - \$stash->set('error', \$error); - \$stash->set('e', \$error); - if (defined (\$handler = \$error->select_handler($handlers))) { + \$_tt_error = \$context->catch(\$@, \\\$output); + die \$_tt_error if \$_tt_error->type =~ /^return|stop\$/; + \$stash->set('error', \$_tt_error); + \$stash->set('e', \$_tt_error); + if (defined (\$_tt_handler = \$_tt_error->select_handler($handlers))) { $catchblock } $default @@ -9226,7 +9218,7 @@ sub clear { } -sub break { +sub OLD_break { return 'last LOOP;'; } @@ -9274,15 +9266,15 @@ sub view { return <get('view'); - my \$view = \$context->view($hash); - \$stash->set($name, \$view); - \$stash->set('view', \$view); + my \$_tt_oldv = \$stash->get('view'); + my \$_tt_view = \$context->view($hash); + \$stash->set($name, \$_tt_view); + \$stash->set('view', \$_tt_view); $block - \$stash->set('view', \$oldv); - \$view->seal(); + \$stash->set('view', \$_tt_oldv); + \$_tt_view->seal(); }; EOF } @@ -9306,14 +9298,14 @@ $block local(\$Template::Perl::context) = \$context; local(\$Template::Perl::stash) = \$stash; - my \$result = ''; - tie *Template::Perl::PERLOUT, 'Template::TieString', \\\$result; - my \$save_stdout = select *Template::Perl::PERLOUT; + my \$_tt_result = ''; + tie *Template::Perl::PERLOUT, 'Template::TieString', \\\$_tt_result; + my \$_tt_save_stdout = select *Template::Perl::PERLOUT; eval \$output; - select \$save_stdout; + select \$_tt_save_stdout; \$context->throw(\$@) if \$@; - \$result; + \$_tt_result; }; EOF } @@ -9358,12 +9350,12 @@ sub filter { $OUTPUT do { my \$output = ''; - my \$filter = \$context->filter($name) + my \$_tt_filter = \$context->filter($name) || \$context->throw(\$context->error); $block - &\$filter(\$output); + &\$_tt_filter(\$output); }; EOF } @@ -9404,20 +9396,20 @@ sub macro { my $nargs = scalar @$args; $args = join(', ', map { "'$_'" } @$args); $args = $nargs > 1 - ? "\@args{ $args } = splice(\@_, 0, $nargs)" - : "\$args{ $args } = shift"; + ? "\@_tt_args{ $args } = splice(\@_, 0, $nargs)" + : "\$_tt_args{ $args } = shift"; return <set('$ident', sub { my \$output = ''; - my (%args, \$params); + my (%_tt_args, \$_tt_params); $args; - \$params = shift; - \$params = { } unless ref(\$params) eq 'HASH'; - \$params = { \%args, %\$params }; + \$_tt_params = shift; + \$_tt_params = { } unless ref(\$_tt_params) eq 'HASH'; + \$_tt_params = { \%_tt_args, %\$_tt_params }; - my \$stash = \$context->localise(\$params); + my \$stash = \$context->localise(\$_tt_params); eval { $block }; @@ -9432,10 +9424,10 @@ EOF return <set('$ident', sub { - my \$params = \$_[0] if ref(\$_[0]) eq 'HASH'; + my \$_tt_params = \$_[0] if ref(\$_[0]) eq 'HASH'; my \$output = ''; - my \$stash = \$context->localise(\$params); + my \$stash = \$context->localise(\$_tt_params); eval { $block }; @@ -9515,9 +9507,9 @@ our $DEFAULT_STYLE = { }; our $QUOTED_ESCAPES = { - n => "\n", - r => "\r", - t => "\t", + n => "\n", + r => "\r", + t => "\t", }; our $CHOMP_FLAGS = qr/[-=~+]/; @@ -9528,7 +9520,7 @@ our $CHOMP_FLAGS = qr/[-=~+]/; sub new { my $class = shift; - my $config = $_[0] && UNIVERSAL::isa($_[0], 'HASH') ? shift(@_) : { @_ }; + my $config = $_[0] && ref($_[0]) eq 'HASH' ? shift(@_) : { @_ }; my ($tagstyle, $debug, $start, $end, $defaults, $grammar, $hash, $key, $udef); my $self = bless { @@ -9544,7 +9536,8 @@ sub new { FILE_INFO => 1, GRAMMAR => undef, _ERROR => '', - FACTORY => 'Template::Directive', + IN_BLOCK => [ ], + FACTORY => $config->{ FACTORY } || 'Template::Directive', }, $class; # update self with any relevant keys in config @@ -9590,11 +9583,41 @@ sub new { $self->new_style($config) || return $class->error($self->error()); - + return $self; } +sub enter_block { + my ($self, $name) = @_; + my $blocks = $self->{ IN_BLOCK }; + push(@{ $self->{ IN_BLOCK } }, $name); +} + +sub leave_block { + my $self = shift; + my $label = $self->block_label; + pop(@{ $self->{ IN_BLOCK } }); + return $label; +} + +sub in_block { + my ($self, $name) = @_; + my $blocks = $self->{ IN_BLOCK }; + return @$blocks && $blocks->[-1] eq $name; +} + +sub block_label { + my ($self, $prefix, $suffix) = @_; + my $blocks = $self->{ IN_BLOCK }; + my $name = @$blocks + ? $blocks->[-1] . scalar @$blocks + : undef; + return join('', grep { defined $_ } $prefix, $name, $suffix); +} + + + sub new_style { my ($self, $config) = @_; @@ -9638,7 +9661,7 @@ sub parse { my ($tokens, $block); $info->{ DEBUG } = $self->{ DEBUG_DIRS } - unless defined $info->{ DEBUG }; + unless defined $info->{ DEBUG }; # store for blocks defined in the template (see define_block()) @@ -9650,7 +9673,7 @@ sub parse { # split file into TEXT/DIRECTIVE chunks $tokens = $self->split_text($text) - || return undef; ## RETURN ## + || return undef; ## RETURN ## push(@{ $self->{ FILEINFO } }, $info); @@ -9659,7 +9682,7 @@ sub parse { pop(@{ $self->{ FILEINFO } }); - return undef unless $block; ## RETURN ## + return undef unless $block; ## RETURN ## $self->debug("compiled main template document block:\n$block") if $self->{ DEBUG } & Template::Constants::DEBUG_PARSER; @@ -9680,12 +9703,13 @@ sub split_text { my $style = $self->{ STYLE }->[-1]; my ($start, $end, $prechomp, $postchomp, $interp ) = @$style{ qw( START_TAG END_TAG PRE_CHOMP POST_CHOMP INTERPOLATE ) }; + my $tags_dir = $self->{ANYCASE} ? qri : qr; my @tokens = (); my $line = 1; - return \@tokens ## RETURN ## - unless defined $text && length $text; + return \@tokens ## RETURN ## + unless defined $text && length $text; # extract all directives from the text while ($text =~ s/ @@ -9718,7 +9742,7 @@ sub split_text { if ($chomp && $pre) { # chomp off whitespace and newline preceding directive if ($chomp == CHOMP_ALL) { - $pre =~ s{ (\n|^) [^\S\n]* \z }{}mx; + $pre =~ s{ (\r?\n|^) [^\S\n]* \z }{}mx; } elsif ($chomp == CHOMP_COLLAPSE) { $pre =~ s{ (\s+) \z }{ }x; @@ -9761,7 +9785,7 @@ sub split_text { # and now the directive, along with line number information if (length $dir) { # the TAGS directive is a compile-time switch - if ($dir =~ /^TAGS\s+(.*)/i) { + if ($dir =~ /^$tags_dir\s+(.*)/) { my @tags = split(/\s+/, $1); if (scalar @tags > 1) { ($start, $end) = map { quotemeta($_) } @tags; @@ -9796,7 +9820,7 @@ sub split_text { : ( 'TEXT', $text) ) if length $text; - return \@tokens; ## RETURN ## + return \@tokens; ## RETURN ## } @@ -9812,32 +9836,32 @@ sub interpolate_text { / ( (?: \\. | [^\$] ){1,3000} ) # escaped or non-'$' character [$1] | - ( \$ (?: # embedded variable [$2] - (?: \{ ([^\}]*) \} ) # ${ ... } [$3] - | - ([\w\.]+) # $word [$4] - ) - ) - /gx) { - - ($pre, $var, $dir) = ($1, $3 || $4, $2); - - # preceding text - if (defined($pre) && length($pre)) { - $line += $pre =~ tr/\n//; - $pre =~ s/\\\$/\$/g; - push(@tokens, 'TEXT', $pre); - } - # $variable reference + ( \$ (?: # embedded variable [$2] + (?: \{ ([^\}]*) \} ) # ${ ... } [$3] + | + ([\w\.]+) # $word [$4] + ) + ) + /gx) { + + ($pre, $var, $dir) = ($1, $3 || $4, $2); + + # preceding text + if (defined($pre) && length($pre)) { + $line += $pre =~ tr/\n//; + $pre =~ s/\\\$/\$/g; + push(@tokens, 'TEXT', $pre); + } + # $variable reference if ($var) { - $line += $dir =~ tr/\n/ /; - push(@tokens, [ $dir, $line, $self->tokenise_directive($var) ]); - } - # other '$' reference - treated as text - elsif ($dir) { - $line += $dir =~ tr/\n//; - push(@tokens, 'TEXT', $dir); - } + $line += $dir =~ tr/\n/ /; + push(@tokens, [ $dir, $line, $self->tokenise_directive($var) ]); + } + # other '$' reference - treated as text + elsif ($dir) { + $line += $dir =~ tr/\n//; + push(@tokens, 'TEXT', $dir); + } } return \@tokens; @@ -9855,108 +9879,114 @@ sub tokenise_directive { my @tokens = ( ); while ($text =~ - / - # strip out any comments - (\#[^\n]*) - | - # a quoted phrase matches in $3 - (["']) # $2 - opening quote, ' or " - ( # $3 - quoted text buffer - (?: # repeat group (no backreference) - \\\\ # an escaped backslash \\ - | # ...or... - \\\2 # an escaped quote \" or \' (match $1) - | # ...or... - . # any other character - | \n - )*? # non-greedy repeat - ) # end of $3 - \2 # match opening quote - | - # an unquoted number matches in $4 - (-?\d+(?:\.\d+)?) # numbers - | - # filename matches in $5 - ( \/?\w+(?:(?:\/|::?)\w*)+ | \/\w+) - | - # an identifier matches in $6 - (\w+) # variable identifier - | - # an unquoted word or symbol matches in $7 - ( [(){}\[\]:;,\/\\] # misc parenthesis and symbols - | [+\-*] # math operations - | \$\{? # dollar with option left brace - | => # like '=' - | [=!<>]?= | [!<>] # eqality tests - | &&? | \|\|? # boolean ops - | \.\.? # n..n sequence - | \S+ # something unquoted - ) # end of $7 - /gmxo) { - - # ignore comments to EOL - next if $1; - - # quoted string - if (defined ($token = $3)) { + / + # strip out any comments + (\#[^\n]*) + | + # a quoted phrase matches in $3 + (["']) # $2 - opening quote, ' or " + ( # $3 - quoted text buffer + (?: # repeat group (no backreference) + \\\\ # an escaped backslash \\ + | # ...or... + \\\2 # an escaped quote \" or \' (match $1) + | # ...or... + . # any other character + | \n + )*? # non-greedy repeat + ) # end of $3 + \2 # match opening quote + | + # an unquoted number matches in $4 + (-?\d+(?:\.\d+)?) # numbers + | + # filename matches in $5 + ( \/?\w+(?:(?:\/|::?)\w*)+ | \/\w+) + | + # an identifier matches in $6 + (\w+) # variable identifier + | + # an unquoted word or symbol matches in $7 + ( [(){}\[\]:;,\/\\] # misc parenthesis and symbols + | [+\-*] # math operations + | \$\{? # dollar with option left brace + | => # like '=' + | [=!<>]?= | [!<>] # eqality tests + | &&? | \|\|? # boolean ops + | \.\.? # n..n sequence + | \S+ # something unquoted + ) # end of $7 + /gmxo) { + + # ignore comments to EOL + next if $1; + + # quoted string + if (defined ($token = $3)) { # double-quoted string may include $variable references - if ($2 eq '"') { - if ($token =~ /[\$\\]/) { - $type = 'QUOTED'; - # unescape " and \ but leave \$ escaped so that - # interpolate_text() doesn't incorrectly treat it - # as a variable reference - for ($token) { - s/\\([^\$nrt])/$1/g; - s/\\([nrt])/$QUOTED_ESCAPES->{ $1 }/ge; - } - push(@tokens, ('"') x 2, - @{ $self->interpolate_text($token) }, - ('"') x 2); - next; - } + if ($2 eq '"') { + if ($token =~ /[\$\\]/) { + $type = 'QUOTED'; + # unescape " and \ but leave \$ escaped so that + # interpolate_text() doesn't incorrectly treat it + # as a variable reference + for ($token) { + s/\\([^\$nrt])/$1/g; + s/\\([nrt])/$QUOTED_ESCAPES->{ $1 }/ge; + } + push(@tokens, ('"') x 2, + @{ $self->interpolate_text($token) }, + ('"') x 2); + next; + } else { - $type = 'LITERAL'; - $token =~ s['][\\']g; - $token = "'$token'"; - } - } - else { - $type = 'LITERAL'; - $token = "'$token'"; - } - } - # number - elsif (defined ($token = $4)) { - $type = 'NUMBER'; - } - elsif (defined($token = $5)) { - $type = 'FILENAME'; - } - elsif (defined($token = $6)) { - # reserved words may be in lower case unless case sensitive - $uctoken = $anycase ? uc $token : $token; - if (defined ($type = $lextable->{ $uctoken })) { - $token = $uctoken; - } - else { - $type = 'IDENT'; - } - } - elsif (defined ($token = $7)) { - # reserved words may be in lower case unless case sensitive - $uctoken = $anycase ? uc $token : $token; - unless (defined ($type = $lextable->{ $uctoken })) { - $type = 'UNQUOTED'; - } - } + $type = 'LITERAL'; + $token =~ s['][\\']g; + $token = "'$token'"; + } + } + else { + $type = 'LITERAL'; + $token = "'$token'"; + } + } + # number + elsif (defined ($token = $4)) { + $type = 'NUMBER'; + } + elsif (defined($token = $5)) { + $type = 'FILENAME'; + } + elsif (defined($token = $6)) { + # Fold potential keywords to UPPER CASE if the ANYCASE option is + # set, unless (we've got some preceeding tokens and) the previous + # token is a DOT op. This prevents the 'last' in 'data.last' + # from being interpreted as the LAST keyword. + $uctoken = + ($anycase && (! @tokens || $tokens[-2] ne 'DOT')) + ? uc $token + : $token; + if (defined ($type = $lextable->{ $uctoken })) { + $token = $uctoken; + } + else { + $type = 'IDENT'; + } + } + elsif (defined ($token = $7)) { + # reserved words may be in lower case unless case sensitive + $uctoken = $anycase ? uc $token : $token; + unless (defined ($type = $lextable->{ $uctoken })) { + $type = 'UNQUOTED'; + } + } - push(@tokens, $type, $token); + push(@tokens, $type, $token); } - return \@tokens; ## RETURN ## + return \@tokens; ## RETURN ## } @@ -9967,7 +9997,7 @@ sub define_block { || return undef; $self->debug("compiled block '$name':\n$block") - if $self->{ DEBUG } & Template::Constants::DEBUG_PARSER; + if $self->{ DEBUG } & Template::Constants::DEBUG_PARSER; $defblock->{ $name } = $block; @@ -10012,6 +10042,7 @@ sub location { my $file = $info->{ path } || $info->{ name } || '(unknown template)'; $line =~ s/\-.*$//; # might be 'n-n' + $line ||= 1; return "#line $line \"$file\"\n"; } @@ -10022,7 +10053,7 @@ sub _parse { my ($self, $tokens, $info) = @_; my ($token, $value, $text, $line, $inperl); my ($state, $stateno, $status, $action, $lookup, $coderet, @codevars); - my ($lhs, $len, $code); # rule contents + my ($lhs, $len, $code); # rule contents my $stack = [ [ 0, undef ] ]; # DFA stack @@ -10041,152 +10072,152 @@ sub _parse { my $in_string = 0; while(1) { - # get state number and state - $stateno = $stack->[-1]->[0]; - $state = $states->[$stateno]; - - # see if any lookaheads exist for the current state - if (exists $state->{'ACTIONS'}) { - - # get next token and expand any directives (i.e. token is an - # array ref) onto the front of the token list - while (! defined $token && @$tokens) { - $token = shift(@$tokens); - if (ref $token) { - ($text, $line, $token) = @$token; - if (ref $token) { - if ($info->{ DEBUG } && ! $in_string) { + # get state number and state + $stateno = $stack->[-1]->[0]; + $state = $states->[$stateno]; + + # see if any lookaheads exist for the current state + if (exists $state->{'ACTIONS'}) { + + # get next token and expand any directives (i.e. token is an + # array ref) onto the front of the token list + while (! defined $token && @$tokens) { + $token = shift(@$tokens); + if (ref $token) { + ($text, $line, $token) = @$token; + if (ref $token) { + if ($info->{ DEBUG } && ! $in_string) { # - - - - - - - - - - - - - - - - - - - - - - - - - - # This is gnarly. Look away now if you're easily + # This is gnarly. Look away now if you're easily # frightened. We're pushing parse tokens onto the # pending list to simulate a DEBUG directive like so: - # [% DEBUG msg line='20' text='INCLUDE foo' %] + # [% DEBUG msg line='20' text='INCLUDE foo' %] # - - - - - - - - - - - - - - - - - - - - - - - - - - my $dtext = $text; - $dtext =~ s[(['\\])][\\$1]g; - unshift(@$tokens, - DEBUG => 'DEBUG', - IDENT => 'msg', - IDENT => 'line', - ASSIGN => '=', - LITERAL => "'$line'", - IDENT => 'text', - ASSIGN => '=', - LITERAL => "'$dtext'", - IDENT => 'file', - ASSIGN => '=', - LITERAL => "'$info->{ name }'", - (';') x 2, - @$token, - (';') x 2); - } - else { - unshift(@$tokens, @$token, (';') x 2); - } - $token = undef; # force redo - } - elsif ($token eq 'ITEXT') { - if ($inperl) { - # don't perform interpolation in PERL blocks - $token = 'TEXT'; - $value = $text; - } - else { - unshift(@$tokens, - @{ $self->interpolate_text($text, $line) }); - $token = undef; # force redo - } - } - } - else { - # toggle string flag to indicate if we're crossing - # a string boundary - $in_string = ! $in_string if $token eq '"'; - $value = shift(@$tokens); - } - }; - # clear undefined token to avoid 'undefined variable blah blah' - # warnings and let the parser logic pick it up in a minute - $token = '' unless defined $token; - - # get the next state for the current lookahead token - $action = defined ($lookup = $state->{'ACTIONS'}->{ $token }) - ? $lookup - : defined ($lookup = $state->{'DEFAULT'}) - ? $lookup - : undef; - } - else { - # no lookahead actions - $action = $state->{'DEFAULT'}; - } + my $dtext = $text; + $dtext =~ s[(['\\])][\\$1]g; + unshift(@$tokens, + DEBUG => 'DEBUG', + IDENT => 'msg', + IDENT => 'line', + ASSIGN => '=', + LITERAL => "'$line'", + IDENT => 'text', + ASSIGN => '=', + LITERAL => "'$dtext'", + IDENT => 'file', + ASSIGN => '=', + LITERAL => "'$info->{ name }'", + (';') x 2, + @$token, + (';') x 2); + } + else { + unshift(@$tokens, @$token, (';') x 2); + } + $token = undef; # force redo + } + elsif ($token eq 'ITEXT') { + if ($inperl) { + # don't perform interpolation in PERL blocks + $token = 'TEXT'; + $value = $text; + } + else { + unshift(@$tokens, + @{ $self->interpolate_text($text, $line) }); + $token = undef; # force redo + } + } + } + else { + # toggle string flag to indicate if we're crossing + # a string boundary + $in_string = ! $in_string if $token eq '"'; + $value = shift(@$tokens); + } + }; + # clear undefined token to avoid 'undefined variable blah blah' + # warnings and let the parser logic pick it up in a minute + $token = '' unless defined $token; + + # get the next state for the current lookahead token + $action = defined ($lookup = $state->{'ACTIONS'}->{ $token }) + ? $lookup + : defined ($lookup = $state->{'DEFAULT'}) + ? $lookup + : undef; + } + else { + # no lookahead actions + $action = $state->{'DEFAULT'}; + } - # ERROR: no ACTION - last unless defined $action; + # ERROR: no ACTION + last unless defined $action; - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # shift (+ive ACTION) - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if ($action > 0) { - push(@$stack, [ $action, $value ]); - $token = $value = undef; - redo; - }; + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # shift (+ive ACTION) + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if ($action > 0) { + push(@$stack, [ $action, $value ]); + $token = $value = undef; + redo; + }; - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # reduce (-ive ACTION) - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ($lhs, $len, $code) = @{ $rules->[ -$action ] }; + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # reduce (-ive ACTION) + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ($lhs, $len, $code) = @{ $rules->[ -$action ] }; - # no action imples ACCEPTance - $action - or $status = ACCEPT; + # no action imples ACCEPTance + $action + or $status = ACCEPT; - # use dummy sub if code ref doesn't exist - $code = sub { $_[1] } - unless $code; + # use dummy sub if code ref doesn't exist + $code = sub { $_[1] } + unless $code; - @codevars = $len - ? map { $_->[1] } @$stack[ -$len .. -1 ] - : (); + @codevars = $len + ? map { $_->[1] } @$stack[ -$len .. -1 ] + : (); - eval { - $coderet = &$code( $self, @codevars ); - }; - if ($@) { - my $err = $@; - chomp $err; - return $self->_parse_error($err); - } + eval { + $coderet = &$code( $self, @codevars ); + }; + if ($@) { + my $err = $@; + chomp $err; + return $self->_parse_error($err); + } - # reduce stack by $len - splice(@$stack, -$len, $len); + # reduce stack by $len + splice(@$stack, -$len, $len); - # ACCEPT - return $coderet ## RETURN ## - if $status == ACCEPT; + # ACCEPT + return $coderet ## RETURN ## + if $status == ACCEPT; - # ABORT - return undef ## RETURN ## - if $status == ABORT; + # ABORT + return undef ## RETURN ## + if $status == ABORT; - # ERROR - last - if $status == ERROR; + # ERROR + last + if $status == ERROR; } continue { - push(@$stack, [ $states->[ $stack->[-1][0] ]->{'GOTOS'}->{ $lhs }, - $coderet ]), + push(@$stack, [ $states->[ $stack->[-1][0] ]->{'GOTOS'}->{ $lhs }, + $coderet ]), } - # ERROR ## RETURN ## + # ERROR ## RETURN ## return $self->_parse_error('unexpected end of input') - unless defined $value; + unless defined $value; # munge text of last directive to make it readable return $self->_parse_error("unexpected end of directive", $text) - if $value eq ';'; # end of directive SEPARATOR + if $value eq ';'; # end of directive SEPARATOR return $self->_parse_error("unexpected token ($value)", $text); } @@ -10201,7 +10232,7 @@ sub _parse_error { $line = 'unknown' unless $line; $msg .= "\n [% $text %]" - if defined $text; + if defined $text; return $self->error("line $line: $msg"); } @@ -10215,10 +10246,10 @@ sub _dump { my $key; foreach $key (qw( START_TAG END_TAG TAG_STYLE ANYCASE INTERPOLATE - PRE_CHOMP POST_CHOMP V1DOLLAR )) { - my $val = $self->{ $key }; - $val = '' unless defined $val; - $output .= sprintf($format, $key, $val); + PRE_CHOMP POST_CHOMP V1DOLLAR )) { + my $val = $self->{ $key }; + $val = '' unless defined $val; + $output .= sprintf($format, $key, $val); } $output .= '}'; @@ -17845,6 +17876,20 @@ proto.hash_functions.values = function(hash) { return list; } +proto.hash_functions.pairs = function(hash) { + var list = new Array(); + var keys = new Array(); + for ( var key in hash ) { + keys.push( key ); + } + keys.sort(); + for ( var key in keys ) { + key = keys[key] + list.push( { 'key': key, 'value': hash[key] } ); + } + return list; +} + // delete proto.hash_functions.remove = function(hash, key) { return delete hash[key]; @@ -19703,7 +19748,7 @@ use warnings; sub main { return &kernel } sub kernel { <<'...'; -if(typeof Jemplate=="undefined"){var Jemplate=function(){this.init.apply(this,arguments)}}Jemplate.VERSION="0.22";Jemplate.process=function(){var A=new Jemplate(Jemplate.prototype.config);return A.process.apply(A,arguments)};(function(){if(!Jemplate.templateMap){Jemplate.templateMap={}}var proto=Jemplate.prototype={};proto.config={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,FILTERS:{},INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};proto.defaults={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};Jemplate.init=function(config){Jemplate.prototype.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof Jemplate.prototype.config[i]=="undefined"){Jemplate.prototype.config[i]=Jemplate.prototype.defaults[i]}}};proto.init=function(config){this.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof this.config[i]=="undefined"){this.config[i]=Jemplate.prototype.defaults[i]}}};proto.process=function(template,data,output){var context=this.config.CONTEXT||new Jemplate.Context();context.config=this.config;context.stash=new Jemplate.Stash(this.config.STASH,this.config);context.__filter__=new Jemplate.Filter();context.__filter__.config=this.config;context.__plugin__=new Jemplate.Plugin();context.__plugin__.config=this.config;var result;var proc=function(input){try{if(typeof context.config.PRE_PROCESS=="string"){context.config.PRE_PROCESS=[context.config.PRE_PROCESS]}for(var i=0;i/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=indexnew; +my @templates; +push @templates, $jemplate->compile_template_content( <<_END_, 't0' ); +Hello, World. +_END_ +push @templates, $jemplate->compile_template_content( <<_END_, 't1' ); +[% FOREACH pair = hash.pairs %] +[% pair.key %] = [% pair.value %] +[% END %] _END_ +push @templates, $jemplate->compile_template_content( <<_END_, 't2' ); +[% FOREACH key = hash.keys %] +[% key %] = [% hash.\$key %] +[% END %] +_END_ + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @templates, "1;" ); test_js <<'_END_'; -areEqual( 1, 1 ) +result = Jemplate.process( 't1', { hash: { c: 1, a: 2, b: 3 } } ); +like( result, /a = 2\s+b = 3\s+c = 1/ ) _END_ 1; diff --git a/t/quoted.t b/t/quoted.t index 9387c4f..ac89669 100644 --- a/t/quoted.t +++ b/t/quoted.t @@ -17,7 +17,7 @@ stash.set('foo', 'foo'); //line 1 "test_template" stash.set('bar', 'bar'); output += '\n'; -//line 0 "test_template" +//line 1 "test_template" output += stash.get('foo') + '/' + stash.get('bar'); output += '\n';