[Templates-cvs] cvs commit: TT3/lib/Template Templates.pm

cvs@template-toolkit.org cvs@template-toolkit.org
Thu, 25 Mar 2004 14:30:41 +0000


cvs         04/03/25 14:30:41

  Added:       lib/Template Templates.pm
  Log:
  * added the Template::Templates module which implements the resource
    manager for locating, loading, compiling, caching, storing and playing
    table tennis with templates.  Well, all apart from the ping pong.
    I just added that to bring small amusement to an otherwise serious day.
  
  Revision  Changes    Path
  1.1                  TT3/lib/Template/Templates.pm
  
  Index: Templates.pm
  ===================================================================
  #========================================================================
  #
  # Template::Templates
  #
  # DESCRIPTION
  #   Templates manager.
  # 
  # 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) 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: Templates.pm,v 1.1 2004/03/25 14:30:41 abw Exp $
  #
  #========================================================================
  
  package Template::Templates;
  
  use strict;
  use warnings;
  use Template::Document;
  use Template::Base;
  use base qw( Template::Base );
  use vars qw( $VERSION $DEBUG $ERROR $THROW
               $ABSOLUTE $RELATIVE $DEFINITIVE $WALK 
               $SOURCE @CONFIG );
  
  $VERSION    = sprintf("%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/);
  $DEBUG      = 0 unless defined $DEBUG;
  $ERROR      = '';
  $THROW      = 'template';
  
  # default options
  $ABSOLUTE   = 1 unless defined $ABSOLUTE;
  $RELATIVE   = 1 unless defined $RELATIVE;
  $DEFINITIVE = 1 unless defined $DEFINITIVE;
  $WALK       = 0 unless defined $WALK;
  $SOURCE     = 'Template::Source';
  @CONFIG     = qw( absolute relative definitive walk source );
  
  
  sub init {
      my ($self, $config) = @_;
  
      $self->SUPER::init($config) || return;
      $self->pkgconf($config, @CONFIG) || return;
  
      $self->{ load_source } = $config->{ load_source }
          if exists $self->{ load_source };
  
      $self->{ idmap } = $config->{ static } ? { } : '';
      $self->{ cache } = $config->{ cache  };
      $self->{ store } = $config->{ store  };
  
      return $self;
  }
  
  
  sub fetch {
      my ($self, $context, $name) = @_;
      my ($path, $source, $id, $object);
      my $debug  = $self->{ DEBUG };
  
      $self->debug("fetch template: $name\n") if $debug;
  
      # Three levels of caching are implemented:
      #   idmap: map path to source id for template
      #   cache: map source id to template component in memory
      #   store: map source id to template component on disk
  
      my $idmap = $self->{ idmap };
      my $cache = $self->{ cache };
      my $store = $self->{ store };
      my ($mapsrc);
  
      # Fetch a list of one or more paths where the template may be located.
      # $path can already be a reference to a template document or something
      # else, but that's OK because the paths() method will spit it back and
      # the source() method will do the right thing to handle it
      
      my $paths = $self->paths($context, $name) || return;
          
      eval {
          PATH: foreach $path (@$paths) {
              $self->debug("looking for path: $path\n") if $debug;
            
              # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              # Source:
              #   If source paths are static and the requested path is an 
              #   absolute one then we can cache the source id returned by a 
              #   provider for the requested path.  Then we can subsequently 
              #   use the id to locate the component in the cache or store 
              #   instead of fetching a full source object from the provider 
              #   each time we use it
              # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              
              $mapsrc = $idmap && $path =~ m[^(\w+:)?/];
              $source = $id = undef;
              
              if (ref $path) {
                  $self->debug("casting reference to a source object\n") if $debug;
                  $source = $self->source($context, $path) || return; 
                  $id = $source->id();
                  $self->debug("new source: $id\n") if $debug
              }
              elsif ($mapsrc && ($id = $idmap->{ $path })) {
                  $self->debug("mapped path $path to component id $id\n") if $debug;
              }
              elsif ($source = $self->source($context, $path)) {
                  $id = $source->id();
                  $self->debug("sourced document: $id\n") if $debug;
                  
                  # save path/id mapping for next time if static paths
                  $idmap->{ $path } = $id if $mapsrc;
              }
              else {
                  # nothing showed up for this path so try the next on
                  next PATH;
              }
              
          
              # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              # Cache 
              #   look in the memory cache for a compiled template component,
              #   and check that it is fresh with respect to its source code
              # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              
              if ($cache && ($object = $cache->get($id))) {
                  if ($object->fresh()) {
                      $self->debug("found cached template\n") if $debug;
                      last PATH;                              ## OK ##
                  }
                  else {
                      $object = undef;
                      $self->debug('stale cached component: ', $object->error()) 
                          if $debug;
                  }
                  
              }
              
              # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              # Store
              #   If we didn't find a template component in the cache then we
              #   look for one in the store.  If we find one then we restore
              #   its source link to a valid source object so that it can
              #   subsequently check its freshness.  If we haven't got a
              #   source object (e.g. if we used a component id stored in the
              #   idmap) then we need to go and fetch a complete source object
              #   first.  We then save the restored template object in the
              #   cache, if we have one and the rights options are set.
              # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              
              if ($store && ($object = $store->get($id))) {
                  # fetch source object if we need to
                  $source ||= $self->source($context, $path) || return;
  
                  # notify component of its source object
                  $object->source($source);
  
                  if ($object->fresh()) {
                      $self->debug("found stored template\n") if $debug;
                      $object->cache($cache) if $cache;
                      last PATH;
                  }
                  else {
                      $object = undef;
                      $self->debug('stale stored component: ', $object->error()) 
                          if $debug;
                  }
              }
          
  
              # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              # Compile
              #   If we haven't got a cached/stored object by now then we're 
              #   going to have to compile it ourselves.  We first need to 
              #   fetch a complete source object if we haven't got one (e.g. 
              #   if we used an id stored in the idmap)
              # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          
              $source ||= $self->source($context, $path);
              return unless defined $source;
              
              if ($source) {
                  # save the source info for the path we located
                  $idmap->{ $path } = $source->id() if $mapsrc;
              }
              else {
                  # it's unusual but not impossible for us to get here - 
                  # a provider could advertise a template one minute and 
                  # then remove it some time later, invalidating any 
                  # cached source id we previously had in the idmap.  
                  delete $idmap->{ $path } if $mapsrc;
                  next PATH;                              ## NEXT PATH ##
              }
              
              # OK, now we should have a valid source ready to be compiled
              $object = $source->component(context => $context) 
                  || return $self->error($source->error());
              
              $object->cache($cache) if $cache;
              $object->store($store) if $store;
              
              # all done - we've got a compiled template component
              last PATH;
          }
      };
      return $self->error($@) if $@;
  
      return $object 
          || $self->decline("$name not found");
  
  }
  
  
  
  #------------------------------------------------------------------------
  # source($context, $path)
  #
  # Locates the source document for a template identified by $path.
  #------------------------------------------------------------------------
  
  sub source {
      my ($self, $context, $path) = @_;
      my $srcmod = $self->{ source };
      my $source;
  
      $self->debug("source($path)\n") if $self->{ DEBUG };
  
      # $name may already be a template source object
      return $path if UNIVERSAL::isa($path, $srcmod);
  
      if (ref $path) {
          # it may still be something else ref-like, such as 
          # a code reference or a scalar text reference
          $source = $path;
      }
      else {
          # otherwise it's the name of a template
          $source = $context->search( templates => $path ) || return;
      }
  
      # $source should be a Template::Source object (or whatever
      # the current value of $srcmod is), or a code reference, or
      # a text string or reference to one.  Either way we need to 
      # make sure we pass back a Template::Source object.
  
      if (UNIVERSAL::isa($source, $srcmod)) {
          # $source is already a source object, yay!
          return $source;
      }
      elsif (UNIVERSAL::isa($source, 'HASH')) {
          # $source is a hash reference
          $self->debug("constructing source object from hash reference\n")
              if $self->{ DEBUG };
          
          $source->{ path } = $path 
              unless defined $source->{ path };
  
          $source->{ id } = "hash:$path"
              unless defined $source->{ id };
          
          return $self->object( source => $source );
      }
      elsif (UNIVERSAL::isa($source, 'CODE')) {
          # $source is an anonymous subroutine reference
          $self->debug("constructing source object from code reference\n")
              if $self->{ DEBUG };
  
          return $self->object( source => {
              id    => "code:$path",
              path  => $path,
              time  => time(),
              code  => $source,
              text  => '',
              fresh => 1,
              cache => 0,
              store => 0,
          });
      }
      elsif (UNIVERSAL::isa($source, 'SCALAR') || ! ref $source) {
          # $source is plain text or a reference to a scalar
          $self->debug("constructing source object from template text\n")
              if $self->{ DEBUG };
  
          return $self->object( source => {
              id    => "text:$path",
              path  => $path,
              time  => time(),
              text  => $source,
              fresh => 1,
              cache => 0,
              store => 0,
          });
      }
      else {
          return $self->error("invalid template source: $source");
      }
  }
  
  
  #------------------------------------------------------------------------
  # paths($context, $name)
  #
  # Returns a list (list context) or reference to a list (scalar context)
  # of candidate paths based on current context path,  and a file name of 
  # $name.  If the $name is relative (e.g. ./foo ../foo ../../foo) then it
  # is computed relative to the current path.  If the $name is absolute 
  # (e.g. /foo) then it is left alone.  Otherwise, the method returns a 
  # list of paths, starting with $name by itself, and then appearing in 
  # each directory up to the root.  Examples:
  #    /foo/bar + /baz    => [ /baz ]
  #    /foo/bar + ./baz   => [ /foo/bar/baz ]
  #    /foo/bar + ../baz  => [ /foo/baz ]
  #    /foo/bar + baz     => [ baz, /foo/bar/baz, /foo/baz, /baz ]
  #------------------------------------------------------------------------
  
  sub paths {
      my ($self, $context, $name) = @_;
      my ($prefix, $root, @paths);
  
      # TODO: we must make sure all components have a path, or 
      # figure out what to do if they don't...
      my $base = $context->{ path } || '';
  
      # $name could be a reference to something template-like, in
      # which case we leave the caller to sort it out.
      return wantarray ? ($name) : [$name]
          if ref $name;
  
      # copy original name 
      my $path = $name;
  
      # look for a word: prefix and remove it
      $prefix = $1
          if $path =~ s/^(\w+)://;
  
      if ($path =~ m{^//}) {
          return $self->decline("definitive paths not allowed: $name")
              unless $self->{ definitive };
          $self->debug("- using definitive path: $name\n") if $DEBUG;
          push(@paths, $name);
      }
      elsif ($path =~ m{^/}) {
          return $self->decline("absolute paths not allowed: $name")
              unless $self->{ absolute };
          $self->debug("- using absolute path: $name\n") if $DEBUG;
          push(@paths, $name);
      }
      elsif ($path =~ /^\./) {
          return $self->decline("relative paths not allowed: $name")
              unless $self->{ relative };
  
          # any name starting in '.' is relative to current $base path so
          # keep stripping dots and slashes, modifying it accordingly
          while ($path =~ s[^(\.+)/][]) {
              my $dots = $1;
  
              if ($dots eq '.') {
                  # do nothing - keep path as it is
              }
              elsif ($dots eq '..') {
                  # remove last part of current path to move up a directory
                  $base =~ s{ / [^/]+ $ }{}x;
              }
              else {
                  return $self->decline("invalid prefix on template name: $dots/");
              }
          }
          $path = "$base/$path";
          $path = "$prefix:$path" if $prefix;
          $self->debug("- using relative path: $path\n") if $DEBUG;
          push(@paths, $path);
      }
      else {
          # first try the name by itself
          push(@paths, $name);
          $self->debug("- using own path: $name\n") if $DEBUG;
  
          # TODO: allow user to set what the default behaviour is, 
          # instead of just having a walk flag
  
          # then try walking up or down the path 
          if ($self->{ walk }) {
              my @walk;
              for (;;) {
                  push(@walk, "$base/$path");
                  last unless $base;
                  $base =~ s{ / [^/]+ $ }{}x;
              }
              if ($self->{ walk } eq 'down') {
                  @walk = reverse @walk;
              }
              elsif ($self->{ walk } ne 'up') {
                  return $self->error( "invalid path walking direction: ",
                                       $self->{ walk } );
              }
              push(@paths, map {
                  $self->debug("- walking path $self->{ walk }: $_\n") if $DEBUG;
                  $prefix ? "$prefix:$_" : $_;
              } @walk);
          }
      }
  
      return wantarray ? @paths : \@paths;
  }
  
  
  
  
  1;
  
  __END__
  
  =head1 NAME
  
  Template::Templates - templates resource manger
  
  =head1 SYNOPSIS
  
      # we have lots of friends who join us for the ride
      use Template::Cache;
      use Template::Store;
      use Template::Provider;
      use Template::Component;
      use Template::Compiler;       # not working yet
      use Template::TT2::Compiler;  # this is!
      use Template::Templates;
  
      # memory cache and persistant store for compiled templates
      my $cache = Template::Cache( size => 128 );
      my $store = Template::Store( directory => '/tmp/tt3/store' );
  
      # templates resource manager
      my $templates = Template::Templates->new({
          cache  => $cache,  # in-memory cache
          store  => $store,  # persistant store
          static => 1,       # no dynamic provider paths
      });
  
      # templates provider fetches template source
      my $provider = Template::Provider({
          # where to look for templates
          path => [ 
              '/home/dent/web/tt3',      # default options
              '/home/dent/web/tt2' => {  # custom options
                  compiler => 'tt2',       # TT2 templates
                  cache    => 1,           # do cache
                  store    => 0,           # don't store
              },
          ],
          # default options for templates
          options => {
              compiler => 'tt3',  # default compiler
              cache    => 1,      # cache template
              store    => 1,      # store template        
          },
      });
  
      # TODO: this should use Template::Compilers
      # compilers required to compile templates
      my $compilers = {
          tt3 => Template::Compiler->new(),      # not yet working :-(
          tt2 => Template::TT2::Compiler->new(), # is working, kinda
      };
  
      # now we need a parent component to hang it all together
      my $component = Template::Component->new({
          templates => $provider,       # the provider, not us
          compilers => $compilers,      # compilers we need
          resources => {
              templates => $templates,  # here we are
          },
      });
  
      # Phew!  Finally we can ask the component to process a template
      # and it will delegate to the Template::Templates object
      $component->process('/product/header');
  
      # or we can go direct if we really want to
      $templates->fetch($component, '/product/header');
  
  =head1 DESCRIPTION
  
  This module implements a resource manager for finding, fetching, compiling,
  caching and storing templates.
  
  It's really not half as complicated as the above synopsis might suggest.
  The module is itself reasonably straightforward, but it sits right in the
  middle of a collaboration between a number of different objects.  That
  makes it hard to show it being used in isolation without bringing a whole
  bunch of other players onto the field.
  
  But then again, you wouldn't normally be using a Template::Templates
  object directly because that's all hidden away behind the nice front
  end that we'll be providing for you.  
  
  =head1 METHODS
  
  =head2 new()
  
  Constructor method which creates a new Template::Templates object.
  
      my $templates = Template::Templates->new();
  
  Named parameters can be passed as a list of options, or as a reference
  to a hash array:
  
      # list of options
      $templates = Template::Templates->new( walk => 'up' );
  
      # reference to hash array of options
      $templates = Template::Templates->new({ walk => 'up' });
  
  The following named parameters can be defined.
  
  =head3 absolute
  
  This flag is enabled by default and allows template names to be
  specified with absolute paths starting withing a C</> character.  
  
      [% PROCESS /product/header %]
  
  Note however that this represents an absolute I<template> path, not an
  absolute I<file> path.  The name specified will still be resolved relative
  to whatever search path(s) the template provider(s) define.
  
  For example, a provider may be defined as follows:
  
      my $provider = Template::Provider->new(
          path => [ '/home/dent/web/tt3', '/home/dent/web/tt2' ]
      );
  
  When we request a template using an absolute template path, as shown
  in the left column below, it will be resolved to one of two possible
  files with absolute file paths, shown in the column on the right.
  
      /header             => /home/dent/web/tt3/header
                          => /home/dent/web/tt2/header
  
      /product/header     => /home/dent/web/tt3/product/header
                          => /home/dent/web/tt2/product/header
  
  From the perspective of a template author, the virtual filestem
  represented by the template paths on the left, is the only one that
  exists.  How that maps onto a real file system or other storage
  mechanism is entirely hidden from view, being of concern only to the
  providers responsible for implementing the abstraction.
  
  This is a change in behaviour from previous versions of the Template
  Toolkit (pre-v3) in which the C<ABSOLUTE> flag allowed you to access
  templates using absolute file systems paths, and was disabled by default.
  If you want to emulate the behaviour of the TT2 C<ABSOLUTE> flag then 
  you should define the root filesystem directory (e.g. C</>) in the 
  search path of one or more template providers.
  
      my $provider = Template::Provider->new(
          path => [ '/home/dent/web/tt3', '/' ]
      );
  
  Now template paths will be mapped to the F</home/dent/web/tt3> directory,
  or to C</>.
  
      /header             => /home/dent/web/tt3/header
                          => /header
  
      /etc/passwd         => /home/dent/web/tt3/etc/passwd
                          => /etc/passwd
  
  If it's not immediately obvious that this is an inherently dangerous
  practice, then consider this a formal warning to that effect.  Doing 
  so allows template authors to access any files on the file system that
  are accessible by the current user (who may not be the template author).
  
  That's one reason why we decided to change the meaning of the
  C<ABSOLUTE> option for TT3.  It is very unusual for someone to want to
  enable access to an entire file system, making this option all but
  redundant.  On the other hand, it is a more common requirement (and
  oft-cited limitation of previous versions of the Template Toolkit) to
  request a template based on the illusion of the virtual template file
  system that the providers collectively implement.
  
  =head3 relative
  
  This flag is enabled by default and allows template names to be
  specified with relative paths starting withing C<./> or C<../>
  character sequences.
  
      [% PROCESS ./header %]
      [% PROCESS ../header %]
      [% PROCESS ../../header %]
  
  The full path of the template is resolved relative to the path of the
  current template being processed.  For example, if you are currently
  processing the F</product/widget/info> template then the following
  paths will be resolved as shown below:
  
      ./header          => /product/widget/header
      ../header         => /product/header
      ../../header      => /header
      ../../../header   => /header    # can't go any higher
  
  Note that excessive C<../> elements in the path are ignored, as shown 
  in the last example.  There is no directory above C</>, so the extra 
  C<../> has no effect.
  
  As with the C<ABSOLUTE> option, this marks a change in behaviour from
  the corresponding C<RELATIVE> option in TT2 which located templates
  relative to the current working directory.  Like C<ABSOLUTE>, this
  turned out to be an ill-conceived idea and has been replaced by something
  far more useful and powerful in TT3.
  
  To emulate the behaviour of the TT2 C<RELATIVE> option, you can
  explicitly add the current working directory to the search path of one
  or more template providers.  This is shown in the following example:
  
      use Cwd;
  
      my $provider = Template::Provider->new(
          path => [ '/home/dent/web/tt3', getcwd() ]
      );
  
  Template names can also be specified with a additional prefix.  This
  is used to bind it to a particular provider, or to indicate to a
  provider what kind of template is requested, how it should be fetched,
  where it should be fetched from, or to denote some other custom
  behaviour.
  
      [% PROCESS src:header %]
      [% PROCESS user:header %]
  
  The prefix is effectively ignored when relative paths are expanded (or
  more accurately, it is removed, the path is expanded, and then the
  prefix is replaced).  This is shown in the following examples where
  the template names in the left column are mapped to the template paths
  on the right.
  
      src:./header          => src:/product/widget/header
      src:../header         => src:/product/header
      src:../../header      => src:/header
      src:../../../header   => src:/header
  
  =head3 definitive
  
  This flag is also enabled by default and allows template to be
  specified using definitive paths that start with the C<//> character
  sequence.  This is most often used in conjunction with a provider
  prefix.
  
      [% PROCESS http://template-toolkit.org/templates/news.tt3 %]
      [% INSERT  file://tmp/report.log %]
  
  An absolute template name starting with a single C</> character is
  always assumed to be relative to one of the root paths in the virtual
  file system that the providers collectively define.  So it's not 
  really absolute, but it just looks that way from the perspective 
  of our virtual template file system.
  
  On the other hand, a definitive path starting with a double C<//>
  character sequence is taken to be really, truly and utterly absolute
  to the whole information space of the visible universe (and that's a
  pretty big place).  Well OK, maybe we're exaggerating slightly, but
  that's the basic idea.  Unlike an absolute path which is only really
  absolute within our virtual template namespace (but does eventually
  get mapped by a provider onto a definitive path, e.g. a file in a real
  filesytem), a definitive path is assumed to be part of someone else's
  address space, and we don't go messing with it.  Instead we just hand
  it over to each provider in turn, passing the full path exactly as
  written, and leave it up to one of them to figure out what to do with
  it.
  
  Providers that haven't been configured to accept a particular prefix
  will decline the request outright.  In practice, that means it is
  perfectly safe and legal to write the following in a template:
  
      [% INSERT file://etc/passwd %]
  
  Unless you explicitly define a provider that responds to the C<file:>
  prefix, the directive will fail, reporting that the template cannot be
  found.  Furthermore, your C<file:> provider doesn't have to map its
  files directly onto the filesystem root.  You can set its path to 
  any directory you like so that the requested file is fetched from 
  somewhere else.
  
      # TODO: implement prefix management in providers (not yet working)
  
      my $provider = Template::Provider->new(
          prefix => 'file',
          path   => '/home/dent/web/files',
      );
  
  You can even map the C<file:> prefix to a different provider,
  say one that fetches templates from a database.  From the 
  perspective of the template, it looks like a file, but behind the
  scenes it may be implemented as something else.
  
  =head3 walk
  
  The C<walk> option determines the behaviour used to locate a template
  that isn't specified using an absolute, relative, or definitive path.
  
      [% INCLUDE header %]
  
  If C<walk> is set to C<up>, then the C<fetch()> method will first
  look for C<header> defined as a block or local template, and then
  go on to look for C<header> in the current directory (based on the 
  current template location, as per the C<relative> option), and then 
  in the parent directory, and so on.
  
  So if we are current processing the F</product/widget/info> and 
  request F<header>, the following paths will be tried in order until
  the first that matches a valid template.
  
      header
      /product/widget/header
      /product/header
      /header
  
  If C<walk> is set to C<down> then the order of the paths searched
  is reversed.  However, the original name requested, C<header>, is 
  always tried first.
  
      header
      /header
      /product/header
      /product/widget/header
  
  TODO: I'm planning to add an explicit path syntax for these as well,
  I was thinking C<.../header> for walk up and C</.../header> to walk
  down.
  
  =head3 static
  
  This flag can be set to indicate that a particular template path
  (e.g. F</product/header>) will always resolve to the same template
  component.
  
  The Template::Templates module does not load any templates itself, but
  instead delegates to one or more Template::Providers objects.  A
  provider may use a dynamic search path that changes the list of
  directories in which it looks for templates over time.  So if we ask
  it for F</product/header> now, we might get back a template from the
  F</web/templates/product/header> file.  But when we ask it again some
  time later, its search path may have changed and we might instead get
  a template from the F</web/templates/custom/orange/product/header>
  file.
  
  To account for this, the C<fetch()> method asks the provider(s) to
  locate (but not load) the template each time it is used.  The provider
  returns a unique identifier for each template (usually generated from
  the full file path) which allows us to disambiguate one
  C</product/header> template from another.  Once we know which specific
  template component we're looking for, we can check the cache and/or
  store for a previously compiled component, or we can load and compile
  the template ourselves and cache/store it for later.
  
  If the C<static> flag is set then the module assumes that the provider
  paths are not going to change.  This saves us from having to ask the
  provider to map the template name to a component id each time we use
  it, making it run a little faster.
  
  
  =head3 cache
  
  A reference to a Template::Cache object, or a subclass, or an object
  with a compatible get()/set() interface (e.g. Cache::Cache objects).
  The cache is responsible for caching compiled templates in memory.
  
      my $cache = Template::Cache->new( size => 128 );
  
      my $templates = Template::Templates->new(
          cache => $cache,
      );
  
  =head3 store
  
  A reference to a Template::Store object, or a subclass, or an object
  with a compatible get()/set() interface.  The store is responsible for
  storing compiled templates persistantly on disk or other storage
  medium.
  
  =head3 source
  
  This option can be used to provide the name of an object class to be 
  used instead of Template::Source.  This is used to upgrade templates 
  that are specified as subroutine references, scalar references or 
  other "inline" declarations that are not sourced from a provider.
  
      my $templates = Template::Templates->new(
          source => 'My::Template::Source',
      );
  
  The module specified in this option will be loaded automatically.
  
  =head3 load_source
  
  This flag can be explciticly set to 0 to prevent the module defined in 
  the C<source> option from being loaded automatically. 
  
      package My::Template::Source;
      ...
  
      package main;
      my $template = Template::Template->new({
          source => 'My::Template::Source',
          load_source => 0,
      });
  
  =head2 fetch($context, $name)
  
  This method locates a template in the context passed as the first
  argument with the name passed as the second.
  
      my $header = $templates->fetch($context, 'header');
  
  The first argument, C<$context>, should be a reference to the current
  component from where the search to locate the template should begin.
  Given that the C<fetch()> method is usually called from the
  C<template()> method of Template::Component (TODO: perhaps soon to be
  the Template::Template subclass of it?), the C<$context> argument
  passed is the C<$self> reference of the component.
  
      package Template::Component;
  
      sub template {
          my ($self, $name) = @_;
  
          ...
  
          my $component = $templates->fetch($self, $name);
  
          ...
      }
  
  The C<fetch()> method first calls the C<paths()> method to expand the
  template name into one or more candidate paths.  It then calls the
  C<source()> method for each (first looking in a local cache if the
  C<static> flag is set), to see if the template can be located.  If it
  is, then a unique identifier for the template is returned (wrapped up
  as part of a Template::Source object).  This identifier can then be 
  used to load a previously compiled template from the in-memory cache,
  or from the persistant store.  Failing that, the template source is 
  loaded and compiled and the resulting component is added to the cache
  and store as appropriate.
  
  If the C<static> flag is set then the C<fetch()> method only locates
  the source for a given path once.  The unique identifier returned for
  a path is stored locally, allowing the template to be subsequently
  fetched from the cache or store without have to query the provider
  each time.
  
  =head2 source($context, $path)
  
  This method locates the source of a template and returns a
  Template::Source object. 
  
      my $source = $templates->source($context, '/product/header');
  
  The first argument is a reference to a component object that defines
  the current operating context.  The second argument provides the full
  template path (<i>not</i> the full file system path) of the template
  in question.  
  
  The method returns a Template::Source object representing the located
  template, or undef of the template cannot be located.
  
  The C<$path> argument can also contain a reference to a subroutine,
  template text, or hash array, all of which are upgraded to
  Template::Source objects and returned.
  
  =head2 paths($context, $name)
  
  This method expands the template name passed as a second argument and
  returns a list of one or more absolute templates paths (absolute in
  the sense of a template name like F</product/header>, not absolute
  file names) computed relative to the current context path.
  
  For example, if the template defining the current context is
  F</product/info> then a request for the F<./header> template will
  resolve to F</product/header>, while F<../header> will resolve to
  F</header>.
  
  =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) 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: