[Templates-cvs] cvs commit: TT3/lib/Template Provider.pm
cvs@template-toolkit.org
cvs@template-toolkit.org
Tue, 23 Mar 2004 13:24:23 +0000
cvs 04/03/23 13:24:23
Added: lib/Template Provider.pm
Log:
* added new Template::Provider module
Revision Changes Path
1.1 TT3/lib/Template/Provider.pm
Index: Provider.pm
===================================================================
#========================================================================
#
# Template::Provider
#
# DESCRIPTION
# Base class object for providing templates and other resources.
#
# AUTHOR
# Andy Wardley <abw@wardley.org>
#
# COPYRIGHT
# Copyright (C) 1996-2004 Andy Wardley. All Rights Reserved.
# Copyright (C) 1998-2002 Canon Research Centre Europe Ltd.
# Copyright (C) 2002-2004 Fotango Ltd.
#
# This module is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# REVISION
# $Id: Provider.pm,v 1.1 2004/03/23 13:24:23 abw Exp $
#
#========================================================================
package Template::Provider;
use strict;
use warnings;
use Template::Source;
use Template::Base;
use base qw( Template::Base );
use vars qw( $VERSION $ERROR $DEBUG $SOURCE
$MAX_PATHS $STATIC $ID @CONFIG );
$VERSION = sprintf("%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/);
$ERROR = '';
$DEBUG = 0 unless defined $DEBUG;
$SOURCE = 'Template::Source';
$MAX_PATHS = 32;
$STATIC = 0;
$ID = 1;
@CONFIG = qw( source max_paths static );
#------------------------------------------------------------------------
# init(\%config)
#------------------------------------------------------------------------
sub init {
my ($self, $config) = @_;
# set id or have one generated
$self->id($config->{ id });
# set various other config options
$self->pkgconf($config, @CONFIG) || return;
# set path, if defined, or default to nothing
if (defined $config->{ path }) {
$self->path($config->{ path }) || return;
}
else {
$self->{ path } = [ ];
}
# allow base class to provide templates from a hash
$self->{ templates } = $config->{ templates };
return $self;
}
#------------------------------------------------------------------------
# id()
# id($new_id)
#
# We give each provider a unique id, or generate one automatically for
# it. We need to be able to tell one provider apart from another so that
# we can track which provider provided each template. Once an id has
# been set it cannot be changed.
#------------------------------------------------------------------------
sub id {
my $self = shift;
return $self->{ id } ||= shift || do {
my ($sec, $min, $hour, $day, $mon, $year) = localtime(CORE::time);
sprintf( "%04d%02d%02d%02d%02d%02d%03d",
$year + 1900, $mon + 1, $day,
$hour, $min, $sec,
$ID++ );
};
}
#------------------------------------------------------------------------
# fetch($name, \%options)
#
# Fetch a template document if it can be found in any of the locations
# defined in the paths for this provider. Calls fetch_path() for each
# path.
#------------------------------------------------------------------------
sub fetch {
my $self = shift;
my $name = shift;
my $args = @_ && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
my $paths = $self->paths() || return;
my ($path, $opts, $item);
$self->debug("fetch($name)\n") if $self->{ DEBUG };
foreach my $p (@$paths) {
if (UNIVERSAL::isa($p, 'ARRAY')) {
($path, $opts) = @$p;
$opts = { %$opts, %$args };
}
else {
# we keep $path separate from $p to avoid
# aliasing problems in the foreach loop
$path = $p;
$opts = $args;
}
$self->debug("calling fetch_path($path, $name, $opts)\n")
if $self->{ DEBUG };
$item = $self->fetch_path($self->join_path($path, $name), $opts);
return unless defined $item;
last if $item;
}
$self->debug("fetched $name: $item\n") if $self->{ DEBUG };
return $item
|| $self->decline("$name not found");
}
#------------------------------------------------------------------------
# fetch_path($path, $name, \%options)
#
# Fetch a template document identified by $name, from a specific location
# provided as $path.
#------------------------------------------------------------------------
sub fetch_path {
my ($self, $path, $opts) = @_;
my $templates = $self->{ templates }
|| return $self->decline('no templates defined');
my $template = $templates->{ $path }
|| return $self->decline('template not found: $path');
my $source = {
path => $path,
time => CORE::time(),
text => $template,
fresh => 1,
};
return $self->source($source);
}
#------------------------------------------------------------------------
# join_path($path1, $path2, ...)
#
# Join all the arguments path to form a composite path.
#------------------------------------------------------------------------
sub join_path {
my ($self, @paths) = @_;
my $path = '';
my $next;
while (@paths) {
$next = shift @paths;
$next =~ s[^/][];
$path .= '/' unless $path =~ m[/$];
$path .= $next;
}
return $path;
}
#------------------------------------------------------------------------
# path()
# path($new_path)
# path($new_path_1, $new_path2, ...)
#
# Method to get the current path or set a new path for the provider
# to search. Each path can be a literal string (e.g. '/tmp/foo'),
# a subroutine reference, a reference to an object implementing a
# paths() method, or a reference to a list containing any of the
# above. Subroutines and objects can return any of these items and
# they will be expanded appropriately.
#------------------------------------------------------------------------
sub path {
my $self = shift;
if (@_) {
$self->debug("set_path: @_\n") if $self->{ DEBUG };
# update path
$self->{ path } =
(@_ == 1) && UNIVERSAL::isa($_[0], 'ARRAY')
? shift : [ @_ ];
# invalidate any cached paths
delete $self->{ paths }
}
return $self->{ path };
}
#------------------------------------------------------------------------
# paths()
# paths($path)
#
# Evaluates the path list (or the internal path list if not provided as an
# argument, ignoring any blank entries, and calling any subroutine or
# object references to return dynamically generated path lists.
# Returns a reference to a new list of paths or undef on error.
#------------------------------------------------------------------------
sub paths {
my $self = shift;
my $limit = $self->{ max_paths } || $MAX_PATHS;
my $debug = $self->{ DEBUG };
my ($path, @ipaths, @opaths, $options);
# parse arguments
shift while @_ && ! defined $_[0];
if (@_ == 1 && UNIVERAL::isa($_[0], 'ARRAY')) {
# single argument is a reference to a list
$path = shift;
@ipaths = @$path;
}
elsif (@_) {
# multiple arguments are individual paths
@ipaths = @_;
}
elsif ($self->{ paths }) {
# no arguments, but we have cached paths
$self->debug("returning cached paths\n") if $debug;
return $self->{ paths }; ## RETURN ##
}
else {
# no arguments and no cached paths so build from scratch
@ipaths = @{ $self->{ path } };
}
# keep expanding the input path list until we run out of
# paths or exceed the max_paths limit
while (@ipaths && --$limit) {
# get next path, false value terminates list
$path = shift(@ipaths);
# undef terminates list, any other false value is ignored
last unless defined $path;
next unless $path;
unless (ref $path) {
# the usual case of a simple path, followed
# by an optional hash ref of options
if (@ipaths && ref $ipaths[0] eq 'HASH') {
$self->debug("path + options: $path\n") if $debug;
$options = shift(@ipaths);
push(@opaths, [$path, $options]);
}
else {
$self->debug("path: $path\n") if $debug;
push(@opaths, $path);
}
}
elsif (UNIVERSAL::isa($path, 'ARRAY')) {
# any list references get expanded back onto the
# start of the input path list
$self->debug("expanding path list\n") if $debug;
unshift(@ipaths, @$path);
}
elsif (UNIVERSAL::isa($path, 'CODE')) {
# any code references get called and the results
# pushed back onto the input list for checking
$self->debug("calling path code: $path\n") if $debug;
$path = eval { &$path() };
if ($@) {
chomp $@;
return $self->error($@);
}
unshift(@ipaths, $path);
}
elsif (UNIVERSAL::can($path, 'paths')) {
# any objects with a paths() method gets called the same
$self->debug("calling path object: $path\n") if $debug;
my $newpath;
$newpath = eval { $path->paths() };
if ($@) {
chomp $@;
return $self->error($@);
}
# undefined value is an error
return $self->error($path->error())
unless defined $newpath;
# false value is ignored (decline)
unshift(@ipaths, $newpath) if $newpath;
}
else {
# problem is between keyboard and chair
return $self->error("invalid path item: $path");
}
}
return $self->error("exceeded path limit ($self->{ max_paths })")
unless $limit;
# cache expanded paths only if the static flag is set
$self->{ paths } = \@opaths
if $self->{ static };
return wantarray ? @opaths : \@opaths;
}
#------------------------------------------------------------------------
# source(\%options)
#
# Create a new Template::Source object (or whatever 'source' is set to).
#------------------------------------------------------------------------
sub source {
my $self = shift;
$self->object( source => @_ );
}
#------------------------------------------------------------------------
# load($file)
#
# Method to load the template from the file or other resource identified
# by the first argument. This method is usually called by a document
# object that was created via the document() method above. The $file
# value passed is whatever the provider sets the document 'load' parameter
# to be.
#------------------------------------------------------------------------
sub load {
my $self = shift;
return $self->error('load() not implemented in the provider base class');
}
#------------------------------------------------------------------------
# time($file)
#
# Return the modification time of the file or other resource named as an
# argument, as per load().
#------------------------------------------------------------------------
sub time {
my $self = shift;
return $self->error('time() not implemented in the provider base class');
}
#------------------------------------------------------------------------
# validate($file)
#
# Validation method used to check that it's OK to go ahead and load a
# template. Argument is as per load().
#------------------------------------------------------------------------
sub validate {
my $self = shift;
# Looks alright to me, but then what do I know? I'm just a humble
# base class provider and they haven't seen fit to provide me with
# a validation method of my own.
return 1;
}
1;
__END__
=head1 NAME
Template::Provider - locate and load template source documents
=head1 SYNOPSIS
use Template::Provider;
my $provider = Template::Provider->new({
path => ['/home/abw/tt3', '/usr/local/tt3'],
});
my $source = $provider->fetch('myfile')
|| die $provider->error();
=head1 DESCRIPTION
This module implements a base class provider for the Template Toolkit.
A provider is responsible for locating templates on disk, in a database,
or using some other kind of storage/retrieval system, and returning
Template::Source objects to represent the pertinent information relating
to them.
=head1 METHODS
=head2 new(\%config)
Constructor method which creates a new Template::Provider object. It accepts
an optional reference to a hash array, or a list of named parameters which can
include the following options.
=head3 id
A unique identifier for the provider, used to generate unique
identifiers for the source documents created. A default id will be
generated if not otherwise provided. Note however that this id contains
the current timestamp and as such, will change each time you invoke the
provider. This may defeat the storage of compiled template documents.
TODO: this isn't a very good idea and possibly needs a slight re-think.
=head3 path
A path or reference to a list of paths that define the possible locations of
template provided by this object. These are analagous to directories in
a file system (and indeed are exactly that for Template::Provider::File), but
may of course be employed by a provider that is not based on a file system.
# single path
my $provider = Template::Provider->new({
path => '/usr/local/tt3/templates',
});
# reference to list of paths
my $provider = Template::Provider->new({
path => [ '/usr/local/tt3/templates',
'/home/abw/templates' ],
});
In addition to static paths defined as above, the items in a C<path>
list can also be subroutines that return a reference to a list of paths,
or an object that implements the C<path()> method to do the same.
my $provider = Template::Provider->new({
path => [
'/usr/local/tt3/template',
sub {
# trivial example, but could do anything here...
return [ '/usr/local/tt2/templates',
'/home/abw/templates' ],
},
My::Paths->new(), # has paths() method
},
});
=head3 max_paths
This option can be set to limit the number of paths that are generated
dynamically by subroutines or objects defined in the C<path> option.
If unspecified, the value for this is taken from the $MAX_PATHS
package variable which is set to 32 by default.
=head3 static
This option can be set to indicate that the paths defined as
configuration options, or those returned by subroutines or objects in
the C<path> list, do not change over time. In this case, the
providers expands the path list once and only once. The default value
for this is 0, resulting in the paths being examined and expanded if necessary
for each request.
=head2 id()
Returns the unique identifier for the provider, either provided as a
constructor parameter or generated automatically.
=head2 fetch($name, \%options)
Fetch a template matching the first argument, $name. This method
constructs a complete path for a file by joining the name onto each
path in the C<path> list, and then calls C<fetch_path()> to see if
the template can be fetched.
my $source = $provider->fetch('wibble')
|| die $provider->error();
=head2 fetch_path($path, \%options)
Fetch a template matching the full path given by the first argument.
my $source = $provider->fetch_path('/home/abw/templates/wibble')
|| die $provider->error();
=head2 join_path(@parts)
Joins all arguments into a single path using C</> as a delimiter.
This may be re-defined in subclassed providers to implement
different path styles.
=head2 path()
Accessor method which returns a reference to the current path list.
=head2 paths()
This method examines the current C<path> list and expands any list
references and calls any subroutines or object to construct a complete
list of possible paths by which the template might be located.
=head2 source(\%args)
Method to create a new Template::Source object from the argument
provided.
=head2 load($path)
Load the contents of the template defined by $path. This does nothing
in the base class and returns an error to that effect.
=head2 time($path)
Return the modification of the template defined by $path. This does
nothing in the base class and returns an error to that effect.
=head2 validate($path)
Validates that the path passed as an argument is valid and that the
template defined by that path can be accessed.
=head1 AUTHOR
Andy Wardley E<lt>abw@wardley.orgE<gt>
=head1 VERSION
$Revision: 1.1 $
=head1 COPYRIGHT
Copyright (C) 1996-2004 Andy Wardley. All Rights Reserved.
Copyright (C) 1998-2002 Canon Research Centre Europe Ltd.
Copyright (C) 2003-2004 Fotango Ltd.
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
=cut
# Local Variables:
# mode: perl
# perl-indent-level: 4
# indent-tabs-mode: nil
# End:
#
# vim: expandtab shiftwidth=4: