Skip Menu |

This queue is for tickets about the Template-Toolkit CPAN distribution.

Report information
The Basics
Id: 9802
Status: rejected
Priority: 0/
Queue: Template-Toolkit

People
Owner: Nobody in particular
Requestors: sriha [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Critical
Broken in: 2.14
Fixed in: (no value)



Subject: FOREACH incorrect when a plugin returns (0)
The Template::Iterator treats a 1-element array containing something that evaluates to false (i.e. 0 or "") as an empty array. The following template loop iterates zero times when plugin_function returns an array (0). It iterates correctly if the function returns arrays (1) or (0, 1). [% FOREACH i IN plugin_function; "$i "; END; %] The attached patch fixes Template::Iterator::new to behave correctly.
--- Template/Iterator.pm Sun Jan 16 23:35:09 2005 +++ /Library/Perl/5.8.1/darwin-thread-multi-2level/Template/Iterator.pm Sun Jan 16 23:37:08 2005 @@ -72,8 +72,9 @@ sub new { my $class = shift; - my $data = shift || [ ]; + my $data = shift; my $params = shift || { }; + $data = [] unless defined $data; if (ref $data eq 'HASH') { # map a hash into a list of { key => ???, value => ??? } hashes,
From: sriha [...] cpan.org
The patch submitted above introduces another problem, in that empty lists are converted into a one-elemtent list ['']. The problem is that Template::Stash::get converts undef variable values to ''. That works fine in scalar context, but in a list context (i.e. when called for FOREACH) it should convert undef to []. I'm attaching a new patch that also modifes Stash.pm and Directive.pm, s.t. in the context of a FOREACH, variables containing undef are treated as empty arrays.
diff -rc ./Directive.pm /Library/Perl/5.8.1/darwin-thread-multi-2level/Template/Directive.pm *** ./Directive.pm Thu May 20 03:46:54 2004 --- /Library/Perl/5.8.1/darwin-thread-multi-2level/Template/Directive.pm Sun Feb 6 15:22:00 2005 *************** *** 422,427 **** --- 422,432 ---- $loop_restore = '$stash = $context->delocalise()'; } $block = pad($block, 3) if $PRETTY; + + ## Convert $stash->get(...) to $stash->getlist(...) to + ## correctly deal with empty lists. + + $list =~ s/^\$stash->get\(/\$stash->getlist\(/; return <<EOF; diff -rc ./Iterator.pm /Library/Perl/5.8.1/darwin-thread-multi-2level/Template/Iterator.pm *** ./Iterator.pm Mon Oct 4 05:27:39 2004 --- /Library/Perl/5.8.1/darwin-thread-multi-2level/Template/Iterator.pm Sun Feb 6 23:23:05 2005 *************** *** 72,79 **** sub new { my $class = shift; ! my $data = shift || [ ]; my $params = shift || { }; if (ref $data eq 'HASH') { # map a hash into a list of { key => ???, value => ??? } hashes, --- 72,80 ---- sub new { my $class = shift; ! my $data = shift; my $params = shift || { }; + $data = [] unless defined $data; if (ref $data eq 'HASH') { # map a hash into a list of { key => ???, value => ??? } hashes, diff -rc ./Stash.pm /Library/Perl/5.8.1/darwin-thread-multi-2level/Template/Stash.pm *** ./Stash.pm Mon Oct 4 05:27:39 2004 --- /Library/Perl/5.8.1/darwin-thread-multi-2level/Template/Stash.pm Sun Feb 6 15:21:49 2005 *************** *** 384,389 **** --- 384,419 ---- #------------------------------------------------------------------------ # get($ident) # + # Returns the value for an variable stored in the stash. Returns the + # value of the identifier or an empty string if undefined. Errors are + # thrown via die(). + #------------------------------------------------------------------------ + + sub get { + my $self = shift; + my $result = $self->_get(@_); + return defined $result ? $result : $self->undefined(@_); + } + + + #------------------------------------------------------------------------ + # getlist($ident) + # + # Returns the value for an variable stored in the stash. Returns the + # value of the identifier or [] if undefined. Errors are thrown via + # die(). + #------------------------------------------------------------------------ + + sub getlist { + my $self = shift; + my $result = $self->_get(@_); + return defined $result ? $result : []; + } + + + #------------------------------------------------------------------------ + # _get($ident) + # # Returns the value for an variable stored in the stash. The variable # may be specified as a simple string, e.g. 'foo', or as an array # reference representing compound variables. In the latter case, each *************** *** 392,402 **** # list reference of arguments or 0 if undefined. So, the compound # variable [% foo.bar('foo').baz %] would be represented as the list # [ 'foo', 0, 'bar', ['foo'], 'baz', 0 ]. Returns the value of the ! # identifier or an empty string if undefined. Errors are thrown via ! # die(). #------------------------------------------------------------------------ ! sub get { my ($self, $ident, $args) = @_; my ($root, $result); $root = $self; --- 422,431 ---- # list reference of arguments or 0 if undefined. So, the compound # variable [% foo.bar('foo').baz %] would be represented as the list # [ 'foo', 0, 'bar', ['foo'], 'baz', 0 ]. Returns the value of the ! # identifier or undef. Errors are thrown via die(). #------------------------------------------------------------------------ ! sub _get { my ($self, $ident, $args) = @_; my ($root, $result); $root = $self; *************** *** 419,428 **** else { $result = $self->_dotop($root, $ident, $args); } ! ! return defined $result ? $result : $self->undefined($ident, $args); } - #------------------------------------------------------------------------ # set($ident, $value, $default) --- 448,455 ---- else { $result = $self->_dotop($root, $ident, $args); } ! return $result; } #------------------------------------------------------------------------ # set($ident, $value, $default)
From: sriha [...] cpan.org
Attached is a plugin module that demonstrates the problem. The following template shows how list are treated as literal "[1, 2, 3]", as array variables "ListTest.echo_list(1, 2, 3)", and as variables that implement the as_list method "ListTest.echo_as_list(1, 2, 3)". [% USE ListTest %] [% FOREACH i IN [0, 1, 2, 3]; "$i, "; END %][0, 1, 2, 3] [% FOREACH i IN ListTest.echo_list(0, 1, 2, 3); "$i, "; END %]echo_list(0, 1, 2, 3) [% FOREACH i IN ListTest.echo_as_list(0, 1, 2, 3); "$i, "; END %]echo_as_list(0, 1, 2, 3) [% FOREACH i IN ["", "Hello", "World"]; "$i, "; END %]["", "Hello", "World"] [% FOREACH i IN ListTest.echo_list("", "Hello", "World"); "$i, "; END %]echo_list("", "Hello", "World") [% FOREACH i IN ListTest.echo_as_list("", "Hello", "World"); "$i, "; END %]echo_as_list("", "Hello", "World") [% FOREACH i IN [1, 2, 3]; "$i, "; END %][1, 2, 3] [% FOREACH i IN ListTest.echo_list(1, 2, 3); "$i, "; END %]echo_list(1, 2, 3) [% FOREACH i IN ListTest.echo_as_list(1, 2, 3); "$i, "; END %]echo_as_list(1, 2, 3) [% FOREACH i IN ["Hello", "World"]; "$i, "; END %]["Hello", "World"] [% FOREACH i IN ListTest.echo_list("Hello", "World"); "$i, "; END %]echo_list("Hello", "World") [% FOREACH i IN ListTest.echo_as_list("Hello", "World"); "$i, "; END %]echo_as_list("Hello", "World") [% FOREACH i IN [1]; "$i, "; END %][1] [% FOREACH i IN ListTest.echo_list(1); "$i, "; END %]echo_list(1) [% FOREACH i IN ListTest.echo_as_list(1); "$i, "; END %]echo_as_list(1) [% FOREACH i IN [0]; "$i, "; END %][0] [% FOREACH i IN ListTest.echo_list(0); "$i, "; END %]echo_list(0) [% FOREACH i IN ListTest.echo_as_list(0); "$i, "; END %]echo_as_list(0) [% FOREACH i IN [""]; "$i, "; END %][""] [% FOREACH i IN ListTest.echo_list(""); "$i, "; END %]echo_list("") [% FOREACH i IN ListTest.echo_as_list(""); "$i, "; END %]echo_as_list("") [% FOREACH i IN []; "$i, "; END %][] [% FOREACH i IN ListTest.echo_list(); "$i, "; END %]echo_list() [% FOREACH i IN ListTest.echo_as_list(); "$i, "; END %]echo_as_list() [% FOREACH i IN 3; "$i, "; END %]3 [% FOREACH i IN ListTest.echo_scalar(3); "$i, "; END %]echo_scalar(3) [% FOREACH i IN 0; "$i, "; END %]0 [% FOREACH i IN ListTest.echo_scalar(0); "$i, "; END %]echo_scalar(0) [% FOREACH i IN ""; "$i, "; END %]"" [% FOREACH i IN ListTest.echo_scalar(""); "$i, "; END %]echo_scalar("")
package Template::Plugin::ListTest; use strict; use base 'Template::Plugin'; use Template::Plugin; sub load { # called as MyPlugin->load($context) my ($class, $context) = @_; return $class; # returns 'MyPlugin' } #------------------------------------------------------------------------ # new($context, $name) # # Constructor method which returns a sub-routine closure for constructing # complex URL's from a base part and hash of additional parameters. #------------------------------------------------------------------------ sub new { my $class = shift; my $self = { CONTEXT => shift }; return bless $self, $class; } sub echo_list { shift; wantarray ? @_ : \@_; } sub echo_scalar { shift; shift; } sub echo_as_list { shift; return AsList->new(@_); } package AsList; sub new { my $class = shift; bless { list => \@_ }, $class } sub as_list { my $self = shift; $self->{list}; } 1; __END__
This bug describes a general limitation (i.e. design flaw) in TT2. It will be resolved in TT3, but not in TT2 as any changes to this behaviour in TT2 will break existing code. Thanks all the same. A