[Templates-cvs] cvs commit: TT3/lib/Template Source.pm
cvs@template-toolkit.org
cvs@template-toolkit.org
Tue, 23 Mar 2004 14:57:59 +0000
cvs 04/03/23 14:57:59
Added: lib/Template Source.pm
Log:
* added Template::Source module
Revision Changes Path
1.1 TT3/lib/Template/Source.pm
Index: Source.pm
===================================================================
#========================================================================
#
# Template::Source
#
# DESCRIPTION
# Object representing a template source document.
#
# 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: Source.pm,v 1.1 2004/03/23 14:57:58 abw Exp $
#
#========================================================================
package Template::Source;
use strict;
use warnings;
use Template::Base;
use base qw( Template::Base );
use vars qw( $VERSION $DEBUG $ERROR $LIFE );
$VERSION = sprintf("%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/);
$DEBUG = 0 unless defined $DEBUG;
$ERROR = '';
$LIFE = 60; # default lifetime is 60 seconds before re-stat
#------------------------------------------------------------------------
# init()
#
# Constructor method accepting the following named parameters
# id: a unique id
# name: short name for source file (grokked from path if not defined)
# path: source path relative to TT templates_path or other provider root
# time: modification time of template source
# stat: last time the file was checked for freshness
# life: how long between checking file for freshness
# fresh: flag to indicate that template is always fresh
# text: source text
# code: source code (usually compiled from text)
# provider: reference to the provider of the source
# load: parameter to pass to provider load()/time() methods
#------------------------------------------------------------------------
sub init {
my ($self, $config) = @_;
my $path;
# at least one of 'path' or 'name' must be defined
return $self->error('no name or path defined')
unless defined($config->{ path })
or defined($config->{ name });
# path defaults to name
$path = $config->{ path };
$path = $config->{ name } unless defined $path;
# $path = "/$path" unless $path =~ m[^/]; # just a thought...
$self->{ path } = $path;
# name defaults to last part of path
unless (defined ($self->{ name } = $config->{ name })) {
($self->{ name } = $path) =~ s{^([^/]*/)*}{};
}
# the provider indicates who provided us - we might be
# checking back with them later to see if we're still fresh,
# using the 'load' data to indicate who we are
my $provider = $self->{ provider } = $config->{ provider };
$self->{ load } = $config->{ load };
# we need a unique id, either provided as an 'id' argument
# or constructed from the provider id (if any) and path
$self->{ id } = $config->{ id } || do {
my $pid = $provider ? $provider->id() : '';
$pid ? "${pid}:$self->{path}" : $self->{path};
};
# the modification time of the source file, the most recent
# stat time when we checked it for freshness, and the lifetime
# indicating the number of seconds that can elapse before we
# need to check for freshness again
my $now = time;
$self->{ time } = $config->{ time } || $now;
$self->{ stat } = $config->{ stat } || $now;
$self->{ fresh } = $config->{ fresh } || 0;
$self->{ life } = $config->{ life };
$self->{ life } = $LIFE unless defined $self->{ life };
# 'text' and 'code' items may or may not be defined
# and can be text strings or references to them, either
# way we end up with a text ref
foreach my $key (qw( text code )) {
$self->{ $key } =
ref $config->{ $key }
? $config->{ $key }
: \$config->{ $key }
if defined $config->{ $key };
}
# save any options defined
$self->{ options } = $config->{ options } || { };
return $self;
}
#------------------------------------------------------------------------
# id()
# name()
# path()
# time()
# life()
# stat()
#
# Accessor method to return the values of various internal items.
#------------------------------------------------------------------------
sub id {
return $_[0]->{ id };
}
sub name {
return $_[0]->{ name };
}
sub path {
return $_[0]->{ path };
}
sub time {
return $_[0]->{ time };
}
sub life {
return $_[0]->{ life };
}
sub stat {
return $_[0]->{ stat };
}
#------------------------------------------------------------------------
# options()
#
# Accessor method to return a reference to the current options hash array.
#------------------------------------------------------------------------
sub options {
return $_[0]->{ options };
}
#------------------------------------------------------------------------
# option($name)
#
# Accessor method to return the value of a a current option.
#------------------------------------------------------------------------
sub option {
my $self = shift;
return $self->{ options } unless @_;
my $name = shift;
return $self->{ options }->{ $name };
}
#------------------------------------------------------------------------
# text()
#
# Return the source text if already defined, or go and ask the provider
# to load it.
#------------------------------------------------------------------------
sub text {
my $self = shift;
my $args = @_ && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
# if we got any text passed in as a constructor argument then
# it will be defined in 'text' as a scalar reference, and thus
# is true, even if the text is empty or contains the value '0'
return $self->{ text } ||= do {
my $provider = $args->{ provider } || $self->{ provider }
|| return $self->error('no provider defined');
$provider->load($self->{ load })
|| return $self->error($provider->error());
};
}
#------------------------------------------------------------------------
# code(\%args)
#
# Return the compiled source code if already defined, or fetch the text
# and ask a compiler to compile it. In this case, a reference to a
# component must be passed as a 'context' named argument, through which
# a suitable compiler can be retrieved.
#------------------------------------------------------------------------
sub code {
my $self = shift;
my $args = @_ && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
return $self->{ code } ||= do {
$self->debug("compiling document: $self->{ name }\n") if $self->{ DEBUG };
my $text = $self->text($args) || return;
my $context = $args->{ context } || $self->{ context }
|| return $self->error('no context defined to compile code');
my $compiler = $context->compiler($self->{ compiler })
|| return $self->error($context->error());
my $code = $compiler->compile(text => $text, name => $self->{ name })
|| return $self->error($compiler->error());
$self->debug("compiled template code for $self->{ name }:\n$$code\n")
if $self->{ DEBUG };
$code;
};
}
#------------------------------------------------------------------------
# component(\%args)
#
# Fetches the compiled source code via a call to code() and evaluates it
# to (hopefully) generate a Template::Component object.
#------------------------------------------------------------------------
sub component {
my $self = shift;
my $code = $self->{ code } || $self->code(@_) || return;
my $component;
# trap any errors thrown via die()
local $SIG{__WARN__} = sub { die(@_) };
# evaluate Perl code to generate component object
$component = eval $$code;
# check for errors
return $self->error("failed to create component: $@")
if $@ || ! $component;
# set source in component
# $component->source($self);
$self->debug("created component: $component\n") if $self->{ DEBUG };
return $component;
}
#------------------------------------------------------------------------
# fresh()
#
# Checks to see if the source is still fresh. It first examines the
# 'fresh' flag which can be set to 1 to keep the source fresh
# indefinately. If that is undefined or false then it looks at the
# 'stat' and 'life' options in comparison with the current system time
# to decide if it's worth going back to the provided to see if the
# source file has changed. By default, we only check for changes
# when more than a minute has elapsed (life = 60) since we last checked
# (stat). If that is the case then we call the provider time() method.
#------------------------------------------------------------------------
sub fresh {
my $self = shift;
my $now = CORE::time();
# Fresh flag can be set if document is always considered fresh.
# Documents created from anonymous subroutines or template text
# use this flag, for example.
return 1 if $self->{ fresh };
# TODO: accept a time parameter to compare against
# don't bother checking if we've checked within the lifetime of a stat
if ($now < $self->{ stat } + $self->{ life }) {
$self->debug("stat life has not expired, skipping freshness check\n")
if $self->{ DEBUG };
return 1;
}
# TODO: should we just return OK, or not?
my $provider = $self->{ provider }
|| return $self->error('no provider defined');
my $time = $provider->time($self->{ load });
return $self->error($provider->error())
unless defined $time;
return $time > $self->{ time } ? 0 : 1;
}
#------------------------------------------------------------------------
# strip()
#
# Delete anything non-essential and potentially large (e.g. text and
# code) to reduce the memory footprint of the object as much as possible
# to make it more suitable for caching in memory.
#------------------------------------------------------------------------
sub strip {
my $self = shift;
delete @$self{ qw( text code ) };
return $self;
}
#------------------------------------------------------------------------
# stub()
#
# Return a reference to a hash array containing the minimal set of data
# items (id and time) required to identifying the source to a cache,
# store or other template storage mechanism.
#------------------------------------------------------------------------
sub stub {
my $self = shift;
return {
id => $self->{ id },
time => $self->{ time },
};
}
1;
__END__
=head1 NAME
Template::Source - object representing a template source document
=head1 SYNOPSIS
use Template::Source;
my $source = Template::Source->new({
id => 'file:/tmp/product/info', # unique ID
name => 'info', # short name
path => '/product/info', # TT "local" path
time => 987654321, # modification time
stat => 999888777, # time last checked
life => 120, # re-check every 120 seconds
text => $text, # template text
load => '/tmp/product/info', # provider load parameter
provider => $provider, # source provider
options => \%opts, # component options
}) || die Template::Source->error();
=head1 DESCRIPTION
This module implements an object to represent information relating to
the source of a template, typically a file, as provided by a
Template::Provider.
=head1 METHODS
=head2 new()
Constructor method which creates a new Template::Source object. It accepts
the following named parameters:
=head3 id
A unique identifier for the template. This will be constructed automatically
if not explicitly defined, using the provider id (if a provider is defined)
and the source path.
=head3 name
The short name of the template file. e.g. 'header'. This is extracted from
the last component of C<path> if not explicitly defined.
=head3 path
The full path of the file relative to the root directory (or other location)
of its provider. For example, a provider might locate the template in the
F</usr/local/tt3/templates/site/header> file, given a root directory of
F</usr/local/tt3/templates>. In this case, the path would be 'site/header'.
This defaults to the value of C<name> if undefined. However, at least one
of C<name> or C<path> must be provided. The constructor will fail with
an appropriate error message if not.
=head3 time
The modification time (in seconds since the epoch) of the template
file.
=head3 stat
The time (in seconds since the epoch) when the modifcation time of the
template source was last checked.
=head3 life
The number of seconds that are allowed to elapse between checking the modification
time of the template source. If 'stat' + 'life' is more than the current system
time, then the modification time is checked again and 'stat' reset.
=head3 text
A scalar or reference to a scalar containing the text of the template source
document.
=head3 code
A scalar or reference to a scalar containing the compiled Perl code for the
template source document. This is usually generated on demand from the
source C<text> by feeding it to the appropriate Template::Compiler object.
=head3 provider
A reference to the Template::Provider object (or subclass) which provided
this source document.
=head3 load
A parameter which is passed to the provider to load() or check the time()
of a source document. The base class Template::Provider module defines
this parameter as the absolute path to the source file. However, subclassed
providers are free to define it to contain whatever information is required
to subsequently load or check the time of a template source document.
=head3 options
This option can be used to provide a hash array of options which are passed
to the component that is generated from the source template.
=head2 id()
Returns the source id, provided as a named parameter to the C<new()> constructor
method or generated automatically.
=head2 name()
Returns the short name of the template source, provided as a constructor
parameter, or generated from the value provided for C<path>
=head2 path()
Returns the full path of the template source relative to the appropriate
search path of the provider.
=head2 time()
Returns the modification time of the template source as the number of
seconds since the epoch.
=head2 life()
Returns the lifetime of the template source. This indicates the time
in seconds that are allowed to elapse between calls to check the
modification time of the source document.
=head2 stat()
Returns the time in seconds since the epoch at which the modification time
of the source document was last checked.
=head2 options()
Returns a reference to a hash array of options provided as the C<options>
constructor parameter. These options are passed to the Template::Component
compiled from the template source.
my $source = Template::Source->new({
path => '/foo/bar/baz',
options => {
cache => 0,
store => 1,
},
});
=head2 option($name)
Returns the value of a particular option defined in the C<options> hash.
$cache->store($source->id(), $source)
if $source->option('cache');
=head2 text()
Returns the text defined as a constructor option or calls on the provider
(if defined) to load it on demand.
=head2 code(\%options)
Returns the compiled Perl code for the template, provided as a constructor
option or generated by calling on the appropriate compiler to compile it.
=head2 component(\%options)
Returns a reference to a Template::Component object which implements the
functionality of the template document. This is created by evaluating the
Perl code returned by the code() method.
=head2 stub()
Returns a reference to a hash array containing the minimal amount of
information (id, time) which can be used to identify templates in a
cache or store. See Template::Templates for an example of this in use.
=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: