Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

*** empty log message ***

git-svn-id: svn://svn.tt2.org/tt/Template2/trunk@78 d5a88997-0a34-4036-9ed2-92fb5d660d91
  • Loading branch information...
commit 36ff03e20d78f5c28a134d414bc47bf115876fb3 1 parent 6642a76
@abw authored
View
102 Changes
@@ -17,7 +17,107 @@
#========================================================================
#------------------------------------------------------------------------
-# Version 2.00
+# Version 2.01 - snapshot 0.1
+#------------------------------------------------------------------------
+
+* Added the Template::View module, the VIEW directive and the View plugin
+ which can be used collectively to create dynamic views. This is a very
+ powerful tool which fulfills a number of requirements and makes possible
+ a number of things that have previously been messy, difficult or not
+ possible. Views are primarily collections of templates. You can define
+ BLOCKs within a view and they remain local to it, but can be called
+ from outside the view. This is still very experimental. Things are
+ likely to change. Take a look at t/view.t for examples.
+
+* Fixed the parser to accept expressions on the right hand side of
+ parameter definitions for INCLUDE, etc. e.g.
+
+ [% INCLUDE header
+ title = my_title or your_title or default_title
+ bgcol = (style == 'dark' ? '#000000' : '#ffffff')
+ %]
+
+* Added the facility to specify multiple templates within a PROCESS,
+ INCLUDE, INSERT or WRAPPER directive. For all but WRAPPER, the
+ templates are processed in the order specified.
+
+ [% PROCESS config + header + menu %]
+ [% INCLUDE section/break + html/titlebar
+ title='A New Section'
+ %]
+ [% WRAPPER edge + box + titlebar %]
+ ...
+ [% END %]
+
+ Multiple WRAPPER templates get processed in reverse order to create
+ the correct nesting effect. In the example above, the enclosed block
+ is processed and passed to 'titlebar' which wraps it and passes the
+ output to 'header' which wraps it and passes the output to 'box', which
+ wraps it and passes the output to 'edge' which wraps it and returns the
+ output. Thus the specification order is outermost to innermost, but
+ they are actually processed from the inside out.
+
+* Templates specified to INCLUDE, PROCESS, WRAPPER and INSERT can now
+ be given a prefix (delimited by ':', as in "file:blahblah.txt" or
+ "http://www.tt2.org/index.html", for example) which maps them to a
+ particular template provider or providers. A PREFIX_MAP
+ configuration option can be specified as a hash array mapping prefix
+ names to a reference to a list of providers. For convenience, you
+ can also specify the argument as a string of integers, delimited by
+ any non-numerical sequence, to indicate indices into the LOAD_TEMPLATES
+ provider list. e.g.
+
+ my $template = Template->new({
+ LOAD_TEMPLATES => [ $foo, $bar, $baz, $wiz ],
+ PREFIX_MAP => {
+ src => '0, 2', # $foo and $baz
+ lib => '1, 2', # $bar and $baz
+ all => '0, 1, 2', # $foo, $bar and $baz
+ }
+ });
+
+ Thus [% INCLUDE src:hello.tt2 %] indicates the 'hello.tt2' template
+ to be provided by $foo or $baz, [% INCLUDE lib:hello.tt2 %] is mapped
+ to $bar and $baz, [% INCLUDE all:hello.tt2 %] can be provided by
+ $foo, $bar or $baz, and the default [% INCLUDE hello.tt2 %] is
+ mapped to the entire LOAD_TEMPLATES list: $foo, $bar, $baz and $wiz.
+ This is initially useful for things like ttree which would like a way
+ to differentiate between templates in one place and templates in
+ another. It can also be used, of course, to provider special providers
+ for certain file type, as in http://fetch.some.file.com/blah/blah/...
+
+* I *think* I identified the bug whereby non-exception objects were
+ be raised, causing an error in Template::Service...
+
+* Added the 'indent' filter to indent a block by prefixing each line with
+ a specified string, or a number of spaces when the argument is numerical.
+
+* Added tag style 'star' of the form [* ... *]
+
+* Fixed a bug in Template::Stash which was raising an error when an
+ element on the left hand side of a '.' evaluated to a defined, but
+ empty value.
+
+* Fixed an obscure bug in Template::Stash which occurred when calling
+ a scalar method on a value which contained a valid and visible object
+ package name. e.g. [% name = 'Foo::Bar'; name.baz() %] called
+ Foo::Bar->baz().
+
+* Fixed another obscure bug, this time in Template::Parser which wasn't
+ chomping the final newline in the input string. Thanks to Paul
+ Makepeace for reporting the problem.
+
+* Removed some old "delegate-to-another-object" code from Template::Plugin,
+ including a nasty AUTOLOAD method which prevented derived objects from
+ acting as transparent hashes. If delegative functionality is required
+ then it should be implemented as Template::Plugin::Delegate (and may
+ well be in the fullness of time).
+
+* Fixed a whole bunch of typos and spellos thanks to a patch from Leon.
+
+
+#------------------------------------------------------------------------
+# Version 2.00 1st December 2000
#------------------------------------------------------------------------
* Added the repeat(n), search(pattern) and replace(search, replace)
View
8 MANIFEST
@@ -35,7 +35,9 @@ lib/Template/Plugin/CGI.pm
lib/Template/Plugin/DBI.pm
lib/Template/Plugin/Datafile.pm
lib/Template/Plugin/Date.pm
+lib/Template/Plugin/Directory.pm
lib/Template/Plugin/Dumper.pm
+lib/Template/Plugin/File.pm
lib/Template/Plugin/Format.pm
lib/Template/Plugin/Iterator.pm
lib/Template/Plugin/Table.pm
@@ -55,6 +57,7 @@ lib/Template/Stash.pod
lib/Template/Test.pm
lib/Template/Test.pod
lib/Template/Tutorial.pod
+lib/Template/View.pm
parser/Grammar.pm.skel
parser/Parser.yp
parser/README
@@ -79,6 +82,7 @@ t/datafile.t
t/date.t
t/dbi.t
t/directive.t
+t/directry.t
t/document.t
t/dom.t
t/dumper.t
@@ -86,6 +90,7 @@ t/error.t
t/evalperl.t
t/exception.t
t/filter.t
+t/file.t
t/foreach.t
t/format.t
t/include.t
@@ -96,6 +101,8 @@ t/object.t
t/output.t
t/parser.t
t/plugins.t
+t/plusfile.t
+t/prefix.t
t/process.t
t/provider.t
t/ref.t
@@ -148,6 +155,7 @@ t/try.t
t/url.t
t/vars.t
t/varsv1.t
+t/view.t
t/vmeth.t
t/while.t
t/wrap.t
View
4 README
@@ -1,8 +1,8 @@
Template Toolkit
- Version 2.00-rc2
+ Version 2.00
- 14th November 2000
+ 1st December 2000
Copyright (C) 1996-2000 Andy Wardley. All Rights Reserved
Copyright (C) 1998-2000 Canon Research Centre Europe Ltd.
View
98 TODO
@@ -1,6 +1,12 @@
The following lists detail pending items, known bugs and possible future
enhancements planned for the Template Toolkit.
+LATEST
+------
+
+* Fix Template::DBI iterator first/last() - it doesn't behave the same as
+ list first/last() and get_all() fails?
+
#------------------------------------------------------------------------
# PENDING
@@ -25,14 +31,22 @@ enhancements planned for the Template Toolkit.
working on a POD to Template translator that will allow you to
convert any POD documents (include the TT docs, of course) into TT
templates that can then be processed into virtually any other format
- or style.
+ or style. There's a working prototype of this system if anyone wants
+ to take a look at it. Ask me for the code if you're interested and
+ don't mind a few rough edges.
+
+* The File and Directory plugins have grown out of a Directory plugin
+ that Michael Stevens <michael@etla.org> wrote and posted to the list.
+ I need to post my changes back to Michael and the list and see what
+ people think and where they can be improved.
* Template Toolkit FAQ.
* Template::Internals. A document about the internals describing how,
where and why to hack on them.
-* "Camelot!", "Camelot!", "Camelot!". "It's only a model".
+* "Camelot!", "Camelot!", "Camelot!" (It's only a model). See
+ http://www.template-toolkit.org/camelot/ for further info.
#------------------------------------------------------------------------
@@ -86,8 +100,78 @@ enhancements planned for the Template Toolkit.
# foo <- this gets printed
%]
+
#------------------------------------------------------------------------
-# FUTURE ENHANCEMENTS
+# LATEST THOUGHTS
+#
+# These are some of the latest ideas.
+#------------------------------------------------------------------------
+
+* Template::View, to implement dynamic views.
+ (this is now underway - see Template::View, and Views.pod somewhere
+ in the manual section of docs (under construction).
+
+* Ability to set different parser options for BLOCK definitions, etc.
+ See P2.pm - got half a lexer working.
+
+ [% BLOCK header
+ eval_perl = 0
+ pre_chomp = 1
+ %]
+ ...
+ [% END %]
+
+ Anonymous BLOCK can then be used to set a parser scope
+
+ [% BLOCK trim=1 %]
+
+ [% END %]
+
+ [% BLOCK trim=0 %]
+
+ [% END %]
+
+ And/or set different tag styles, etc.
+
+ [% BLOCK tags='star' %]
+ [* INCLUDE this_is_a_directive *]
+ [% INCLUDE this_is_not %]
+ [* END *]
+
+ [% INCLUDE back_to_normal %]
+
+ This will require some fairly serious work on the parser.
+
+* Template::Component, kind of compimentary to Template::View.
+ It should be possible to create a component object which
+ encapsulates pure Perl application logic with output templates,
+ managed by a related Template::View. HTML::Mason is most definately
+ the inspiration for this. For example, we might like to define
+ components on a per-directory basis. A directory might contain a
+ Perl module or modules which encode application logic, and one or
+ more templates which represent the view elements.
+
+ /usr/local/tt2/components/Mailbox/
+ Mailbox.pm
+ header
+ footer
+ title
+ or
+
+ /usr/local/tt2/components/Mailbox/
+ perl/
+ Mailbox.pm
+ Reader.pm
+ Writer.pm
+ Blah/Blah.pm
+ template/
+ header
+ footer
+ title
+ config.xml
+
+#------------------------------------------------------------------------
+# POSSIBLE FUTURE ENHANCEMENTS (the less recent thoughts)
#
# Things that might get added to a future release.
#------------------------------------------------------------------------
@@ -173,4 +257,12 @@ enhancements planned for the Template Toolkit.
that we will be able to incorporate some of those ideas some time
soon.
+#------------------------------------------------------------------------
+# misc
+#------------------------------------------------------------------------
+
+* default DELIMITER should be defined as a package var somewhere. It's
+ currently hardcoded in Template::Parser and Template::Context.
+
+* CVS add plusfile.t, prefix.t, ...
View
44 bin/ttree
@@ -96,7 +96,8 @@ my $replace = $config->get('define');
my $ttopts = {
%ucttopts,
RELATIVE => 1,
- INCLUDE_PATH => [ @$libdir, '.' ],
+# INCLUDE_PATH => [ @$libdir, '.' ],
+ INCLUDE_PATH => [ $srcdir, @$libdir ],
OUTPUT_PATH => $destdir,
};
@@ -138,7 +139,7 @@ if ($debug) {
# main-amble
#------------------------------------------------------------------------
-chdir($srcdir) || die "$srcdir: $!\n";
+#chdir($srcdir) || die "$srcdir: $!\n";
my $template = Template->new($ttopts);
@@ -146,7 +147,7 @@ if (@ARGV) {
# explicitly process files specified on command lines
foreach my $file (@ARGV) {
print " + $file\n" if $verbose;
- $template->process("$file", $replace, $file)
+ $template->process($file, $replace, $file)
|| print " ! ", $template->error(), "\n";
}
}
@@ -165,16 +166,23 @@ else {
sub process_tree {
my $dir = shift;
- my ($file, $path, $check);
+ my ($file, $path, $abspath, $check);
my $target;
local *DIR;
- opendir(DIR, $dir || '.') || return undef;
+ my $absdir = join('/', $srcdir ? $srcdir : (), $dir ? $dir : ());
+ $absdir ||= '.';
+
+ print STDERR " * processing tree: $absdir\n" if $debug;
+
+ opendir(DIR, $absdir) || do { warn "$absdir: $!\n"; return undef; };
FILE: while (defined ($file = readdir(DIR))) {
next if $file eq '.' || $file eq '..';
$path = $dir ? "$dir/$file" : $file;
- next unless -e $path;
+ $abspath = "$absdir/$file";
+
+ next unless -e $abspath;
# check against ignore list
foreach $check (@$ignore) {
@@ -184,22 +192,23 @@ sub process_tree {
next FILE;
}
}
-
- if (-d $path) {
+
+ if (-d $abspath) {
if ($recurse) {
my ($uid, $gid, $mode);
(undef, undef, $mode, undef, $uid, $gid, undef, undef,
- undef, undef, undef, undef, undef) = stat($path);
+ undef, undef, undef, undef, undef) = stat($abspath);
# create target directory if required
$target = "$destdir/$path";
unless (-d $target || $dryrun) {
mkdir $target, $mode || do {
- warn "mkdir($target): $!\n";
+ warn "mkdir ($target): $!\n";
next;
};
- chown($uid, $gid, $target) || warn "chown($target): $!\n";
+# commented out by abw on 2000/12/04 - seems to raise a warning?
+# chown($uid, $gid, $target) || warn "chown($target): $!\n";
printf " + %-32s (created target directory)\n", $path
if $verbose;
}
@@ -212,7 +221,7 @@ sub process_tree {
}
}
else {
- process_file($path);
+ process_file($path, $abspath);
}
}
closedir(DIR);
@@ -226,18 +235,19 @@ sub process_tree {
#------------------------------------------------------------------------
sub process_file {
- my $file = shift;
+ my ($file, $absfile) = @_;
my ($dest, $base, $check, $srctime, $desttime, $mode, $uid, $gid);
+ $absfile ||= $file;
$dest = $destdir ? "$destdir/$file" : $file;
$base = basename($file);
-
+
# print "proc $file => $dest\n";
# stat the source file unconditionally, so we can preserve
# mode and ownership
(undef, undef, $mode, undef, $uid, $gid, undef, undef, undef, $srctime,
- undef, undef, undef) = stat($file);
+ undef, undef, undef) = stat($absfile);
# test modification time of existing destination file
if (-f $dest && ! $all) {
@@ -257,7 +267,7 @@ sub process_file {
if $verbose;
unless ($dryrun) {
- copy($file, $dest);
+ copy($absfile, $dest);
if ($preserve) {
chown($uid, $gid, $dest) || warn "chown($dest): $!\n";
@@ -281,7 +291,7 @@ sub process_file {
# process file
unless ($dryrun) {
- $template->process("./$file", $replace, $file)
+ $template->process($file, $replace, $file)
|| print(" ! ", $template->error(), "\n");
if ($preserve) {
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-rc2';
+$VERSION = '2.00';
$ERROR = '';
$DEBUG = 0;
View
2  lib/Template/Base.pm
@@ -59,6 +59,8 @@ sub new {
}
# fold all remaining args into a hash, or use provided hash ref
+# local $" = ', ';
+# print STDERR "args: [@_]\n";
$cfg = ref $_[0] eq 'HASH' ? shift : { @_ };
my $self = bless {
View
195 lib/Template/Context.pm
@@ -76,7 +76,8 @@ $VERSION = sprintf("%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/);
sub template {
my ($self, $name) = @_;
- my ($blocks, $defblocks, $provider, $template, $error);
+ my ($prefix, $blocks, $defblocks, $provider, $template, $error);
+ my $providers;
# references to Template::Document (or sub-class) objects objects, or
# CODE references are assumed to be pre-compiled templates and are
@@ -91,18 +92,29 @@ sub template {
return $template
if ($template = $self->{ BLOCKS }->{ $name });
- # the we iterate through the BLKSTACK list to see if any of the
+ # then we iterate through the BLKSTACK list to see if any of the
# Template::Documents we're visiting define this BLOCK
foreach $blocks (@{ $self->{ BLKSTACK } }) {
return $template
if $blocks && ($template = $blocks->{ $name });
}
+
+ # now it's time to ask the providers, so we look to see if any
+ # prefix is specified to indicate the desired provider set.
+ if ($name =~ s/^(\w+)://o) {
+ $providers = $self->{ PREFIX_MAP }->{ $1 }
+ || return $self->throw(Template::Constants::ERROR_FILE,
+ "no providers for template prefix '$1'");
+# print STDERR "prefix identified: $1\n";
+ }
}
+ $providers = $self->{ LOAD_TEMPLATES }
+ unless $providers;
# finally we try the regular template providers which will
# handle references to files, text, etc., as well as templates
# reference by name
- foreach my $provider (@{ $self->{ LOAD_TEMPLATES } }) {
+ foreach my $provider (@$providers) {
($template, $error) = $provider->fetch($name);
return $template unless $error;
# return $self->error($template)
@@ -224,43 +236,56 @@ sub view {
sub process {
my ($self, $template, $params) = @_;
- my ($blocks, $output);
- my $name = $template;
+ my ($trim, $blocks) = @$self{ qw( TRIM BLOCKS ) };
+ my (@compiled, $name, $compiled);
+ my ($stash, $tblocks, $error, $tmpout);
+ my $output = '';
- # request compiled template from cache
- $template = $self->template($template);
-# || die Template::Exception->new(&Template::Constants::ERROR_FILE,
-# $self->{ _ERROR } || "$template: not found");
+# my ($blocks, $output);
+# my $name = $template;
- # merge any local blocks defined in the Template::Document into our
- # local BLOCKS cache
- @{ $self->{ BLOCKS } }{ keys %$blocks } = values %$blocks
- if UNIVERSAL::isa($template, 'Template::Document')
- && ($blocks = $template->blocks);
+ $template = [ $template ] unless ref $template eq 'ARRAY';
- # update stash with any new parameters passed
- $params ||= { };
- $params->{ component } = ref $template eq 'CODE'
- ? { ref $name ? () : ( name => $name, modtime => time() ) }
- : $template;
- $self->{ STASH }->update($params);
-# if $params;
-
- if (ref $template eq 'CODE') {
- $output = &$template($self);
- }
- elsif (ref $template) {
- $output = $template->process($self);
- }
- else {
- die "invalid template reference: $template\n";
+ # fetch compiled template for each name specified
+ foreach $name (@$template) {
+ push(@compiled, $self->template($name));
}
- if ($self->{ TRIM }) {
- for ($output) {
- s/^\s+//;
- s/\s+$//;
+ # update stash with any new parameters passed
+ $self->{ STASH }->update($params);
+ $stash = $self->{ STASH };
+
+ foreach $name (@$template) {
+ $compiled = shift @compiled;
+ my $element = ref $compiled eq 'CODE'
+ ? { (name => (ref $name ? () : $name), modtime => time()) }
+ : $compiled;
+ $stash->set('component', $element);
+
+ # merge any local blocks defined in the Template::Document into our
+ # local BLOCKS cache
+ @$blocks{ keys %$tblocks } = values %$tblocks
+ if UNIVERSAL::isa($compiled, 'Template::Document')
+ && ($tblocks = $compiled->blocks());
+
+ if (ref $compiled eq 'CODE') {
+ $tmpout = &$compiled($self);
+ }
+ elsif (ref $compiled) {
+ $tmpout = $compiled->process($self);
+ }
+ else {
+ $self->throw('file',
+ "invalid template reference: $compiled");
}
+
+ if ($trim) {
+ for ($tmpout) {
+ s/^\s+//;
+ s/\s+$//;
+ }
+ }
+ $output .= $tmpout;
}
return $output;
@@ -283,6 +308,61 @@ sub process {
sub include {
my ($self, $template, $params) = @_;
+ my $trim = $self->{ TRIM };
+ my (@compiled, $name, $compiled);
+ my ($stash, $error, $tmpout);
+ my $output = '';
+
+ $template = [ $template ] unless ref $template eq 'ARRAY';
+
+ # fetch compiled template for each name specified
+ foreach $name (@$template) {
+ push(@compiled, $self->template($name));
+ }
+
+ # localise the variable stash with any parameters passed
+ $stash = $self->{ STASH } = $self->{ STASH }->clone($params);
+
+ eval {
+ foreach $name (@$template) {
+ $compiled = shift @compiled;
+ my $element = ref $compiled eq 'CODE'
+ ? { (name => (ref $name ? '' : $name), modtime => time()) }
+ : $compiled;
+ $stash->set('component', $element);
+
+ if (ref $compiled eq 'CODE') {
+ $tmpout = &$compiled($self);
+ }
+ elsif (ref $compiled) {
+ $tmpout = $compiled->process($self);
+ }
+ else {
+ $self->throw('file',
+ "invalid template reference: $compiled");
+ }
+
+ if ($trim) {
+ for ($tmpout) {
+ s/^\s+//;
+ s/\s+$//;
+ }
+ }
+ $output .= $tmpout;
+ }
+ };
+ $error = $@;
+ # ensure stash is delocalised before dying
+ $self->{ STASH } = $self->{ STASH }->declone();
+ $self->throw(ref $error
+ ? $error : (Template::Constants::ERROR_FILE, $error))
+ if $error;
+
+ return $output;
+}
+
+sub old_include {
+ my ($self, $template, $params) = @_;
my ($error, $blocks);
my $output = '';
my $name = $template;
@@ -335,20 +415,38 @@ sub include {
sub insert {
my ($self, $file) = @_;
- my ($text, $error);
+ my ($providers, $text, $error);
+ my $output = '';
- foreach my $provider (@{ $self->{ LOAD_TEMPLATES } }) {
- ($text, $error) = $provider->load($file);
- return $text unless $error;
- if ($error == Template::Constants::STATUS_ERROR) {
- $self->throw($text) if ref $text;
- $self->throw(Template::Constants::ERROR_FILE, $text);
+ my $files = ref $file eq 'ARRAY' ? $file : [ $file ];
+
+ FILE: foreach $file (@$files) {
+ if ($file =~ s/^(\w+)://o) {
+ $providers = $self->{ PREFIX_MAP }->{ $1 }
+ || return $self->throw(Template::Constants::ERROR_FILE,
+ "no providers for file prefix '$1'");
+ }
+ else {
+ $providers = $self->{ LOAD_TEMPLATES };
}
- }
- $self->throw(Template::Constants::ERROR_FILE, "$file: not found");
+ foreach my $provider (@$providers) {
+ ($text, $error) = $provider->load($file);
+ next FILE unless $error;
+ if ($error == Template::Constants::STATUS_ERROR) {
+ $self->throw($text) if ref $text;
+ $self->throw(Template::Constants::ERROR_FILE, $text);
+ }
+ }
+ $self->throw(Template::Constants::ERROR_FILE, "$file: not found");
+ }
+ continue {
+ $output .= $text;
+ }
+ return $output;
}
+
#------------------------------------------------------------------------
# throw($type, $info, \$output) [% THROW errtype "Error info" %]
#
@@ -623,6 +721,15 @@ sub _init {
$self->{ $name } = ref $item eq 'ARRAY' ? $item : [ $item ];
}
+ my $providers = $self->{ LOAD_TEMPLATES };
+ my $prefix_map = $self->{ PREFIX_MAP } = $config->{ PREFIX_MAP } || { };
+ while (my ($key, $val) = each %$prefix_map) {
+ $prefix_map->{ $key } = [ map { $providers->[$_] } split(/\D+/, $val) ]
+ unless ref $val eq 'ARRAY';
+# print(STDERR "prefix $key => $val => [",
+# join(', ', @{ $prefix_map->{ $key } }), "]\n");
+ }
+
# STASH
$self->{ STASH } = $config->{ STASH } || do {
my $predefs = $config->{ VARIABLES }
@@ -651,12 +758,14 @@ sub _init {
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# RECURSION - flag indicating is recursion into templates is supported
# EVAL_PERL - flag indicating if PERL blocks should be processed
+# # DELIMITER - used to detect template prefixes
# TRIM - flag to remove leading and trailing whitespace from output
# BLKSTACK - list of hashes of BLOCKs defined in current template(s)
# CONFIG - original configuration hash
$self->{ RECURSION } = $config->{ RECURSION } || 0;
$self->{ EVAL_PERL } = $config->{ EVAL_PERL } || 0;
+# $self->{ DELIMITER } = $config->{ DELIMITER } || ':';
$self->{ TRIM } = $config->{ TRIM } || 0;
$self->{ BLKSTACK } = [ ];
$self->{ CONFIG } = $config;
View
112 lib/Template/Directive.pm
@@ -231,6 +231,21 @@ sub args {
return '[ ' . join(', ', @$args) . ' ]';
}
+#------------------------------------------------------------------------
+# filenames(\@names)
+#------------------------------------------------------------------------
+
+sub filenames {
+ my ($class, $names) = @_;
+ if (@$names > 1) {
+ $names = '[ ' . join(', ', @$names) . ' ]';
+ }
+ else {
+ $names = shift @$names;
+ }
+ return $names;
+}
+
#------------------------------------------------------------------------
# get($expr) [% foo %]
@@ -284,26 +299,28 @@ sub default {
#------------------------------------------------------------------------
-# insert($file) [% INSERT file %]
+# insert(\@nameargs) [% INSERT file %]
+# # => [ [ $file, ... ], \@args ]
#------------------------------------------------------------------------
sub insert {
my ($class, $nameargs) = @_;
my ($file, $args) = @$nameargs;
+ $file = $class->filenames($file);
return "$OUTPUT \$context->insert($file);";
}
-
#------------------------------------------------------------------------
# include(\@nameargs) [% INCLUDE template foo = bar %]
-# # => [ $file, \@args ]
+# # => [ [ $file, ... ], \@args ]
#------------------------------------------------------------------------
sub include {
my ($class, $nameargs) = @_;
my ($file, $args) = @$nameargs;
my $hash = shift @$args;
+ $file = $class->filenames($file);
$file .= @$hash ? ', { ' . join(', ', @$hash) . ' }' : '';
return "$OUTPUT \$context->include($file);";
}
@@ -311,13 +328,14 @@ sub include {
#------------------------------------------------------------------------
# process(\@nameargs) [% PROCESS template foo = bar %]
-# # => [ $file, \@args ]
+# # => [ [ $file, ... ], \@args ]
#------------------------------------------------------------------------
sub process {
my ($class, $nameargs) = @_;
my ($file, $args) = @$nameargs;
my $hash = shift @$args;
+ $file = $class->filenames($file);
$file .= @$hash ? ', { ' . join(', ', @$hash) . ' }' : '';
return "$OUTPUT \$context->process($file);";
}
@@ -426,7 +444,7 @@ EOF
#------------------------------------------------------------------------
# wrapper(\@nameargs, $block) [% WRAPPER template foo = bar %]
-# # => [ $file, \@args ]
+# # => [ [$file,...], \@args ]
#------------------------------------------------------------------------
sub wrapper {
@@ -434,12 +452,17 @@ sub wrapper {
my ($file, $args) = @$nameargs;
my $hash = shift @$args;
+ local $" = ', ';
+# print STDERR "wrapper([@$file], { @$hash })\n";
+
+ return $class->multi_wrapper($file, $hash, $block)
+ if @$file > 1;
+ $file = shift @$file;
+
$block = pad($block, 1) if $PRETTY;
-# push(@$hash, "'content'", '$content');
push(@$hash, "'content'", '$output');
$file .= @$hash ? ', { ' . join(', ', @$hash) . ' }' : '';
-
return <<EOF;
# WRAPPER
@@ -452,6 +475,31 @@ EOF
}
+sub multi_wrapper {
+ my ($class, $file, $hash, $block) = @_;
+ $block = pad($block, 1) if $PRETTY;
+
+ push(@$hash, "'content'", '$output');
+ $hash = @$hash ? ', { ' . join(', ', @$hash) . ' }' : '';
+
+ $file = join(', ', reverse @$file);
+# print STDERR "multi wrapper: $file\n";
+
+ return <<EOF;
+
+# WRAPPER
+$OUTPUT do {
+ my \$output = '';
+$block
+ foreach ($file) {
+ \$output = \$context->include(\$_$hash);
+ }
+ \$output;
+};
+EOF
+}
+
+
#------------------------------------------------------------------------
# while($expr, $block) [% WHILE x < 10 %]
# ...
@@ -597,7 +645,7 @@ EOF
#------------------------------------------------------------------------
# throw(\@nameargs) [% THROW foo "bar error" %]
-# # => [ $type, \@args ]
+# # => [ [$type], \@args ]
#------------------------------------------------------------------------
sub throw {
@@ -605,6 +653,9 @@ sub throw {
my ($type, $args) = @$nameargs;
my $hash = shift(@$args);
my $info = shift(@$args);
+ $type = shift @$type; # uses same parser production as INCLUDE
+ # etc., which allow multiple names
+ # e.g. INCLUDE foo+bar+baz
if (! $info) {
$args = "$type, undef";
@@ -667,21 +718,61 @@ sub stop {
#------------------------------------------------------------------------
# use(\@lnameargs) [% USE alias = plugin(args) %]
-# # => [ $file, \@args, $alias ]
+# # => [ [$file, ...], \@args, $alias ]
#------------------------------------------------------------------------
sub use {
my ($class, $lnameargs) = @_;
my ($file, $args, $alias) = @$lnameargs;
+ $file = shift @$file; # same production rule as INCLUDE
$alias ||= $file;
$args = &args($class, $args);
$file .= ", $args" if $args;
- my $set = &assign($class, $alias, '$plugin');
+# my $set = &assign($class, $alias, '$plugin');
return "# USE\n"
. "\$stash->set($alias,\n"
. " \$context->plugin($file));";
}
+#------------------------------------------------------------------------
+# view(\@nameargs, $block) [% VIEW name args %]
+# # => [ [$file, ... ], \@args ]
+#------------------------------------------------------------------------
+
+sub view {
+ my ($class, $nameargs, $block, $defblocks) = @_;
+ my ($name, $args) = @$nameargs;
+ my $hash = shift @$args;
+ $name = shift @$name; # same production rule as INCLUDE
+ $block = pad($block, 1) if $PRETTY;
+
+ if (%$defblocks) {
+ $defblocks = join(",\n", map { "'$_' => $defblocks->{ $_ }" }
+ keys %$defblocks);
+ $defblocks = pad($defblocks, 1) if $PRETTY;
+ $defblocks = "{\n$defblocks\n}";
+ push(@$hash, "'blocks'", $defblocks);
+ }
+ $hash = @$hash ? '{ ' . join(', ', @$hash) . ' }' : '';
+
+ return <<EOF;
+# VIEW
+do {
+ my \$output = '';
+ my \$oldv = \$stash->get('view');
+ my \$view = \$context->view($hash);
+ \$stash->set($name, \$view);
+ \$stash->set('view', \$view);
+
+$block
+
+ \$stash->set('view', \$oldv);
+ \$view->seal();
+ \$output;
+};
+EOF
+}
+
#------------------------------------------------------------------------
# perl($block)
@@ -760,6 +851,7 @@ EOF
sub filter {
my ($class, $lnameargs, $block) = @_;
my ($name, $args, $alias) = @$lnameargs;
+ $name = shift @$name;
$args = &args($class, $args);
$args = $args ? "$args, $alias" : ", undef, $alias"
if $alias;
View
2  lib/Template/Document.pm
@@ -163,6 +163,8 @@ sub AUTOLOAD {
$method =~ s/.*:://;
return if $method eq 'DESTROY';
+# my ($pkg, $file, $line) = caller();
+# print STDERR "called $self->AUTOLOAD($method) from $file line $line\n";
return $self->{ $method };
}
View
6,407 lib/Template/Grammar.pm
3,339 additions, 3,068 deletions not shown
View
78 lib/Template/Parser.pm
@@ -42,6 +42,7 @@ require 5.004;
use strict;
use vars qw( $VERSION $DEBUG $ERROR );
use base qw( Template::Base );
+use vars qw( $TAG_STYLE $DEFAULT_STYLE );
use Template::Directive;
use Template::Grammar;
@@ -61,7 +62,7 @@ $ERROR = '';
# -- COMMON TAG STYLES --
#========================================================================
-my $TAG_STYLE = {
+$TAG_STYLE = {
'default' => [ '\[%', '%\]' ],
'template' => [ '\[%', '%\]' ],
'template1' => [ '[\[%]%', '%[\]%]' ],
@@ -73,6 +74,18 @@ my $TAG_STYLE = {
'star' => [ '\[\*', '\*\]' ],
};
+$DEFAULT_STYLE = {
+ START_TAG => $TAG_STYLE->{ default }->[0],
+ END_TAG => $TAG_STYLE->{ default }->[1],
+# TAG_STYLE => 'default',
+ ANYCASE => 0,
+ INTERPOLATE => 0,
+ PRE_CHOMP => 0,
+ POST_CHOMP => 0,
+ V1DOLLAR => 0,
+ EVAL_PERL => 0,
+};
+
#========================================================================
# ----- PUBLIC METHODS -----
@@ -127,10 +140,45 @@ sub new {
@$self{ qw( LEXTABLE STATES RULES ) }
= @$grammar{ qw( LEXTABLE STATES RULES ) };
+ $self->new_style($config)
+ || return $class->error($self->error());
+
return $self;
}
+sub new_style {
+ my ($self, $config) = @_;
+ my $styles = $self->{ STYLE } ||= [ ];
+ my $style = { %{ $styles->[-1] || $DEFAULT_STYLE } };
+ my ($tagstyle, $tags, $start, $end, $key);
+
+ # expand START_TAG and END_TAG from specified TAG_STYLE
+ if ($tagstyle = $config->{ TAG_STYLE }) {
+ return $self->error("Invalid tag style: $tagstyle")
+ unless defined ($tags = $TAG_STYLE->{ $tagstyle });
+ ($start, $end) = @$tags;
+ $config->{ START_TAG } ||= $start;
+ $config->{ END_TAG } ||= $end;
+ }
+
+ foreach $key (keys %$DEFAULT_STYLE) {
+ $style->{ $key } = $config->{ $key } if defined $config->{ $key };
+ }
+ push(@$styles, $style);
+ return $style;
+}
+
+sub old_style {
+ my $self = shift;
+ my $styles = $self->{ STYLE };
+ return $self->error('only 1 parser style remaining')
+ unless (@$styles > 1);
+ pop @$styles;
+ return $styles->[-1];
+}
+
+
#------------------------------------------------------------------------
# parse($text)
#
@@ -179,8 +227,9 @@ sub parse {
sub split_text {
my ($self, $text) = @_;
my ($pre, $dir, $prelines, $dirlines, $postlines, $chomp, $tags, @tags);
+ my $style = $self->{ STYLE }->[-1];
my ($start, $end, $prechomp, $postchomp, $interp ) =
- @$self{ qw( START_TAG END_TAG PRE_CHOMP POST_CHOMP INTERPOLATE ) };
+ @$style{ qw( START_TAG END_TAG PRE_CHOMP POST_CHOMP INTERPOLATE ) };
my @tokens = ();
my $line = 1;
@@ -357,8 +406,11 @@ sub interpolate_text {
sub tokenise_directive {
my ($self, $text, $line) = @_;
my ($token, $uctoken, $type, $lookup);
- my ($lextable, $anycase, $start, $end) =
- @$self{ qw( LEXTABLE ANYCASE START_TAG END_TAG ) };
+# my ($lextable, $anycase, $start, $end) =
+# @$self{ qw( LEXTABLE ANYCASE START_TAG END_TAG ) };
+ my $lextable = $self->{ LEXTABLE };
+ my $style = $self->{ STYLE }->[-1];
+ my ($anycase, $start, $end) = @$style{ qw( ANYCASE START_TAG END_TAG ) };
my @tokens = ( );
while ($text =~
@@ -392,7 +444,7 @@ sub tokenise_directive {
# an unquoted word or symbol matches in $7
( [(){}\[\]:;,\/\\] # misc parenthesis and symbols
# | \-> # arrow operator (for future?)
- | \+\-\* # math operations
+ | [+\-*] # math operations
| \$\{? # dollar with option left brace
| => # like '='
| [=!<>]?= | [!<>] # eqality tests
@@ -496,6 +548,22 @@ sub define_block {
return undef;
}
+sub push_defblock {
+ my $self = shift;
+ my $stack = $self->{ DEFBLOCK_STACK } ||= [];
+ push(@$stack, $self->{ DEFBLOCK } );
+ $self->{ DEFBLOCK } = { };
+}
+
+sub pop_defblock {
+ my $self = shift;
+ my $defs = $self->{ DEFBLOCK };
+ my $stack = $self->{ DEFBLOCK_STACK } || return $defs;
+ return $defs unless @$stack;
+ $self->{ DEFBLOCK } = pop @$stack;
+ return $defs;
+}
+
#------------------------------------------------------------------------
# add_metadata(\@setlist)
View
398 lib/Template/Plugin/Directory.pm
@@ -0,0 +1,398 @@
+#============================================================= -*-Perl-*-
+#
+# Template::Plugin::Directory
+#
+# DESCRIPTION
+# Plugin for encapsulating information about a file system directory.
+#
+# AUTHORS
+# Michael Stevens <michael@etla.org>, with some mutilations from
+# Andy Wardley <abw@kfs.org>.
+#
+# COPYRIGHT
+# This module is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# REVISION
+# $Id$
+#
+#============================================================================
+
+package Template::Plugin::Directory;
+
+require 5.004;
+
+use strict;
+use Cwd;
+use File::Spec;
+use vars qw( $VERSION );
+use base qw( Template::Plugin::File );
+
+$VERSION = sprintf("%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/);
+
+
+#------------------------------------------------------------------------
+# new(\%config)
+#
+# Constructor method.
+#------------------------------------------------------------------------
+
+sub new {
+ my $config = ref($_[-1]) eq 'HASH' ? pop(@_) : { };
+ my ($class, $context, $path) = @_;
+
+ return $class->throw('no directory specified')
+ unless defined $path and length $path;
+
+ my $self = $class->SUPER::new($context, $path, $config);
+ my ($dir, @files, $name, $item, $abs, $rel, $check);
+ $self->{ files } = [ ];
+ $self->{ dirs } = [ ];
+ $self->{ list } = [ ];
+ $self->{ _dir } = { };
+
+ # don't read directory if 'nostat' or 'noscan' set
+ return $self if $config->{ nostat } || $config->{ noscan };
+
+ $self->throw("$path: not a directory")
+ unless $self->{ isdir };
+
+ $self->scan($config);
+
+ return $self;
+}
+
+
+#------------------------------------------------------------------------
+# scan(\%config)
+#
+# Scan directory for files and sub-directories.
+#------------------------------------------------------------------------
+
+sub scan {
+ my ($self, $config) = @_;
+ $config ||= { };
+ local *DH;
+ my ($dir, @files, $name, $abs, $rel, $item);
+
+ # set 'noscan' in config if recurse isn't set, to ensure Directories
+ # created don't try to scan deeper
+ $config->{ noscan } = 1 unless $config->{ recurse };
+
+ $dir = $self->{ abs };
+ opendir(DH, $dir) or return $self->throw("$dir: $!");
+
+ @files = readdir DH;
+ closedir(DH)
+ or return $self->throw("$dir close: $!");
+
+ my ($path, $files, $dirs, $list) = @$self{ qw( path files dirs list ) };
+ @$files = @$dirs = @$list = ();
+
+ foreach $name (sort @files) {
+ next if $name =~ /^\./;
+ $abs = File::Spec->catfile($dir, $name);
+ $rel = File::Spec->catfile($path, $name);
+
+ if (-d $abs) {
+ $item = Template::Plugin::Directory->new(undef, $rel, $config);
+ push(@$dirs, $item);
+ }
+ else {
+ $item = Template::Plugin::File->new(undef, $rel, $config);
+ push(@$files, $item);
+ }
+ push(@$list, $item);
+ $self->{ _dir }->{ $name } = $item;
+ }
+
+ return '';
+}
+
+
+#------------------------------------------------------------------------
+# file($filename)
+#
+# Fetch a named file from this directory.
+#------------------------------------------------------------------------
+
+sub file {
+ my ($self, $name) = @_;
+ return $self->{ _dir }->{ $name };
+}
+
+
+#------------------------------------------------------------------------
+# present($view)
+#
+# Present self to a Template::View
+#------------------------------------------------------------------------
+
+sub present {
+ my ($self, $view) = @_;
+ $view->view_directory($self);
+}
+
+
+#------------------------------------------------------------------------
+# content($view)
+#
+# Present directory content to a Template::View.
+#------------------------------------------------------------------------
+
+sub content {
+ my ($self, $view) = @_;
+ return $self->{ list } unless $view;
+ my $output = '';
+ foreach my $file (@{ $self->{ list } }) {
+ $output .= $file->present($view);
+ }
+ return $output;
+}
+
+
+#------------------------------------------------------------------------
+# throw($msg)
+#
+# Throw a 'Directory' exception.
+#------------------------------------------------------------------------
+
+sub throw {
+ my ($self, $error) = @_;
+ die Template::Exception->new('Directory', $error);
+}
+
+
+
+
+__END__
+
+=head1 NAME
+
+Template::Plugin::Directory - a plugin for directory listings
+
+=head1 SYNOPSIS
+
+ [% USE dir = Directory(dirpath) %]
+
+ # files returns list of regular files
+ [% FOREACH file = dir.files %]
+ [% file.name %] [% file.path %] ...
+ [% END %]
+
+ # dirs returns list of sub-directories
+ [% FOREACH subdir = dir.dirs %]
+ [% subdir.name %] [% subdir.path %] ...
+ [% END %]
+
+ # list returns both interleaved in order
+ [% FOREACH item = dir.list %]
+ [% IF item.isdir %]
+ Directory: [% item.name %]
+ [% ELSE
+ File: [% item.name %]
+ [% END %]
+ [% END %]
+
+ # define a VIEW to display dirs/files
+ [% VIEW myview %]
+ [% BLOCK file %]
+ File: [% item.name %]
+ [% END %]
+
+ [% BLOCK directory %]
+ Directory: [% item.name %]
+ [% item.content(myview) | indent -%]
+ [% END %]
+ [% END %]
+
+ # display directory content using view
+ [% myview.print(dir) %]
+
+=head1 DESCRIPTION
+
+This Template Toolkit plugin provides a simple interface to directory
+listings. It is derived from the Template::Plugin::File module and
+uses Template::Plugin::File object instances to represent files within
+a directory. Sub-directories within a directory are represented by
+further Template::Plugin::Directory instances.
+
+The constructor expects a directory name as an argument.
+
+ [% USE dir = Directory('/tmp') %]
+
+It then provides access to the files and sub-directories contained within
+the directory.
+
+ # regular files (not directories)
+ [% FOREACH file = dir.files %]
+ [% file.name %]
+ [% END %]
+
+ # directories only
+ [% FOREACH file = dir.dirs %]
+ [% file.name %]
+ [% END %]
+
+ # files and/or directories
+ [% FOREACH file = dir.list %]
+ [% file.name %] ([% file.isdir ? 'directory' : 'file' %])
+ [% END %]
+
+ [% USE Directory('foo/baz') %]
+
+The plugin constructor will throw a 'Directory' error if the specified
+path does not exist, is not a directory or fails to stat() (see
+L<Template::Plugin::File>). Otherwise, it will scan the directory and
+create lists named 'files' containing files, 'dirs' containing
+directories and 'list' containing both files and directories combined.
+The 'nostat' option can be set to disable all file/directory checks
+and directory scanning.
+
+Each file in the directory will be represented by a
+Template::Plugin::File object instance, and each directory by another
+Template::Plugin::Directory. If the 'recurse' flag is set, then those
+directories will contain further nested entries, and so on. With the
+'recurse' flag unset, as it is by default, then each is just a place
+marker for the directory and does not contain any further content
+unless its scan() method is explicitly called. The 'isdir' flag can
+be tested against files and/or directories, returning true if the item
+is a directory or false if it is a regular file.
+
+ [% FOREACH file = dir.list %]
+ [% IF file.isdir %]
+ * Directory: [% file.name %]
+ [% ELSE %]
+ * File: [% file.name %]
+ [% END %]
+ [% END %]
+
+This example shows how you might walk down a directory tree, displaying
+content as you go. With the recurse flag disabled, as is the default,
+we need to explicitly call the scan() method on each directory, to force
+it to lookup files and further sub-directories contained within.
+
+ [% USE dir = Directory(dirpath) %]
+ * [% dir.path %]
+ [% INCLUDE showdir %]
+
+ [% BLOCK showdir -%]
+ [% FOREACH file = dir.list -%]
+ [% IF file.isdir -%]
+ * [% file.name %]
+ [% file.scan -%]
+ [% INCLUDE showdir dir=file FILTER indent(4) -%]
+ [% ELSE -%]
+ - [% f.name %]
+ [% END -%]
+ [% END -%]
+ [% END %]
+
+This example is adapted (with some re-formatting for clarity) from
+a test in F<t/directry.t> which produces the following output:
+
+ * test/dir
+ - file1
+ - file2
+ * sub_one
+ - bar
+ - foo
+ * sub_two
+ - waz.html
+ - wiz.html
+ - xyzfile
+
+The 'recurse' flag can be set (disabled by default) to cause the
+constructor to automatically recurse down into all sub-directories,
+creating a new Template::Plugin::Directory object for each one and
+filling it with any further content. In this case there is no need
+to explicitly call the scan() method.
+
+ [% USE dir = Directory(dirpath, recurse=1) %]
+ ...
+
+ [% IF file.isdir -%]
+ * [% file.name %]
+ [% INCLUDE showdir dir=file FILTER indent(4) -%]
+ [% ELSE -%]
+ ...
+
+From version 2.01, the Template Toolkit provides support for views.
+A view can be defined as a VIEW ... END block and should contain
+BLOCK definitions for files ('file') and directories ('directory').
+
+ [% VIEW myview %]
+ [% BLOCK file %]
+ - [% item.name %]
+ [% END %]
+
+ [% BLOCK directory %]
+ * [% item.name %]
+ [% item.content(myview) FILTER indent %]
+ [% END %]
+ [% END %]
+
+Then the view print() method can be called, passing the
+Directory object as an argument.
+
+ [% USE dir = Directory(dirpath, recurse=1) %]
+ [% myview.print(dir) %]
+
+When a directory is presented to a view, either as [% myview.print(dir) %]
+or [% dir.present(view) %], then the 'directory' BLOCK within the 'myview'
+VIEW is processed, with the 'item' variable set to alias the Directory object.
+
+ [% BLOCK directory %]
+ * [% item.name %]
+ [% item.content(myview) FILTER indent %]
+ [% END %]
+
+The directory name is first printed and the content(view) method is
+then called to present each item within the directory to the view.
+Further directories will be mapped to the 'directory' block, and files
+will be mapped to the 'file' block.
+
+With the recurse option disabled, as it is by default, the 'directory'
+block should explicitly call a scan() on each directory.
+
+ [% VIEW myview %]
+ [% BLOCK file %]
+ - [% item.name %]
+ [% END %]
+
+ [% BLOCK directory %]
+ * [% item.name %]
+ [% item.scan %]
+ [% item.content(myview) FILTER indent %]
+ [% END %]
+ [% END %]
+
+ [% USE dir = Directory(dirpath) %]
+ [% myview.print(dir) %]
+
+=head1 TODO
+
+Might be nice to be able to specify accept/ignore options to catch
+a subset of files.
+
+=head1 AUTHORS
+
+Michael Stevens <michael@etla.org> wrote the original Directory plugin
+on which this is based. Andy Wardley <abw@kfs.org> split it into
+separate File and Directory plugins, added some extra code and documentation
+for VIEW support, and made a few other minor tweaks.
+
+=head1 REVISION
+
+$Revision$
+
+=head1 COPYRIGHT
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+See also L<Template::Plugin::File>, L<Template::Plugin|Template::Plugin>
+and L<Template::View>.
+
View
402 lib/Template/Plugin/File.pm
@@ -0,0 +1,402 @@
+#============================================================= -*-Perl-*-
+#
+# Template::Plugin::File
+#
+# DESCRIPTION
+# Plugin for encapsulating information about a system file.
+#
+# AUTHOR
+# Originally written by Michael Stevens <michael@etla.org> as the
+# Directory plugin, then mutilated by Andy Wardley <abw@kfs.org>
+# into separate File and Directory plugins, with some additional
+# code for working with views, etc.
+#
+# COPYRIGHT
+# This module is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# REVISION
+# $Id$
+#
+#============================================================================
+
+package Template::Plugin::File;
+
+require 5.004;
+
+use strict;
+use Cwd;
+use File::Spec;
+use File::Basename;
+
+use vars qw( $VERSION );
+use base qw( Template::Plugin );
+use vars qw( @STAT_KEYS );
+
+$VERSION = sprintf("%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/);
+
+@STAT_KEYS = qw( dev ino mode nlink uid gid rdev size
+ atime mtime ctime blksize blocks );
+
+
+#------------------------------------------------------------------------
+# new($context, $file, \%config)
+#
+# Create a new File object. Takes the pathname of the file as
+# the argument following the context and an optional
+# hash reference of configuration parameters.
+#------------------------------------------------------------------------
+
+sub new {
+ my $config = ref($_[-1]) eq 'HASH' ? pop(@_) : { };
+ my ($class, $context, $path) = @_;
+ my ($root, $home, @stat, $abs);
+
+ return $class->throw('no file specified')
+ unless defined $path and length $path;
+
+ # path, dir, name, root, home
+
+ if (File::Spec->file_name_is_absolute($path)) {
+ $root = '';
+ }
+ elsif (($root = $config->{ root })) {
+ # strip any trailing '/' from root
+ $root =~ s[/$][];
+ }
+ else {
+ $root = '';
+ }
+
+ my ($name, $dir, $ext) = fileparse($path, '\.\w+');
+ # fixup various items
+ $dir =~ s[/$][];
+ $dir = '' if $dir eq '.';
+ $name = $name . $ext;
+ $ext =~ s/^\.//g;
+ my @fields = File::Spec->splitdir($dir);
+ shift @fields if @fields && ! length $fields[0];
+ $home = join('/', ('..') x @fields);
+ $abs = File::Spec->catfile($root ? $root : (), $path);
+
+ my $self = {
+ path => $path,
+ name => $name,
+ root => $root,
+ home => $home,
+ dir => $dir,
+ ext => $ext,
+ abs => $abs,
+ user => '',
+ group => '',
+ isdir => '',
+ stat => defined $config->{ stat } ? $config->{ stat }
+ : ! $config->{ nostat },
+ map { ($_ => '') } @STAT_KEYS,
+ };
+
+ if ($self->{ stat }) {
+ (@stat = stat( $abs ))
+ || return $class->throw("$abs: $!");
+ @$self{ @STAT_KEYS } = @stat;
+ unless ($config->{ noid }) {
+ $self->{ user } = getpwuid( $self->{ uid }) || $self->{ uid };
+ $self->{ group } = getgrgid( $self->{ gid }) || $self->{ gid };
+ }
+ $self->{ isdir } = -d $abs;
+ }
+
+ bless $self, $class;
+}
+
+
+#-------------------------------------------------------------------------
+# rel($file)
+#
+# Generate a relative filename for some other file relative to this one.
+#------------------------------------------------------------------------
+
+sub rel {
+ my ($self, $path) = @_;
+ $path = $path->{ path } if ref $path eq ref $self; # assumes same root
+ return $path if $path =~ m[^/];
+ return $path unless $self->{ home };
+ return $self->{ home } . '/' . $path;
+}
+
+
+#------------------------------------------------------------------------
+# present($view)
+#
+# Present self to a Template::View.
+#------------------------------------------------------------------------
+
+sub present {
+ my ($self, $view) = @_;
+ $view->view_file($self);
+}
+
+
+sub throw {
+ my ($self, $error) = @_;
+ die Template::Exception->new('File', $error);
+}
+
+
+__END__
+
+=head1 NAME
+
+Template::Plugin::File - plugin providing information about a file
+
+=head1 SYNOPSIS
+
+ [% USE File(filepath) %]
+ [% File.path %] # full path
+ [% File.name %] # filename
+ [% File.dir %] # directory
+
+=head1 DESCRIPTION
+
+This plugin provides an abstraction of a file. It can be used to
+fetch details about files from the file system, or to represent abstract
+files (e.g. when creating an index page) that may or may not exist on
+a file system.
+
+A file name or path should be specified as a constructor argument. e.g.
+
+ [% USE File('foo.html') %]
+ [% USE File('foo/bar/baz.html') %]
+ [% USE File('/foo/bar/baz.html') %]
+
+The file should exist on the current file system (unless 'nostat'
+option set, see below) as an absolute file when specified with as
+leading '/' as per '/foo/bar/baz.html', or otherwise as one relative
+to the current working directory. The constructor performs a stat()
+on the file and makes the 13 elements returned available as the plugin
+items:
+
+ dev ino mode nlink uid gid rdev size
+ atime mtime ctime blksize blocks
+
+e.g.
+
+ [% USE File('/foo/bar/baz.html') %]
+
+ [% File.mtime %]
+ [% File.mode %]
+ ...
+
+In addition, the 'user' and 'group' items are set to contain the
+user and group names as returned by calls to getpwuid() and getgrgid()
+for the file 'uid' and 'gid' elements, respectively.
+
+ [% USE File('/tmp/foo.html') %]
+ [% File.uid %] # e.g. 500
+ [% File.user %] # e.g. abw
+
+This user/group lookup can be disabled by setting the 'noid' option.
+
+ [% USE File('/tmp/foo.html', noid=1) %]
+ [% File.uid %] # e.g. 500
+ [% File.user %] # nothing
+
+The 'isdir' flag will be set if the file is a directory.
+
+ [% USE File('/tmp') %]
+ [% File.isdir %] # 1
+
+If the stat() on the file fails (e.g. file doesn't exists, bad
+permission, etc) then the constructor will throw a 'File' exception.
+This can be caught within a TRY...CATCH block.
+
+ [% TRY %]
+ [% USE File('/tmp/myfile') %]
+ File exists!
+ [% CATCH File %]
+ File error: [% error.info %]
+ [% END %]
+
+Note the capitalisation of the exception type, 'File' to indicate an
+error thrown by the 'File' plugin, to distinguish it from a regular
+'file' exception thrown by the Template Toolkit.
+
+Note that the 'File' plugin can also be referenced by the lower case
+name 'file'. However, exceptions are always thrown of the 'File'
+type, regardless of the capitalisation of the plugin named used.
+
+ [% USE file('foo.html') %]
+ [% file.mtime %]
+
+As with any other Template Toolkit plugin, an alternate name can be
+specified for the object created.
+
+ [% USE foo = file('foo.html') %]
+ [% foo.mtime %]
+
+The 'nostat' option can be specified to prevent the plugin constructor
+from performing a stat() on the file specified. In this case, the
+file does not have to exist in the file system, no attempt will be made
+to verify that it does, and no error will be thrown if it doesn't.
+The entries for the items usually returned by stat() will be set
+empty.
+
+ [% USE file('/some/where/over/the/rainbow.html', nostat=1)
+ [% file.mtime %] # nothing
+
+All File plugins, regardless of the nostat option, have set a number
+of items relating to the original path specified.
+
+=over 4
+
+=item path
+
+The full, original file path specified to the constructor.
+
+ [% USE file('/foo/bar.html') %]
+ [% file.path %] # /foo/bar.html
+
+=item name
+
+The name of the file without any leading directories.
+
+ [% USE file('/foo/bar.html') %]
+ [% file.name %] # bar.html
+
+=item dir
+
+The directory element of the path with the filename removed.
+
+ [% USE file('/foo/bar.html') %]
+ [% file.name %] # /foo
+
+=item ext
+
+The file extension, if any, appearing at the end of the path following
+a '.' (not included in the extension).
+
+ [% USE file('/foo/bar.html') %]
+ [% file.ext %] # html
+
+=item home
+
+This contains a string of the form '../..' to represent the upward path
+from a file to its root directory.
+
+ [% USE file('bar.html') %]
+ [% file.home %] # nothing
+
+ [% USE file('foo/bar.html') %]
+ [% file.home %] # ..
+
+ [% USE file('foo/bar/baz.html') %]
+ [% file.home %] # ../..
+
+=item root
+
+The 'root' item can be specified as a constructor argument, indicating
+a root directory in which the named file resides. This is otherwise
+set empty.
+
+ [% USE file('foo/bar.html', root='/tmp') %]
+ [% file.root %] # /tmp
+
+=item abs
+
+This returns the absolute file path by constructing a path from the
+'root' and 'path' options.
+
+ [% USE file('foo/bar.html', root='/tmp') %]
+ [% file.path %] # foo/bar.html
+ [% file.root %] # /tmp
+ [% file.abs %] # /tmp/foo/bar.html
+
+=back
+
+In addition, the following method is provided:
+
+=over 4
+
+=item rel(path)
+
+This returns a relative path from the current file to another path specified
+as an argument. It is constructed by appending the path to the 'home'
+item.
+
+ [% USE file('foo/bar/baz.html') %]
+ [% file.rel('wiz/waz.html') %] # ../../wiz/waz.html
+
+=back
+
+=head1 EXAMPLES
+
+ [% USE file('/foo/bar/baz.html') %]
+
+ [% file.path %] # /foo/bar/baz.html
+ [% file.dir %] # /foo/bar
+ [% file.name %] # baz.html
+ [% file.home %] # ../..
+ [% file.root %] # ''
+ [% file.abspath %] # /foo/bar/baz.html
+ [% file.ext %] # html
+ [% file.mtime %] # 987654321
+ [% file.atime %] # 987654321
+ [% file.uid %] # 500
+ [% file.user %] # abw
+
+ [% USE file('foo.html') %]
+
+ [% file.path %] # foo.html
+ [% file.dir %] # ''
+ [% file.name %] # foo.html
+ [% file.root %] # ''
+ [% file.home %] # ''
+ [% file.abspath %] # foo.html
+
+ [% USE file('foo/bar/baz.html') %]
+
+ [% file.path %] # foo/bar/baz.html
+ [% file.dir %] # foo/bar
+ [% file.name %] # baz.html
+ [% file.root %] # ''
+ [% file.home %] # ../..
+ [% file.abspath %] # foo/bar/baz.html
+
+ [% USE file('foo/bar/baz.html', root='/tmp') %]
+
+ [% file.path %] # foo/bar/baz.html
+ [% file.dir %] # foo/bar
+ [% file.name %] # baz.html
+ [% file.root %] # /tmp
+ [% file.home %] # ../..
+ [% file.abspath %] # /tmp/foo/bar/baz.html
+
+ # calculate other file paths relative to this file and its root
+ [% USE file('foo/bar/baz.html', root => '/tmp/tt2') %]
+ [% file.path('baz/qux.html') %] # ../../baz/qux.html
+ [% file.dir('wiz/woz.html') %] # ../../wiz/woz.html
+
+
+=head1 AUTHORS
+
+Michael Stevens <michael@etla.org> wrote the original Directory plugin
+on which this is based. Andy Wardley <abw@kfs.org> split it into
+separate File and Directory plugins, added some extra code and documentation
+for VIEW support, and made a few other minor tweaks.
+
+=head1 REVISION
+
+$Revision$
+
+=head1 COPYRIGHT
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+See also L<Template::Plugin::Directory>, L<Template::Plugin|Template::Plugin>
+and L<Template::View>.
+
+=cut
+
View
87 lib/Template/Plugin/View.pm
@@ -0,0 +1,87 @@
+#============================================================= -*-Perl-*-
+#
+# Template::Plugin::View
+#
+# DESCRIPTION
+# A user-definable view based on templates. Similar to the concept of
+# a "Skin".
+#
+# AUTHOR
+# Andy Wardley <abw@kfs.org>
+#
+# COPYRIGHT
+# Copyright (C) 2000 Andy Wardley. All Rights Reserved.
+#
+# This module is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# REVISION
+# $Id$
+#
+#============================================================================
+
+package Template::Plugin::View;
+
+require 5.004;
+
+use strict;
+use vars qw( $VERSION );
+use base qw( Template::Plugin );
+
+$VERSION = sprintf("%d.%02d", q$Revision$ =~ /(\d+)\.(\d+)/);
+
+use Template::View;
+
+#------------------------------------------------------------------------
+# new($context, \%config)
+#------------------------------------------------------------------------
+
+sub new {
+ my $class = shift;
+ my $context = shift;
+ my $view = Template::View->new($context, @_)
+ || return $class->error($Template::View::ERROR);
+ $view->seal();
+ return $view;
+}
+
+
+
+1;
+
+
+__END__
+
+=head1 NAME
+
+Template::Plugin::View -
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 AUTHOR
+
+Andy Wardley E<lt>abw@kfs.orgE<gt>
+
+=head1 REVISION
+
+$Revision$
+
+=head1 COPYRIGHT
+
+Copyright (C) 2000 Andy Wardley. All Rights Reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+L<Template::Plugin|Template::Plugin>,
+
+=cut
+
+
+
+
+
View
2  lib/Template/Service.pm
@@ -179,7 +179,7 @@ sub _recover {
# there's a pesky lurking somewhere deep - let's hope this catches it
unless (ref $$error) {
- require Carp;
+ use Carp qw( confess );
confess('internal error: not an exception object',
' - please contact the author: <abw@kfs.org>\n',
"ERROR: $$error\n");
View
166 lib/Template/View.pm
@@ -69,6 +69,9 @@ sub _init {
%$map,
};
+ # local BLOCKs definition table
+ $self->{ _BLOCKS } = $config->{ blocks } || { };
+
# name of presentation method which printed objects might provide
$self->{ method } = defined $config->{ method }
? $config->{ method } : 'present';
@@ -78,7 +81,7 @@ sub _init {
$self->{ $arg } = $config->{ $arg } || '';
}
- # name of data itemused by view()
+ # name of data item used by view()
$self->{ item } = $config->{ item } || 'item';
# map methods of form ${include_prefix}_foobar() to include('foobar')?
@@ -92,17 +95,47 @@ sub _init {
# what about mapping foobar() to view('foobar')?
$self->{ view_naked } = $config->{ view_naked } || 0;
+ # the view is initially unsealed, allowing directives in the initial
+ # view template to create data items via the AUTOLOAD; once sealed via
+ # call to seal(), the AUTOLOAD will not update any internal items.
+ delete @$config{ qw( method map default prefix suffix notfound item
+ include_prefix include_naked
+ view_prefix view_naked blocks ) };
+ $self->{ data } = $config;
+ $self->{ SEALED } = 0;
+
return $self;
}
#------------------------------------------------------------------------
+# seal()
+# unseal()
+#
+# Seal or unseal the view to allow/prevent new datat items from being
+# automatically created by the AUTOLOAD method.
+#------------------------------------------------------------------------
+
+sub seal {
+ my $self = shift;
+ $self->{ SEALED } = 1;
+}
+
+sub unseal {
+ my $self = shift;
+ $self->{ SEALED } = 0;
+}
+
+
+#------------------------------------------------------------------------
# clone(\%config)
#
# Cloning method which takes a copy of $self and then applies to it any
# modifications specified in the $config hash passed as an argument.
# Configuration items may also be specified as a list of "name => $value"
# arguments. Returns a reference to the cloned Template::View object.
+#
+# NOTE: may need to copy BLOCKS???
#------------------------------------------------------------------------
sub clone {
@@ -120,11 +153,18 @@ sub clone {
$clone->{ map }->{ default } = $config->{ default }
if defined $config->{ default };
- # update any remaining args
- foreach my $arg (qw( prefix suffix notfound item method
- include_prefix view_prefix view_naked )) {
+ # update any remaining config items
+ my @args = qw( prefix suffix notfound item method include_prefix
+ include_naked view_prefix view_naked );
+ foreach my $arg (@args) {
$clone->{ $arg } = $config->{ $arg } if defined $config->{ $arg };
}
+ push(@args, qw( default map ));
+ delete @$config{ @args };
+
+ # anything left is data
+ my $data = $clone->{ data } = { %{ $self->{ data } } };
+ @$data{ keys %$config } = values %$config;
return $clone;
}
@@ -147,7 +187,7 @@ sub clone {
#------------------------------------------------------------------------
sub print {
- my $self = shift;
+ my $self = shift;
# if final config hash is specified then create a clone and delegate to it
# NOTE: potential problem when called print(\%data_hash1, \%data_hash2);
@@ -230,35 +270,10 @@ sub view {
sub include {
my ($self, $template, $vars) = @_;
my $context = $self->{ _CONTEXT };
- return $context->throw(Template::Constants::ERROR_VIEW,
- "no view template specified")
- unless $template;
-
- $vars = { } unless ref $vars eq 'HASH';
-
- my $notfound = $self->{ notfound };
- my $error;
-
- # try the named template
- $template = $self->template_name($template);
- $self->DEBUG("looking for $template\n") if $DEBUG;
- eval { $template = $context->template($template) };
- # try the 'notfound' template (if defined) if that failed
- if (($error = $@) && $notfound) {
- $notfound = $self->template_name($notfound);
- $self->DEBUG("not found, looking for $notfound\n") if $DEBUG;
- eval { $template = $context->template($notfound) };
-
- return $context->throw(Template::Constants::ERROR_VIEW, $error)
- if $@; # return first error
- }
- elsif ($error) {
- $self->DEBUG("no 'notfound'\n")
- if $DEBUG;
- return $context->throw(Template::Constants::ERROR_VIEW, $error);
- }
+ $template = $self->template($template);
+ $vars = { } unless ref $vars eq 'HASH';
$vars->{ view } ||= $self;
$context->include( $template, $vars );
}
@@ -279,8 +294,11 @@ sub template {
unless $template;
my $notfound = $self->{ notfound };
- my $error;
+ my ($block, $error);
+ return $block
+ if ($block = $self->{ _BLOCKS }->{ $template });
+
# try the named template
$template = $self->template_name($template);
$self->DEBUG("looking for $template\n") if $DEBUG;
@@ -288,12 +306,14 @@ sub template {
# try the 'notfound' template (if defined) if that failed
if (($error = $@) && $notfound) {
- $notfound = $self->template_name($notfound);
- $self->DEBUG("not found, looking for $notfound\n") if $DEBUG;
- eval { $template = $context->template($notfound) };
+ unless ($template = $self->{ _BLOCKS }->{ $notfound }) {
+ $notfound = $self->template_name($notfound);
+ $self->DEBUG("not found, looking for $notfound\n") if $DEBUG;
+ eval { $template = $context->template($notfound) };
- return $context->throw(Template::Constants::ERROR_VIEW, $error)
- if $@; # return first error
+ return $context->throw(Template::Constants::ERROR_VIEW, $error)
+ if $@; # return first error
+ }
}
elsif ($error) {
$self->DEBUG("no 'notfound'\n")
@@ -362,9 +382,28 @@ sub AUTOLOAD {
"attempt to view private member: $item");
}
elsif (exists $self->{ $item }) {
+ # update existing config item (e.g. 'prefix') if unsealed
+ return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW,
+ "cannot update config item in sealed view: $item")
+ if @_ && $self->{ SEALED };
$self->DEBUG("accessing item: $item\n") if $DEBUG;
return @_ ? ($self->{ $item } = shift) : $self->{ $item };
}
+ elsif (exists $self->{ data }->{ $item }) {
+ # get/update existing data item (must be unsealed to update)
+ return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW,
+ "cannot update item in sealed view: $item")
+ if @_ && $self->{ SEALED };
+ $self->DEBUG(@_ ? "updating data item: $item <= $_[0]\n"
+ : "returning data item: $item\n") if $DEBUG;
+ return @_ ? ($self->{ data }->{ $item } = shift)
+ : $self->{ data }->{ $item };
+ }
+ elsif (@_ && ! $self->{ SEALED }) {
+ # set data item if unsealed
+ $self->DEBUG("setting unsealed data: $item => @_\n") if $DEBUG;
+ $self->{ data }->{ $item } = shift;
+ }
elsif ($item =~ s/^$self->{ view_prefix }//) {
$self->DEBUG("returning view($item)\n") if $DEBUG;
return $self->view($item, @_);
@@ -373,6 +412,10 @@ sub AUTOLOAD {
$self->DEBUG("returning include($item)\n") if $DEBUG;
return $self->include($item, @_);
}
+ elsif ($self->{ include_naked }) {
+ $self->DEBUG("returning naked include($item)\n") if $DEBUG;
+ return $self->include($item, @_);
+ }
elsif ($self->{ view_naked }) {
$self->DEBUG("returning naked view($item)\n") if $DEBUG;
return $self->view($item, @_);
@@ -395,20 +438,53 @@ Template::View - customised view of a template processing context