[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: