Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

rc2

git-svn-id: svn://svn.tt2.org/tt/Template2/trunk@70 d5a88997-0a34-4036-9ed2-92fb5d660d91
  • Loading branch information...
commit 867f44f2a714834e634978083ef72907906c8528 1 parent 72a9c54
@abw authored
View
62 Changes
@@ -17,7 +17,67 @@
#========================================================================
#------------------------------------------------------------------------
-# Version 2.00
+# Version 2.00-rc2 14th November 2000
+#------------------------------------------------------------------------
+
+* Added the 'prev' and 'next' methods to Template::Iterator and
+ Template::Plugin::DBI::Iterator to return the previous and next
+ items from the data set.
+
+* Added the 'sort' and 'nsort' virtual methods for hash arrays,
+ thanks to a patch provided by Leon Brocard.
+
+* Various fixes to DBI plugin, configuration and test:- modified
+ Makefile.PL to prompt for DBI DSN specific to user's DBD; changed
+ DBI plugin to accept DBI attributes (e.g. ChopBlanks) as named
+ parameters to connect method; fixed t/dbi.t to not munge 'user'
+ variable in final test; added 'ChopBlanks' attributes to satisfy
+ tests under certain DBD's (e.g. Pg). Thanks to Jonas Liljegren and
+ Chris Nandor for their efforts in finding, testing and fixing the
+ problems.
+
+* Modified the XML::DOM plugin to work with XML::DOM version 1.27
+ which now uses blessed array references instead of hashes as the
+ underlying data types. Changed Makefile.PL and t/dom.t to require
+ version 1.27 or later.
+
+* Changed the Template::Iterator module to *NOT* automatically expand
+ the contents of blessed ARRAY objects to construct the iteration data
+ set. The previous behaviour caused problems with modules such as
+ XML::DOM where a single object passed to the iterator constructor
+ would be expanded into a list of the member data, rather than being
+ treated as a single item list containing that one object. A blessed
+ ARRAY reference can now provide the as_list() method which the
+ iterator constructor will call to return list data.
+
+* Fixed a bug in Template::Provider to ensure that template metadata
+ (e.g. name, modtime, etc.) is written to compiled template files.
+ Thanks to Steven Hetland for reporting the problem.
+
+* Changed the Template::Directive::template() generator method to
+ raise an error if a context reference isn't passed to a template
+ subroutine as the first argument.
+
+* Fixed t/autoformat.t to use locale dependant numerical formatting.
+ Note that versions of Perl prior to 5.6.0 still have problems and
+ will cause t/autoform.t tests 23 and 25 to fail under locales that
+ use a decimal separator other than '.'. The Makefile.PL will issue
+ a warning in such cases. Thanks to Jonas Liljegren for reporting
+ the problem.
+
+* Applied a patch from Leon Brocard which corrects the behaviour of
+ the URL plugin to join parameters with '&' instead of '&'.
+
+* Fixed a bug in the AUTOLOAD method of the Template::Plugin base
+ class which caused warnings about not finding _DELEGATE pseudo-hash
+ method under Perl 5.6.0.
+
+* Various minor documentation fixes, thanks to Henrik Edlund and Leon
+ Brocard.
+
+
+#------------------------------------------------------------------------
+# Version 2.00-rc1 1st November 2000
#------------------------------------------------------------------------
* Added the push(), pop(), unshift() and shift() virtual list methods
View
52 Makefile.PL
@@ -54,7 +54,7 @@ available on your system.
EOF
-foreach my $mods ( Text::Autoformat,
+foreach my $mods ( [ Text::Autoformat => \&check_taf ],
[ XML::DOM => \&check_dom ],
[ XML::RSS => \&check_rss ],
[ XML::XPath => \&check_xpath ],
@@ -144,14 +144,37 @@ sub nope {
#------------------------------------------------------------------------
+# check_taf()
+#
+# There are some problems with Text::Autoformat with version of Perl
+# prior to 5.6.0 not working properly with locales that use a numerical
+# separator other than '.' (e.g. Swedish)
+#------------------------------------------------------------------------
+
+sub check_taf {
+ use POSIX qw( localeconv );
+ my $loc = localeconv;
+ my $dec = $loc->{ decimal_point };
+
+ yep("version $Text::Autoformat::VERSION installed");
+
+ if ($] <= 5.006 && $dec ne '.') {
+ print ' ' x 6,
+ "NOTE: tests 23 and 25 may fail under your locale, see TODO file.\n";
+ }
+}
+
+
+#------------------------------------------------------------------------
# check_dom()
#
-# XML::DOM version 1.25 (and earlier?) dump core with Perl 5.6.0
+# XML::DOM changed from HASH to ARRAY objects somewhere between versions
+# 1.25 and 1.27, so the latter version is an absolute necessity.
#------------------------------------------------------------------------
sub check_dom {
- if ($] >= 5.006 && $XML::DOM::VERSION <= 1.25) {
- nope("version $XML::DOM::VERSION may not work with Perl $]");
+ if ($XML::DOM::VERSION < 1.27) {
+ nope("requires version 1.27 or later ($XML::DOM::VERSION installed)");
}
else {
yep("version $XML::DOM::VERSION installed");
@@ -213,10 +236,14 @@ sub dbi_config {
local $" = ', ';
my $default = (grep(/m.?sql/i, @drivers))[0];
+
+ print <<EOF;
+ - Please enter the driver name for the test database.
+ The DBD drivers installed on your system are
- print " - Please enter the driver name for the test database.\n";
- print " The DBD drivers installed on your system are\n\n";
- print " @drivers\n\n";
+ @drivers
+
+EOF
while (! $driver) {
$driver = prompt(" - Enter driver name: ", $default);
@@ -224,8 +251,17 @@ sub dbi_config {
unless grep(/^$driver$/, @drivers);
}
+ print <<EOF;
+ - Please enter the data source (DSN) of the test database.
+ Many DBD drivers require only a database name (e.g. 'test') while
+ others may require an alternate format or additional parameters
+ (e.g. 'dbname=test'). Please consult your DBD documentation for
+ further details.
+EOF
+
+ my $dbname_eg = $driver eq 'Pg' ? 'dbname=test' : 'test';
while (! $dbname) {
- $dbname = prompt(' - Enter database name: ', 'test');
+ $dbname = prompt(' - Database name: ', $dbname_eg);
}
$dsn = "dbi:$driver:$dbname";
View
15 README
@@ -1,8 +1,8 @@
Template Toolkit
- Version 2.00
+ Version 2.00-rc2
- 1st November 2000
+ 14th November 2000
Copyright (C) 1996-2000 Andy Wardley. All Rights Reserved
Copyright (C) 1998-2000 Canon Research Centre Europe Ltd.
@@ -108,7 +108,7 @@ actually require this module to use the Template Toolkit itself, but
will do if you plan to use the 'ttree' utility (which you really
should). It is available from CPAN:
- http://www.cpan.org/modules/by-module/AppConfig/
+ http://www.cpan.org/authors/Andy_Wardley/
The Template Toolkit implements a "plugin" architecture which allow
you to incorporate the functionality of virtually any Perl module into
@@ -126,12 +126,17 @@ all available from CPAN, should be installed:
Plugin Requires
------ --------
- Autoformat Text::Autoformat 1.02+
+ Autoformat Text::Autoformat 1.03+
DBI DBI 1.14+ and relevant DBD drivers
- XML::DOM XML::DOM 1.25+ (requires XML::Parser 2.23+)
+ XML::DOM XML::DOM 1.27+ (requires XML::Parser 2.23+)
XML::RSS XML::RSS 0.9+ (ditto)
XML::XPath XML::XPath 1.00+ (ditto)
+NOTE: The Text::Autoformat plugin may fail on tests 23 and 25 with
+versions of Perl prior to 5.6.0 when using a locale which has a decimal
+separator other than '.'. See the KNOWN BUGS section of the TODO file
+for further information.
+
OBTAINING AND INSTALLING THE TEMPLATE TOOLKIT
---------------------------------------------
View
25 TODO
@@ -40,10 +40,11 @@ enhancements planned for the Template Toolkit.
# point in the future.
#------------------------------------------------------------------------
-* URL plugin may be incorrect in not escaping certain characters such as
- '&'. I need to check the standards, or wait for someone more
- knowledgeable in these matters to fix it for me, or otherwise give it
- a clean bill of health (hint, hint :-).
+* The Text::Autoformat module has some problems with versions of Perl
+ prior to 5.6.0 when using a locale which has a decimal separator
+ other than '.' (e.g. Swedish, which uses ','). Damian has been made
+ aware of the problem. For now, the Makefile.PL issues a warning but
+ continues regardless.
* The childrenTotemplate() and allChildrenToTemplate() methods of the
XML::DOM can send Perl into a deep recursive loop. This will be
@@ -85,6 +86,22 @@ enhancements planned for the Template Toolkit.
the 'loop' reference to test 'first', 'last', etc., against, but it would
be faster for those cases when you didn't need it.
+* It should be possible to access template BLOCK definitions via the
+ 'template' variable. This would be particularly useful in conjunction
+ with the PRE_PROCESS, PROCESS and/or POST_PROCESS options, e.g.
+
+ <table>
+ <tr>
+ <td>[% INCLUDE $template.blocks.sidebar %]</td>
+ <td>[% INCLUDE $template %]
+ </tr>
+ </table>
+
+ Currently you *can* access these blocks, but the stash automatically
+ calls these unblessed subs, generating an error due to the fact that
+ no context reference is passed to the sub to allow it to act as a
+ template.
+
* It would be useful if template components had some notion of inheritance
so that a 'derived' component could call on the 'super' component.
This can probably best be acheived by means of a Template::Component
View
2  lib/Template.pm
@@ -39,7 +39,7 @@ use File::Path;
## This is the main version number for the Template Toolkit.
## It is extracted by ExtUtils::MakeMaker and inserted in various places.
-$VERSION = '2.00-rc1';
+$VERSION = '2.00-rc2';
$ERROR = '';
$DEBUG = 0;
View
2  lib/Template/Directive.pm
@@ -70,7 +70,7 @@ sub template {
return <<EOF;
sub {
- my \$context = shift;
+ my \$context = shift || die "template sub called without context\n";
my \$stash = \$context->stash;
my \$output = '';
my \$error;
View
2  lib/Template/Document.pm
@@ -237,7 +237,7 @@ sub write_perl_file {
map {
my $x = $metadata->{ $_ };
$x =~ s/['\\]/\\$1/g;
- "'$_' => '$x',";
+ "'$_' => '$x',\n";
} keys %$metadata);
local *CFH;
View
13 lib/Template/Iterator.pm
@@ -81,7 +81,10 @@ sub new {
$data = [ map { { key => $_, value => $data->{ $_ } } }
sort keys %$data ];
}
- elsif (! UNIVERSAL::isa($data, 'ARRAY')) {
+ elsif (UNIVERSAL::can($data, 'as_list')) {
+ $data = $data->as_list();
+ }
+ elsif (ref $data ne 'ARRAY') {
# coerce any non-list data into an array reference
$data = [ $data ] ;
}
@@ -118,7 +121,8 @@ sub get_first {
# initialise various counters, flags, etc.
@$self{ qw( SIZE MAX INDEX COUNT FIRST LAST ) }
- = ( $size, $size - 1, $index, 1, 1, $size > 1 ? 0 : 1 );
+ = ( $size, $size - 1, $index, 1, 1, $size > 1 ? 0 : 1, undef );
+ @$self{ qw( PREV NEXT ) } = ( undef, $self->{ _DATASET }->[ $index + 1 ]);
return $self->{ _DATASET }->[ $index ];
}
@@ -136,6 +140,7 @@ sub get_first {
sub get_next {
my $self = shift;
my ($max, $index) = @$self{ qw( MAX INDEX ) };
+ my $data = $self->{ _DATASET };
# warn about incorrect usage
unless (defined $index) {
@@ -150,8 +155,8 @@ sub get_next {
$index++;
@$self{ qw( INDEX COUNT FIRST LAST ) }
= ( $index, $index + 1, 0, $index == $max ? 1 : 0 );
-
- return $self->{ _DATASET }->[ $index ]; ## RETURN ##
+ @$self{ qw( PREV NEXT ) } = @$data[ $index - 1, $index + 1 ];
+ return $data->[ $index ]; ## RETURN ##
}
else {
return (undef, Template::Constants::STATUS_DONE); ## RETURN ##
View
10 lib/Template/Plugin.pm
@@ -133,11 +133,17 @@ sub fail {
sub AUTOLOAD {
my $self = shift;
my $method = $AUTOLOAD;
- my $delegate = $self->{ _DELEGATE } || return;
$method =~ s/.*:://;
return if $method eq 'DESTROY';
- $delegate->$method(@_);
+
+ if (ref $self eq 'HASH') {
+ my $delegate = $self->{ _DELEGATE } || return;
+ return $delegate->$method(@_);
+ }
+ my ($pkg, $file, $line) = caller();
+ warn "no such '$method' method called on $self at $file line $line\n";
+ return undef;
}
View
73 lib/Template/Plugin/DBI.pm
@@ -34,7 +34,7 @@ use vars qw( $VERSION $DEBUG );
use base qw( Template::Plugin );
#$VERSION = sprintf("%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/) - 1;
-$VERSION = 1.0;
+$VERSION = 1.02;
$DEBUG = 0 unless defined $DEBUG;
@@ -62,7 +62,7 @@ sub new {
#------------------------------------------------------------------------
-# connect( $data_source, $username, $password )
+# connect( $data_source, $username, $password, $attributes )
# connect( { data_source => 'dbi:driver:database'
# username => 'foo'
# password => 'bar' } )
@@ -116,8 +116,9 @@ sub connect {
if $self->{ _DBH } && $self->{ _DBH_CONNECT };
# open new connection
- $self->{ _DBH } = DBI->connect( $dsn, $user, $pass,
- { PrintError => 0 } )
+ $params->{ PrintError } = 0
+ unless defined $params->{ PrintError };
+ $self->{ _DBH } = DBI->connect( $dsn, $user, $pass, $params )
|| return $self->_throw("DBI connect failed: $DBI::errstr");
# store the connection parameters
@@ -326,14 +327,11 @@ sub get_first {
my $self = shift;
# set some status variables into $self
- $self->{ FIRST } = 2;
- $self->{ LAST } = 0;
- $self->{ NUMBER } = 0;
- $self->{ INDEX } = -1;
+ @$self{ qw( PREV ITEM FIRST LAST COUNT INDEX ) }
+ = ( undef, undef, 2, 0, 0, -1 );
- # 'number' is the official iterator counter name but earlier version
- # of the DBI plugin used 'count'
- $self->{ COUNT } = 0;
+ # support 'number' as an alias for 'count' for backwards compatability
+ $self->{ NUMBER } = 0;
# NOTE: 'size' and 'max' should also be supported. This should
# probably trigger a get_all() to determine the size of the result
@@ -362,23 +360,20 @@ sub get_next {
my $self = shift;
my ($data, $fixup);
- print STDERR "get_next() called\n" if $DEBUG;
-
- # increment the 'index' and 'number' counts
+ # increment the 'index' and 'count' counts
$self->{ INDEX }++;
- $self->{ NUMBER }++;
-
- # support 'count' for backwards compatability
- $self->{ COUNT }++;
+ $self->{ COUNT }++;
+ $self->{ NUMBER }++; # 'number' is old name for 'count'
# decrement the 'first-record' flag
$self->{ FIRST }-- if $self->{ FIRST };
- # we should have a row already cache in _ROWCACHE
+ # we should have a row already cache in NEXT
return (undef, Template::Constants::STATUS_DONE)
- unless $data = $self->{ _ROWCACHE };
+ unless $data = $self->{ NEXT };
- print STDERR "get_next() calling _fetchrow()\n" if $DEBUG;
+ # set PREV to be current ITEM from last iteration
+ $self->{ PREV } = $self->{ ITEM };
# look ahead to the next row so that the rowcache is refilled
$self->_fetchrow();
@@ -392,8 +387,7 @@ sub get_next {
}
}
- print STDERR "get_next() returning $data (STATUS_OK)\n" if $DEBUG;
-
+ $self->{ ITEM } = $data;
return ($data, Template::Constants::STATUS_OK);
}
@@ -411,11 +405,11 @@ sub _fetchrow {
my $data = $sth->fetchrow_hashref() || do {
$self->{ LAST } = 1;
- $self->{ _ROWCACHE } = undef;
+ $self->{ NEXT } = undef;
$sth->finish();
return;
};
- $self->{ _ROWCACHE } = $data;
+ $self->{ NEXT } = $data;
return;
}
@@ -490,6 +484,11 @@ forms of 'username' and 'password', respectively.
user => 'username',
pass => 'password') %]
+Any additional DBI attributes can be specified as named parameters.
+The 'PrintError' attribute defaults to 0 unless explicitly set true.
+
+ [% USE DBI(dsn, user, pass, ChopBlanks=1) %]
+
Methods can then be called on the plugin object using the familiar dotted
notation:
@@ -599,9 +598,27 @@ L<Template> and L<DBI>.
=head1 AUTHORS
-The DBI plugin was written by Simon A Matthews,
-E<lt>sam@knowledgepool.comE<gt>, with minor modifications and further
-documentation provided by Andy Wardley E<lt>abw@kfs.orgE<gt>.
+The DBI plugin was written by Simon A Matthews,
+E<lt>sam@knowledgepool.comE<gt>, with contributions from Andy Wardley
+E<lt>abw@kfs.orgE<gt>.
+
+=head1 HISTORY
+
+=over 4
+
+=item 1.01 2000/11/03 abw
+
+Modified connect method to pass all named arguments to DBI. e.g.
+
+ [% USE DBI(dsn, user, pass, ChopBlanks=1) %]
+
+=item 1.02 2000/11/14 abw
+
+Added prev() and next() methods to Template::Plugin::DBI:Iterator to
+return the previous and next items in the iteration set or undef if
+not available.
+
+=back
=head1 COPYRIGHT
View
5 lib/Template/Plugin/Datafile.pm
@@ -34,7 +34,6 @@ use Template::Plugin;
$VERSION = sprintf("%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/);
-
sub new {
my ($class, $context, $filename, $params) = @_;
my ($delim, $line, @fields, @data, @results);
@@ -77,11 +76,13 @@ sub new {
push(@$self, \%record);
}
+# return $self;
bless $self, $class;
}
-sub AUTOLOAD {
+sub as_list {
+ return $_[0];
}
View
28 lib/Template/Plugin/URL.pm
@@ -49,7 +49,7 @@ sub new {
my $newbase = shift unless ref $_[0] eq 'HASH';
my $newargs = shift || { };
my $combo = { %$args, %$newargs };
- my $urlargs = join('&',
+ my $urlargs = join('&amp;',
map { "$_=" . escape($combo->{ $_ }) }
grep { defined $combo->{ $_ } }
keys %$combo);
@@ -90,19 +90,19 @@ Template::Plugin::URL - constructs query URL's with parameters
[% USE url('/cgi-bin/foo.pl') %]
[% url(debug = 1, id = 123) %]
- # ==> /cgi/bin/foo.pl?debug=1&id=123
+ # ==> /cgi/bin/foo.pl?debug=1&amp;id=123
[% USE mycgi = url('/cgi-bin/bar.pl', mode='browse', debug=1) %]
[% mycgi %]
- # ==> /cgi/bin/bar.pl?mode=browse&debug=1
+ # ==> /cgi/bin/bar.pl?mode=browse&amp;debug=1
[% mycgi(mode='submit') %]
- # ==> /cgi/bin/bar.pl?mode=submit&debug=1
+ # ==> /cgi/bin/bar.pl?mode=submit&amp;debug=1
[% mycgi(debug='d2 p0', id='D4-2k[4]') %]
- # ==> /cgi-bin/bar.pl?mode=browse&debug=d2%20p0&id=D4-2k%5B4%5D
+ # ==> /cgi-bin/bar.pl?mode=browse&amp;debug=d2%20p0&amp;id=D4-2k%5B4%5D
=head1 DESCRIPTION
@@ -127,7 +127,7 @@ For the above three examples, these will produce the following outputs:
http://www.somewhere.com/cgi-bin/foo.pl
/cgi-bin/bar.pl?mode=browse
- /cgi-bin/baz.pl?mode=browse&debug=1
+ /cgi-bin/baz.pl?mode=browse&amp;debug=1
Additional parameters may be also be specified:
@@ -135,9 +135,9 @@ Additional parameters may be also be specified:
Which, for the same three examples, produces:
- http://www.somewhere.com/cgi-bin/foo.pl?mode=submit&id=wiz
- /cgi-bin/bar.pl?mode=submit&id=wiz
- /cgi-bin/baz.pl?mode=browse&debug=1&id=wiz
+ http://www.somewhere.com/cgi-bin/foo.pl?mode=submit&amp;id=wiz
+ /cgi-bin/bar.pl?mode=browse&amp;id=wiz
+ /cgi-bin/baz.pl?mode=browse&amp;debug=1&amp;id=wiz
A new base URL may also be specified as the first option:
@@ -146,8 +146,8 @@ A new base URL may also be specified as the first option:
producing
/cgi-bin/waz.pl?test=1
- /cgi-bin/waz.pl?mode=browse&test=1
- /cgi-bin/waz.pl?mode=browse&debug=1&test=1
+ /cgi-bin/waz.pl?mode=browse&amp;test=1
+ /cgi-bin/waz.pl?mode=browse&amp;debug=1&amp;test=1
The ordering of the parameters is non-deterministic due to fact that
@@ -170,6 +170,12 @@ as per regular Template Toolkit syntax.
[% mycgi(debug=1) %]
+Note that in the following line, additional parameters are seperated
+by '&amp;', while common usage on the Web is to just use '&'. '&amp;'
+is actually the Right Way to do it. See this URL for more information:
+http://ppewww.ph.gla.ac.uk/~flavell/www/formgetbyurl.html
+
+ /cgi-bin/waz.pl?mode=browse&amp;debug=1&amp;test=1
=head1 AUTHOR
View
115 lib/Template/Plugin/XML/DOM.pm
@@ -33,7 +33,8 @@ use XML::DOM;
use base qw( Template::Plugin );
use vars qw( $VERSION );
-$VERSION = sprintf("%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/);
+#$VERSION = sprintf("%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/);
+$VERSION = 2.5;
#------------------------------------------------------------------------
@@ -116,9 +117,11 @@ sub parse {
eval { $doc = $parser->$method($content) } and not $@
or return $self->_throw("failed to parse $about: $@");
- # update XML::DOM::Document to contain config details
- my @args = qw( _CONTEXT _PREFIX _SUFFIX _VERBOSE _NOSPACE _DEEP _DEFAULT );
- @$doc{ @args } = @$self{ @args };
+ # update XML::DOM::Document _UserData to contain config details
+ $doc->[ XML::DOM::Node::_UserData ] = {
+ map { ( $_ => $self->{ $_ } ) }
+ qw( _CONTEXT _PREFIX _SUFFIX _VERBOSE _NOSPACE _DEEP _DEFAULT ),
+ };
# keep track of all DOM docs for subsequent dispose()
push(@{ $self->{ _DOCS } }, $doc);
@@ -152,7 +155,7 @@ sub DESTROY {
# call dispose() on each document produced by this parser
foreach my $doc (@{ $self->{ _DOCS } }) {
- delete $doc->{ _CONTEXT };
+ delete $doc->[ XML::DOM::Node::_UserData ]->{ _CONTEXT };
$doc->dispose();
}
delete $self->{ _CONTEXT };
@@ -215,16 +218,18 @@ sub allChildrenToTemplate {
sub _args {
my $self = shift;
- my $doc = $self->{ Doc };
my $args = ref $_[-1] eq 'HASH' ? pop(@_) : { };
+ my $doc = $self->getOwnerDocument() || $self;
+ my $data = $doc->[ XML::DOM::Node::_UserData ];
return {
- prefix => @_ ? shift : $args->{ prefix } || $doc->{ _PREFIX },
- suffix => @_ ? shift : $args->{ suffix } || $doc->{ _SUFFIX },
- verbose => $args->{ verbose } || $doc->{ _VERBOSE },
- nospace => $args->{ nospace } || $doc->{ _NOSPACE },
- deep => $args->{ deep } || $doc->{ _DEEP },
- default => $args->{ default } || $doc->{ _DEFAULT },
+ prefix => @_ ? shift : $args->{ prefix } || $data->{ _PREFIX },
+ suffix => @_ ? shift : $args->{ suffix } || $data->{ _SUFFIX },
+ verbose => $args->{ verbose } || $data->{ _VERBOSE },
+ nospace => $args->{ nospace } || $data->{ _NOSPACE },
+ deep => $args->{ deep } || $data->{ _DEEP },
+ default => $args->{ default } || $data->{ _DEFAULT },
+ context => $data->{ _CONTEXT },
};
}
@@ -248,14 +253,14 @@ sub _args {
sub _template_node {
my $node = shift || die "no XML::DOM::Node reference\n";
- my $args = shift || { };
+ my $args = shift || die "no XML::DOM args passed to _template_node\n";
my $vars = shift || { };
- my $context = $node->{ Doc }->{ _CONTEXT };
+ my $context = $args->{ context } || die "no context in XML::DOM args\n";
my $template;
my $output = '';
- # if this is not a tag then it is text so output it
- unless ($node->{ TagName }) {
+ # if this is not an element then it is text so output it
+ unless ($node->getNodeType() == XML::DOM::ELEMENT_NODE ) {
if ($args->{ verbose }) {
$output = $node->toString();
$output =~ s/\s+$// if $args->{ nospace };
@@ -263,7 +268,7 @@ sub _template_node {
}
else {
my $element = ( $args->{ prefix } || '' )
- . $node->{ TagName }
+ . $node->getTagName()
. ( $args->{ suffix } || '' );
# locate a template by name built from prefix, tagname and suffix
@@ -274,8 +279,11 @@ sub _template_node {
$template = $element unless $template;
# copy 'children' and 'prune' callbacks into node object (see AUTOLOAD)
- $node->{ _TT_CHILDREN } = $vars->{ children };
- $node->{ _TT_PRUNE } = $vars->{ prune };
+ my $doc = $node->getOwnerDocument() || $node;
+ my $data = $doc->[ XML::DOM::Node::_UserData ];
+
+ $data->{ _TT_CHILDREN } = $vars->{ children };
+ $data->{ _TT_PRUNE } = $vars->{ prune };
# add node reference to existing vars hash
$vars->{ node } = $node;
@@ -284,8 +292,8 @@ sub _template_node {
# break any circular references
delete $vars->{ node };
- delete $node->{ _TT_CHILDREN };
- delete $node->{ _TT_PRUNE };
+ delete $data->{ _TT_CHILDREN };
+ delete $data->{ _TT_PRUNE };
}
return $output;
@@ -311,8 +319,8 @@ sub _template_node {
sub _template_kids {
my $node = shift || die "no XML::DOM::Node reference\n";
- my $args = shift || { };
- my $context = $node->{ Doc }->{ _CONTEXT };
+ my $args = shift || die "no XML::DOM args passed to _template_kids\n";
+ my $context = $args->{ context } || die "no context in XML::DOM args\n";
my $output = '';
foreach my $kid ( $node->getChildNodes() ) {
@@ -352,10 +360,13 @@ sub AUTOLOAD {
$method =~ s/.*:://;
return if $method eq 'DESTROY';
+ my $doc = $self->getOwnerDocument() || $self;
+ my $data = $doc->[ XML::DOM::Node::_UserData ];
+
# call 'content' or 'prune' callbacks, if defined (see _template_node())
return &$attrib()
if ($method =~ /^children|prune$/)
- && defined($attrib = $self->{ "_TT_\U$method" })
+ && defined($attrib = $data->{ "_TT_\U$method" })
&& ref $attrib eq 'CODE';
return $attrib
@@ -410,11 +421,14 @@ Template::Plugin::XML::DOM - Template Toolkit plugin to the XML::DOM module
=head1 PRE-REQUISITES
-This plugin requires that the XML::Parser and XML::DOM modules be
-installed. These are available from CPAN:
+This plugin requires that the XML::Parser (2.19 or later) and XML::DOM
+(1.27 or later) modules be installed. These are available from CPAN:
http://www.cpan.org/modules/by-module/XML
+Note that the XML::DOM module is now distributed as part of the
+'libxml-enno' bundle.
+
=head1 DESCRIPTION
This is a Template Toolkit plugin interfacing to the XML::DOM module.
@@ -425,11 +439,11 @@ called on the plugin to parse an XML stream into a DOM document.
[% USE dom = XML.DOM %]
[% doc = dom.parse('/tmp/myxmlfile') %]
-NOTE: previous versions of this XML::DOM plugin expected a filename to
-be passed as an argument to the constructor. This is no longer supported
-due to the fact that it caused a serious memory leak. We apologise for
-the inconvenience but must insist that you change your templates as
-shown:
+NOTE: earlier versions of this XML::DOM plugin expected a filename to
+be passed as an argument to the constructor. This is no longer
+supported due to the fact that it caused a serious memory leak. We
+apologise for the inconvenience but must insist that you change your
+templates as shown:
# OLD STYLE: now fails with a warning
[% USE dom = XML.DOM('tmp/myxmlfile') %]
@@ -607,15 +621,7 @@ Similar to childrenToTemplate() but processing all descendants (i.e. children
of children and so on) recursively. This is identical to calling the
childrenToTemplate() method with the 'deep' flag set to any true value.
-=head1 BUGS
-
-The childrenToTemplate() and allChildrenToTemplate() methods can easily
-slip into deep recursion.
-
-The 'verbose' and 'nospace' options are not documented. They may
-change in the near future.
-
-=head1 AUTHOR
+=head1 AUTHORS
This plugin module was written by Andy Wardley E<lt>abw@kfs.orgE<gt>
and Simon Matthews E<lt>sam@knowledgepool.comE<gt>.
@@ -625,13 +631,36 @@ Cooper E<lt>coopercl@sch.ge.comE<gt>. It extends the the XML::Parser
module, also by Clark Cooper which itself is built on James Clark's expat
library.
-=head1 REVISION
+=head1 VERSION
+
+This is version 2.5 of the XML::DOM plugin.
-$Revision$
+=head1 HISTORY
+
+Version 2.5 : updated for use with version 1.27 of the XML::DOM module.
+
+=over 4
+
+=item *
+
+XML::DOM 1.27 now uses array references as the underlying data type
+for DOM nodes instead of hash array references. User data is now
+bound to the _UserData node entry instead of being forced directly
+into the node hash.
+
+=back
+
+=head1 BUGS
+
+The childrenToTemplate() and allChildrenToTemplate() methods can easily
+slip into deep recursion.
+
+The 'verbose' and 'nospace' options are not documented. They may
+change in the near future.
=head1 COPYRIGHT
-Copyright (C) 2000 Andy Wardley. All Rights Reserved.
+Copyright (C) 2000 Andy Wardley, Simon Matthews. All Rights Reserved.
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
View
17 lib/Template/Provider.pm
@@ -676,7 +676,12 @@ sub _compile {
# call parser to compile template into Perl code
if ($parsedoc = $parser->parse($text, $data)) {
- # NOTE: probably shouldn't write the file until we know it compiled OK
+
+ $parsedoc->{ METADATA } = {
+ 'name' => $data->{ name },
+ 'modtime' => $data->{ time },
+ %{ $parsedoc->{ METADATA } },
+ };
# write the Perl code to the file $compfile, if defined
if ($compfile) {
@@ -690,15 +695,7 @@ sub _compile {
$parsedoc);
print STDERR "error: $error" if $error;
}
-
- # call Template::Document constructor to compile Perl code and
- # return a cohesive object encapsulating the template, additional
- # BLOCKs and metadata
- $parsedoc->{ METADATA } = {
- 'name' => $data->{ name },
- 'modtime' => $data->{ time },
- %{ $parsedoc->{ METADATA } },
- };
+
unless ($error) {
return $data ## RETURN ##
if $data->{ data } = Template::Document->new($parsedoc);
View
10 lib/Template/Stash.pm
@@ -69,6 +69,14 @@ $HASH_OPS = {
@$hash{ keys %$imp } = values %$imp;
return '';
},
+ 'sort' => sub {
+ my ($hash) = @_;
+ [ sort { lc $hash->{$a} cmp lc $hash->{$b} } (keys %$hash) ];
+ },
+ 'nsort' => sub {
+ my ($hash) = @_;
+ [ sort { $hash->{$a} <=> $hash->{$b} } (keys %$hash) ];
+ },
defined $HASH_OPS ? %$HASH_OPS : (),
};
@@ -435,7 +443,7 @@ sub _dotop {
@result = &$value(@$args);
}
elsif ($@) {
- die $@; ## DIE
+ @result = (undef, $@);
}
}
elsif (($value = $SCALAR_OPS->{ $item }) && ! $lvalue) {
View
32 t/autoform.t
@@ -20,6 +20,7 @@ use strict;
use lib qw( ../lib );
use Template qw( :status );
use Template::Test;
+use POSIX qw( localeconv );
$^W = 1;
$Template::Test::DEBUG = 0;
@@ -32,7 +33,18 @@ if ($@) {
exit(0);
}
-test_expect(\*DATA, { POST_CHOMP => 1 });
+# for testing known bug with locales that don't use '.' as a decimal
+# separator - see TODO file.
+# POSIX::setlocale( &POSIX::LC_ALL, 'sv_SE' );
+
+my $loc = localeconv;
+my $dec = $loc->{ decimal_point };
+
+my $vars = {
+ decimal => $dec,
+};
+
+test_expect(\*DATA, { POST_CHOMP => 1 }, $vars);
#------------------------------------------------------------------------
@@ -154,10 +166,11 @@ Item Description Cost
[% autoformat('foo', 'The Foo Item', 123.545) %]
[% autoformat('bar', 'The Bar Item', 456.789) %]
-- expect --
+-- process --
Item Description Cost
===================================
-foo The Foo Item 123.55
-bar The Bar Item 456.79
+foo The Foo Item 123[% decimal %]55
+bar The Bar Item 456[% decimal %]79
-- test --
[% USE autoformat(form => '>>>.<<', numeric => 'AllPlaces') %]
@@ -165,11 +178,10 @@ bar The Bar Item 456.79
FOREACH n = [ 123, 34.54, 99 ] +%]
[% autoformat(987, 654.32) %]
-- expect --
-123.00
- 34.54
- 99.00
-
-987.00
-654.32
-
+-- process --
+123[% decimal %]00
+ 34[% decimal %]54
+ 99[% decimal %]00
+987[% decimal %]00
+654[% decimal %]32
View
5 t/compile1.t
@@ -64,5 +64,10 @@ This is a more complex file which includes some BLOCK definitions
This is the footer, author: abw, version: 3.14
- 3 - 2 - 1
+-- test --
+[% INCLUDE baz %]
+-- expect --
+This is the baz file, a:
+
View
6 t/compile2.t
@@ -34,6 +34,12 @@ my $ttcfg = {
ok( -f "$dir/foo.ttc" );
ok( -f "$dir/complex.ttc" );
+# ensure template metadata is saved in compiled file (bug fixed in v2.00)
+my $out = '';
+my $tt = Template->new($ttcfg);
+ok( $tt->process('baz', { showname => 1 }, \$out) );
+ok( $out =~ /^name: baz/ );
+
# we're going to hack on the foo.ttc file to change some key text.
# this way we can tell that the template was loaded from the compiled
# version and not the source.
View
103 t/dbi.t
@@ -17,16 +17,19 @@
#========================================================================
use strict;
-use lib qw( . ./t ./blib/lib ../blib/lib );
+use lib qw( . ./t ../lib ./blib/lib ../blib/lib );
use vars qw( $DEBUG $run $dsn $user $pass );
use Template::Test;
$^W = 1;
$DEBUG = 0;
-#$Template::Test::PRESERVE = 1;
+$Template::Test::PRESERVE = 1;
eval "use DBI";
-exit if $@;
+if ($@) {
+ print "1..0\n";
+ exit(0);
+}
# load the configuration file created by Makefile.PL which defines
# the $run, $dsn, $user and $pass variables.
@@ -36,11 +39,31 @@ unless ($run) {
exit(0);
}
+my $attr = {
+ PrintError => 0,
+ ChopBlanks => 1,
+};
+
+my $dbh;
+eval {
+ $dbh = DBI->connect($dsn, $user, $pass, $attr);
+};
-my $dbh = DBI->connect($dsn, $user, $pass, { PrintError => 0 }) || do {
+if ($@ || ! $dbh) {
+ warn <<EOF;
+DBI connect() failed:
+ $DBI::errstr
+
+Please ensure that your database server is running and that you specified
+the correct connection parameters. If necessary, re-run the Makefile.PL
+and specify new parameters, or answer 'n' when prompted:
+
+ - Do you want to run the DBI tests?
+
+EOF
ntests(1);
ok(0);
- die "DBI connect() failed: $DBI::errstr\n";
+ exit(0);
};
init_database($dbh);
@@ -50,6 +73,7 @@ my $vars = {
dsn => $dsn,
user => $user,
pass => $pass,
+ attr => $attr,
};
test_expect(\*DATA, undef, $vars);
@@ -65,6 +89,10 @@ $dbh->disconnect();
sub init_database {
my $dbh = shift;
+ # ensure tables don't already exist (in case previous test run failed).
+ sql_query($dbh, 'DROP TABLE usr', 1);
+ sql_query($dbh, 'DROP TABLE grp', 1);
+
# create some tables
sql_query($dbh, 'CREATE TABLE grp (
id Char(16),
@@ -96,17 +124,17 @@ sub init_database {
#------------------------------------------------------------------------
-# sql_query($dbh, $sql)
+# sql_query($dbh, $sql, $quiet)
#------------------------------------------------------------------------
sub sql_query {
- my ($dbh, $sql) = @_;
+ my ($dbh, $sql, $quiet) = @_;
my $sth = $dbh->prepare($sql)
|| warn "prepare() failed: $DBI::errstr\n";
$sth->execute()
- || warn "execute() failed: $DBI::errstr\n";
+ || $quiet || warn "execute() failed: $DBI::errstr\n";
$sth->finish();
}
@@ -134,7 +162,7 @@ __END__
-- test --
[% USE DBI -%]
-[% DBI.connect(dsn, user, pass) -%]
+[% DBI.connect(dsn, user, pass, ChopBlanks => 1) -%]
[% FOREACH user = DBI.query("SELECT name FROM usr WHERE id='abw'") -%]
* [% user.name %]
[% END %]
@@ -144,7 +172,7 @@ __END__
-- test --
[% USE dbi -%]
-[% dbi.connect(dsn, user, pass) -%]
+[% dbi.connect(dsn, user, pass, ChopBlanks => 1) -%]
[% FOREACH user = dbi.query("SELECT name FROM usr WHERE id='sam'") -%]
* [% user.name %]
[% END %]
@@ -154,7 +182,7 @@ __END__
-- test --
[% USE db = DBI -%]
-[% db.connect(dsn, user, pass) -%]
+[% db.connect(dsn, user, pass, ChopBlanks => 1) -%]
[% FOREACH user = db.query("SELECT name FROM usr WHERE id='hans'") -%]
* [% user.name %]
[% END %]
@@ -163,7 +191,10 @@ __END__
* Hans von Lengerke
-- test --
-[% USE db = DBI(data_source => dsn, username => user, password => pass) -%]
+[% USE db = DBI(data_source => dsn,
+ username => user,
+ password => pass,
+ ChopBlanks => 1) -%]
[% FOREACH user = db.query("SELECT name FROM usr WHERE id='mrp'") -%]
* [% user.name %]
[% END %]
@@ -173,7 +204,7 @@ __END__
-- test --
[% USE dbi -%]
-[% dbi.connect(dsn=dsn, user=user, pass=pass) -%]
+[% dbi.connect(dsn=dsn, user=user, pass=pass ChopBlanks=1) -%]
[% FOREACH user = dbi.query("SELECT name FROM usr WHERE id='abw'") -%]
* [% user.name %]
[% END %]
@@ -198,7 +229,7 @@ DBI error - no connection
#------------------------------------------------------------------------
-- test --
-[% USE DBI(dsn, user, pass) -%]
+[% USE DBI(dsn, user, pass, attr) -%]
[% DBI.disconnect -%]
[% TRY;
DBI.query('blah blah');
@@ -210,9 +241,9 @@ DBI error - no connection
DBI error - no connection
-- test --
-[% USE DBI(dsn, user, pass) -%]
+[% USE DBI(dsn, user, pass, attr) -%]
[% DBI.disconnect -%]
-[% DBI.connect(dsn, user, pass) -%]
+[% DBI.connect(dsn, user, pass, attr) -%]
[% FOREACH user = DBI.query("SELECT name FROM usr WHERE id='abw'") -%]
* [% user.name %]
[% END %]
@@ -226,7 +257,7 @@ DBI error - no connection
#------------------------------------------------------------------------
-- test --
-[% USE dbi(dsn, user, pass) -%]
+[% USE dbi(dsn, user, pass, attr) -%]
[% FOREACH user = dbi.query('SELECT * FROM usr ORDER BY id') -%]
[% loop.number %]: [% user.id %] - [% user.name %]
[% END %]
@@ -238,7 +269,7 @@ DBI error - no connection
-- test --
# DBI plugin before TT 2.00 used 'count' instead of 'number'
-[% USE dbi(dsn, user, pass) -%]
+[% USE dbi(dsn, user, pass, attr) -%]
[% FOREACH user = dbi.query('SELECT * FROM usr ORDER BY id') -%]
[% loop.count %]: [% user.id %] - [% user.name %]
[% END %]
@@ -254,7 +285,7 @@ DBI error - no connection
#------------------------------------------------------------------------
-- test --
-[% USE dbi(dsn, user, pass) -%]
+[% USE dbi(dsn, user, pass, attr) -%]
[% FOREACH group = dbi.query('SELECT * FROM grp
ORDER BY id') -%]
Group [% loop.number %]: [% group.name %] ([% group.id %])
@@ -275,11 +306,29 @@ Group 2: The Foo Group (foo)
#------------------------------------------------------------------------
+# test prev and next iterator methods
+#------------------------------------------------------------------------
+
+-- test --
+[% USE dbi(dsn, user, pass, attr) -%]
+[% FOREACH user = dbi.query('SELECT * FROM usr ORDER BY id') -%]
+[% loop.prev ? "[$loop.prev.id] " : "[no prev] " -%]
+[% user.id %] - [% user.name -%]
+[% loop.next ? " [$loop.next.id]" : " [no next]" %]
+[% END %]
+-- expect --
+[no prev] abw - Andy Wardley [hans]
+[abw] hans - Hans von Lengerke [mrp]
+[hans] mrp - Martin Portman [sam]
+[mrp] sam - Simon Matthews [no next]
+
+
+#------------------------------------------------------------------------
# test do() to perform SQL queries without returning results
#------------------------------------------------------------------------
-- test --
-[% USE DBI(dsn, user, pass) -%]
+[% USE DBI(dsn, user, pass, attr) -%]
[% CALL DBI.do("INSERT INTO usr VALUES ('numb', 'Numb Nuts', 'bar')") -%]
[% FOREACH user = DBI.query("SELECT * FROM usr
WHERE grp = 'bar'
@@ -293,7 +342,7 @@ Group 2: The Foo Group (foo)
* Numb Nuts (numb)
-- test --
-[% USE dbi(dsn, user, pass) -%]
+[% USE dbi(dsn, user, pass, attr) -%]
[% IF dbi.do("DELETE FROM usr WHERE id = 'numb'") -%]
deleted the user
[% ELSE -%]
@@ -315,7 +364,7 @@ deleted the user
#------------------------------------------------------------------------
-- test --
-[% USE dbi(dsn=dsn, user=user, pass=pass) -%]
+[% USE dbi(dsn=dsn, user=user, pass=pass, ChopBlanks=1) -%]
[% user_query = dbi.prepare('SELECT * FROM usr
WHERE grp = ?
ORDER BY id') -%]
@@ -341,7 +390,7 @@ Bar:
-- test --
-[% USE dbi(dsn, user, pass) -%]
+[% USE dbi(dsn, user, pass, attr) -%]
[% group_query = dbi.prepare('SELECT * FROM grp
ORDER BY id') -%]
[% user_query = dbi.prepare('SELECT * FROM usr
@@ -365,7 +414,7 @@ Group 2 : The Foo Group
-- test --
-[% USE dbi(dsn, user, pass) -%]
+[% USE dbi(dsn, user, pass, attr) -%]
[% CALL dbi.prepare('SELECT * FROM usr WHERE id = ?') -%]
[% FOREACH uid = [ 'abw', 'sam' ] -%]
===
@@ -390,10 +439,10 @@ Group 2 : The Foo Group
-- test --
[% USE dbi(dbh => dbh) -%]
-[% FOREACH user = dbi.query("SELECT * FROM usr WHERE id = 'abw'") -%]
-* [% user.name %]
+[% FOREACH dbi.query("SELECT * FROM usr WHERE id = 'abw'") -%]
+* [% name %]
[% END -%]
-[% dbi.connect(dsn, user, pass) -%]
+[% dbi.connect(dsn, user, pass, ChopBlanks=1) -%]
[% FOREACH user = dbi.query("SELECT * FROM usr WHERE id = 'abw'") -%]
* [% user.name %]
[% END -%]
View
11 t/dom.t
@@ -18,20 +18,23 @@
use strict;
use lib qw( ./lib ../lib );
+use lib qw( /home/abw/perl/modules/XML/libxml-enno-1.02/lib );
use Template;
use Template::Test;
use Cwd qw( abs_path );
$^W = 1;
#$Template::Test::DEBUG = 1;
+$Template::Test::PRESERVE = 1;
# I hate having to do this
my $shut_up_warnings = $XML::DOM::VERSION;
eval "use XML::DOM";
-
-# XML::DOM version 1.25 (and earlier?) dump core with Perl 5.006
-exit if $@ || ($] == 5.006 && $XML::DOM::VERSION <= 1.25);
+if ($@ || $XML::DOM::VERSION < 1.27) {
+ print "1..0\n";
+ exit(0);
+}
# account for script being run in distribution root or 't' directory
my $file = abs_path( -d 't' ? 't/test/xml' : 'test/xml' );
@@ -160,7 +163,6 @@ Section name: [% node.name %] title: [% node.title %]
<b>[% node.childrenToTemplate(verbose=1) %]</b>
[% END %]
-
-- expect --
Section name: alpha title: The Alpha Zone
<a href="/foo/bar">The Foo Page</a>
@@ -173,7 +175,6 @@ Section name: alpha title: The Alpha Zone
<xml>
<section id="a" title="First Section">>
<page id="a1" title="page 1">
-
<head><author>Andy Wardley</author></head>
<body>
This is the first page
View
10 t/iterator.t
@@ -129,3 +129,13 @@ List of items:
* waz
End of list
+
+-- test --
+[% FOREACH i = [ 'foo' 'bar' 'baz' 'qux' ] %]
+[% "$loop.prev<-" IF loop.prev -%][[% i -%]][% "->$loop.next" IF loop.next +%]
+[% END %]
+-- expect --
+[foo]->bar
+foo<-[bar]->baz
+bar<-[baz]->qux
+baz<-[qux]
View
5 t/process.t
@@ -85,8 +85,3 @@ header.tt2:
title: Joe Random Title
menu: This is the menu, defined in 'config'
footer
-
-
-
-
-
View
3  t/test/src/baz
@@ -1 +1,2 @@
-This is the baz file, a: [% a %][% a = 'charlie' %]
+[% "name: $template.name modtime: $template.modtime\n" IF showname -%]
+This is the baz file, a: [% a %][% a = 'charlie' %]
View
10 t/url.t
@@ -36,12 +36,12 @@ sub sort_params {
print STDERR "sort_parms(\"$query\")\n" if $Template::Test::DEBUG;
- @args = split('&', $args);
+ @args = split('&amp;', $args);
@keys = map { (split('=', $_))[0] } @args;
@argtab{ @keys } = @args;
@keys = sort keys %argtab;
@args = map { $argtab{ $_ } } @keys;
- $args = join('&', @args);
+ $args = join('&amp;', @args);
$query = join('?', length $base ? ($base, $args) : $args);
print STDERR "returning [$query]\n" if $Template::Test::DEBUG;
@@ -83,7 +83,7 @@ here
there
here?any=where
every?which=way
-every?which=way&you=can
+every?which=way&amp;you=can
-- test --
[% USE url('there', name='fred') -%]
@@ -95,8 +95,8 @@ every?which=way&you=can
-- expect --
there?name=fred
there?name=tom
-there?age=24&name=fred
-there?age=42&name=frank
+there?age=24&amp;name=fred
+there?age=42&amp;name=frank
-- test --
[% USE url('/cgi-bin/woz.pl') -%]
View
24 t/vmeth.t
@@ -66,7 +66,10 @@ my $params = {
{ id => 'dick', name => 'Richard' },
{ id => 'larry', name => 'Larry' },
],
- primes => [ 13, 11, 17, 19, 2, 3, 5, 7 ],,
+ primes => [ 13, 11, 17, 19, 2, 3, 5, 7 ],
+ phones => { 3141 => 'Leon', 5131 => 'Andy', 4131 => 'Simon' },
+ groceries => { 'Flour' => 3, 'Milk' => 1, 'Peanut Butter' => 21 },
+
};
test_expect(\*DATA, undef, $params);
@@ -229,3 +232,22 @@ Tom
[% primes.odd.nsort.join(', ') %]
-- expect --
3, 5, 7, 11, 13, 17, 19
+
+-- test --
+[% FOREACH n = phones.sort -%]
+[% phones.$n %] is [% n %],
+[% END %]
+-- expect --
+Andy is 5131,
+Leon is 3141,
+Simon is 4131,
+
+-- test --
+[% FOREACH n = groceries.nsort.reverse -%]
+I want [% groceries.$n %] kilos of [% n %],
+[% END %]
+-- expect --
+I want 21 kilos of Peanut Butter,
+I want 3 kilos of Flour,
+I want 1 kilos of Milk,
+
Please sign in to comment.
Something went wrong with that request. Please try again.