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

cvs@template-toolkit.org cvs@template-toolkit.org
Fri, 10 Dec 2004 14:59:22 +0000


cvs         04/12/10 14:59:22

  Modified:    lib/Template Component.pm
  Log:
  * new component containing only the component related bits - other
    stuff moved into Context and Scope
  
  Revision  Changes    Path
  1.7       +100 -437  TT3/lib/Template/Component.pm
  
  Index: Component.pm
  ===================================================================
  RCS file: /template-toolkit/TT3/lib/Template/Component.pm,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- Component.pm	2004/11/24 16:22:40	1.6
  +++ Component.pm	2004/12/10 14:59:22	1.7
  @@ -26,7 +26,7 @@
   #   * use cheap clone method with bless rather than calling new()?
   #
   # REVISION
  -#   $Id: Component.pm,v 1.6 2004/11/24 16:22:40 abw Exp $
  +#   $Id: Component.pm,v 1.7 2004/12/10 14:59:22 abw Exp $
   #
   #========================================================================
   
  @@ -38,63 +38,96 @@
   use Template::Base;
   use base qw( Template::Base );
   
  -our $VERSION   = sprintf("%d.%02d", q$Revision: 1.6 $ =~ /(\d+)\.(\d+)/);
  +our $VERSION   = sprintf("%d.%02d", q$Revision: 1.7 $ =~ /(\d+)\.(\d+)/);
   our $DEBUG     = 0 unless defined $DEBUG;
   our $ERROR     = '';
   our $THROW     = 'component';
   our $EXCEPTION = 'Template::Exception';
   our $MAX_DEPTH = 64;
  +our $CACHE     = 1;
  +our $STORE     = 1;
  +our $PARAMS    = [ qw( metadata template templates ) ];
   
   
  -
  -#========================================================================
  -# constructor, initialisation, accessors and other housekeeping methods.
  -#========================================================================
  -
   #------------------------------------------------------------------------
   # init()
   #
   # Initialisation method called by base class new() constructor method.
  -# Takes some, or others of the following options, all of which is still
  -# subject to minor change.
  -#   id / name / path / time    # general purpose identifiers
  -#   scope / parent / caller    # context links
  -#   source / provider / load   # source doc ref or prov link
  -#   local                      # local data
  -#   metadata                   # locally defined metadata
  -#   options                    # cache/store/compiler/etc
  -#   depth                      # internal depth counter
  +# Accept the following named parameters:
  +#
  +#   id              # unique identifier generated by provider
  +#   path            # full path to template relative to provider root
  +#   time            # timestamp (defaults to current time)
  +#   metadata        # hash of component metadata
  +#   template        # reference to main template subroutine
  +#   templates       # hash of sub-templates (e.g. BLOCK defs)
  +#   source          # reference to Template::Source source template
  +#   cache           # can/should this component be cached in memory?
  +#   store           # can/should this component be stored persistantly?
   #------------------------------------------------------------------------
   
   sub init {
       my ($self, $config) = @_;
  -    @$self{ keys %$config } = values %$config;
  +
  +    # path is a mandatory parameter
  +    return $self->error("mandatory path parameter not defined")
  +        unless defined ($self->{ path } = $config->{ path });
  +
  +    # name defaults to last part of path
  +    ($self->{ name } = $self->{ path }) =~ s{^([^/]*/)*}{}
  +        unless defined ($self->{ name } = $config->{ name });
  +
  +    # set default time to now, and cache/store options to 
  +    # relevant package vars if not explicitly set in config
  +    $self->{ time  } = $config->{ time } || time();
  +    $self->{ cache } = $self->pkgvar( CACHE => $CACHE )
  +        unless defined ($self->{ cache } = $config->{ cache });
  +    $self->{ store } = $self->pkgvar( STORE => $STORE )
  +        unless defined ($self->{ store } = $config->{ store });
  +
  +    # copy any other parameters defined in $PARAMS from config to self
  +    my $params = $self->pkgvar( PARAMS => $PARAMS );
  +    @$self{ @$params } = @$config{ @$params };
   
  -    $self->{ depth } ||= 0;
  +    return $self;
  +}
  +
  +# TODO: components don't have/need an id, but should they, or can 
  +# they ask source for it?
   
  -    # 'visited' flag should be upgraded to array ref
  -    $self->{ visited } = [ ]
  -        if $self->{ visited } 
  -        && ! UNIVERSAL::isa($self->{ visited }, 'ARRAY');
  +#sub id   { $_[0]->{ id   } }
  +sub path { $_[0]->{ path } }
  +sub time { $_[0]->{ time } }
   
  -    # TMP HACK
  -    $self->{ name } = '[anon]' unless defined $self->{ name };
   
  -    return $self;
  +sub metadata {
  +    my $self = shift;
  +    my $meta = $self->{ metadata } ||= { };
  +    
  +    # when called with an argument, we return the metadata item
  +    # e.g. $comp->metadata('foo') ==> $comp->{ metadata }->{ foo }
  +
  +    if (@_) {
  +        my $item = shift;
  +        return $meta->{ $item };
  +    }
  +
  +    # when called without any argument, we return the complete
  +    # metatdata hash.  note that updating this hash will affect
  +    # the metadata for cached components and effects will persist
  +    # for all future invocations
  +    return $meta
   }
   
   
  -# TODO: rename this to template(), and change Template::Source to
  -# Template::Template?  (and also Template::Provider to
  -# Template::Templates?)
   
   #------------------------------------------------------------------------
   # source($source)
   #
   # Method called to register a Template::Source object with the component
  -# that identifies the source of the template document and provides a 
  +# that identifies the source of the template document.  This provides a 
   # mechanism by which we can check to see if the template source has 
  -# changed since the template component was compiled.
  +# changed since the template component was compiled (fresh()).
   #------------------------------------------------------------------------
   
   sub source {
  @@ -112,6 +145,8 @@
       # TODO: check paths, modification times, etc, to ensure we haven't
       # got a mismatch between template source and compiled template code
   
  +    # TODO: what about setting cache/store options from source?
  +
       return $self;
   }
   
  @@ -143,14 +178,19 @@
   #------------------------------------------------------------------------
   # cache($cache)
   #
  -# Component!  Cache thyself if it is written in the options that you may.
  +# Component!  Cache thyself within the cache object passed to you as the
  +# $cache argument, but only if a true value is declared in your internal
  +# 'cache' option suggesting that you may.
   #------------------------------------------------------------------------
   
   sub cache {
       my ($self, $cache) = @_;
   
  +    # return internal cache flag if $cache object not passed
  +    return $self->{ cache } unless $cache;
  +
       return $self->decline("cache option not set for $self->{ name } component")
  -        unless $self->{ options }->{ cache };
  +        unless $self->{ cache };
   
       defined $cache->set($self->{ id }, $self) 
           || return $self->error( "failed to cache $self->{ name } component: ", 
  @@ -163,16 +203,21 @@
   #------------------------------------------------------------------------
   # store($store)
   #
  -# And it shall come to pass that if the options of the component declare
  -# the will of the component creator to be such that it should be stored
  -# persistantly, then the component shall store itself.  And the word was
  -# good, and good was the word, unless of course, an error occurred.
  +# And so it came to pass that the component checked upon its own
  +# internal 'store' option and found it to have a true value, thereby
  +# indicating the will of the component creator to be that it should
  +# store itself persistantly in compiled form, using the store object
  +# passed unto it as the $store argument.  The word was good, and good
  +# was the word, unless of course, an error occurred.  
   #------------------------------------------------------------------------
   
   sub store {
       my ($self, $store) = @_;
       my ($source, $code);
   
  +    # return state of internal store flag if $store object not passed
  +    return $self->{ store } unless $store;
  +
       return $self->decline("store option not set for component: $self->{ name }")
           unless $self->{ options }->{ store };
   
  @@ -191,419 +236,37 @@
   
   
   
  -# not sure if we need all these... how about an AUTOLOAD that walks
  -# the right chains?
  -sub id   { $_[0]->{ id   } }
  -sub name { $_[0]->{ name } }
  -sub path { $_[0]->{ path } }
  -sub time { $_[0]->{ time } }
  -
  -
  -sub caller {
  -    # TODO: get current caller or nth caller
  -}
  -
  -sub callers {
  -    # TODO: return list of callers
  -}
  -
  -sub parent {
  -    # TODO: get current parent or nth parent
  -}
  -
  -sub parents {
  -    # TODO: return list of parents
  -}
  -
  -
  -
  -
  -#========================================================================
  -# runtime!
  -#========================================================================
  -
  -#------------------------------------------------------------------------
  -# run(\%options)
  -#
  -# This method runs the current component.  The component is effectively
  -# cloned by a call to the enter() method and the resulting clone component
  -# is passed as the first argument to the body subroutine, masquerading as
  -# the object itself.
  -#------------------------------------------------------------------------
  -
  -sub run {
  -    my $self = shift;
  -    my $opts = @_ && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
  -    my $copy = $self->enter($opts);
  -
  -    eval {
  -        # maybe we should call this 'run' or have a separate
  -        # body method which we call if 'body' isn't defined?
  -
  -        my $body = $self->{ body }; 
  - 
  -        # TODO: check body exists, accept other values
  -        # TODO: what about $copy->result(&$copy($copy))?
  -
  -        if (UNIVERSAL::isa($body, 'CODE')) {
  -            $opts->{ result } = &$body($copy);
  -        }
  -        else {
  -            $opts->{ result } = $self->{ body };
  -        }
  -    };
  -
  -    $copy->finish();
  -
  -#    push(@$visited, $copy) if $visited;
  -
  -    # TODO: not sure about this
  -    die $self->catch($@) if $@;
  -	
  -    return $opts->{ result };
  -}
  -
  -
  -
  -#------------------------------------------------------------------------
  -# call($name, \%options)
  -#
  -# Call another component.
  -#------------------------------------------------------------------------
  -
  -sub call {
  -    my $self = shift;
  -    my $name = shift;
  -    my $opts = @_ && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
  -
  -    local $opts->{ caller } = $self;
  -
  -    my $component = $self->template($name) || return;
  -    $component->run($opts);    # TODO: error, return value, etc.
  -
  -}
  -
  -
  -
  -# this is kinda redundant right now, but it's a hook in my head for later
  -
  -sub visited {
  -    return $_[0]->{ visited }
  -        || $_[0]->decline('not recording components visited');
  -}
  -
  -
  -
  -#========================================================================
  -# context management methods
  -#========================================================================
  -
  -#------------------------------------------------------------------------
  -# enter(\%options)
  -#
  -# Enter the current component by cloning a new component object with
  -# a 'context' reference back to $self
  -#------------------------------------------------------------------------
  -
  -sub enter {
  -    my $self = shift;
  -    my $opts = @_ && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
  -
  -    # don't let the caller chain grow too long
  -    return $self->error("maximum context depth ($MAX_DEPTH) reached")
  -        unless (($opts->{ depth } = $self->{ depth } + 1) < $MAX_DEPTH);
  -
  -    # if the current component is tracking the components that it's 
  -    # visiting, then all the components that it visits should do the same
  -    $opts->{ visited } ||= [ ] if $self->{ visited };
  -
  -    # add context link to current component
  -    $opts->{ context } = $self;
  -
  -    # set local variables to args passed
  -    $opts->{ variables } ||= $opts->{ args } || { };
  -
  -    $self->debug("enter() $self->{ name }\n") if $self->{ DEBUG };
  -
  -    # misc flags
  -    $opts->{ DEBUG } = $self->{ DEBUG } if $self->{ DEBUG };
  -    $opts->{ THROW } = $self->{ THROW } if $self->{ THROW };
  -
  -    # bless hash of options into new components
  -    bless $opts, ref $self;
  -}
  -
  -
  -#------------------------------------------------------------------------
  -# leave($component)
  -#
  -# Leave the component passed as the argument, previously created by 
  -# calling the enter() method.
  -#------------------------------------------------------------------------
  -
  -sub leave {
  -    my ($self, $component) = @_;
  -
  -    $self->debug("leave() $self->{ name }\n") if $self->{ DEBUG };
  -
  -    # if $component->{ import }
  -    #    $self->import($component->export());
  -
  -    my $visits = $self->{ visited };
  -    push(@$visits, $component) if $visits;
  -
  -    return $self;
  -}
  -
  -
  -#------------------------------------------------------------------------
  -# finish()
  -# 
  -# Notify the component that we've finished using it, allowing it to 
  -# notify its caller via the leave() method.
  -#------------------------------------------------------------------------
  -
  -sub finish {
  -    my $self   = shift;
  -    my $caller = delete($self->{ caller }) || return 1;
  -
  -    $self->debug("finish()\n") if $self->{ DEBUG };
  -
  -    # delete any other stuff we don't need to keep around
  -
  -    $caller->leave($self)
  -        || return $self->error($caller->error());
  -
  -    return $caller;
  -}
  -
  -
  -
  -
  -
  -
  -#------------------------------------------------------------------------
  -# execute(\%args, \%opts)
  -#
  -# Method to execute the current template, implemented as a wrapper around
  -# the run() method.  The execute() method accepts arguments in the style
  -# of process()
  -#------------------------------------------------------------------------
  -
  -sub execute {
  -    my $self = shift;
  -    my ($args, $opts);
  -    foreach ($args, $opts) {
  -        $_ = { }, last unless @_;
  -        $_ = { }, shift, next unless defined $_[0];
  -        $_ = shift, next if UNIVERSAL::isa($_[0], 'HASH');
  -        $_ = { @_ };
  -    }
  -    local $opts->{ args } = $args;
  -    $self->run($opts);
  -}
  -
  -
  -#------------------------------------------------------------------------
  -# process($name, \%args, \%opts)
  -#
  -# Process another template named by the first argument, using the 
  -# variable values defined by the second argument, and any other 
  -# processing options provided by the third.
  -#------------------------------------------------------------------------
   
   sub process {
  -    my ($self, $name) = (shift, shift);
  -    my ($args, $opts);
  -    foreach ($args, $opts) {
  -        $_ = { }, next unless @_;
  -        $_ = { }, shift, next unless defined $_[0];
  -        $_ = shift, next if UNIVERSAL::isa($_[0], 'HASH');
  -        $_ = { @_ };
  -    }
  -    my $component = $self->template($name) || return;
  -
  -    # TODO: may not want to mess with opts?
  -    local $opts->{ args   } = $args;
  -    local $opts->{ caller } = $self;
  -
  -    # TODO: error, return value, etc.
  -    $component->run($opts);
  -}
  -
  -
  -
  -
  -
  -#========================================================================
  -# methods for locating specific resources 
  -#========================================================================
  -
  -
  -#------------------------------------------------------------------------
  -# component($name, \%options)
  -#------------------------------------------------------------------------
  -
  -sub component {
  -    my ($self, $name) = (shift, shift);
  -
  -    $self->debug("component($name)\n") if $self->{ DEBUG };
  -
  -    # see if $name is already a component
  -#    return $name if UNIVERSAL::can($name, 'run');
  +    my ($self, $context) = @_;
  +    my $template = $self->{ template }
  +        || return $self->error('no template defined');
   
  -    return $self->{ cache }->{ components }->{ $name }
  -        ||= $self->search( components => $name, @_ );
  -}
  -
  -
  -#------------------------------------------------------------------------
  -# template($name, \%options)
  -#
  -# Fetches a template matching the name provided as a first argument.
  -# Delegates to the templates resource manager for the current context 
  -# and caches locally the template returned for subsequent use within
  -# this context.
  -#------------------------------------------------------------------------
  -
  -sub template {
  -    my $self = shift;
  -    my $name = shift;
  -    my $opts = @_ && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
  -    my ($templates, $template);
  -
  -    $self->debug("template($name)\n") if $self->{ DEBUG };
  -
  -    # see if $name is already a component
  -    return $name if UNIVERSAL::can($name, 'run');
  -
  -    # first look in the local templates cache to see if the
  -    # requested template has already been used in this context,
  -    # otherwise fetch the template from the templates resource
  -    # manager.  this isn't a real Template::Cache, just a hash
  -
  -    my $cache = $self->{ cache }->{ templates } ||= { };
  +    # barf if HOT
   
  -    # TODO: check for refs
  -    
  -    if ($template = $cache->{ $name }) {
  -        $self->debug("found $name in used templates cache\n") 
  -            if $self->{ DEBUG };
  -    }
  -    else {
  -        if ($templates = $self->resource('templates')) {
  -            if ($template = $templates->fetch($self, $name, $opts)) {
  -                $cache->{ $name } = $template;
  -                $self->debug("stored $name in used templates cache\n")
  -                    if $self->{ DEBUG };
  -            }
  -            elsif (! defined $template) {
  -                # report error
  -                return $self->error($templates->error());
  -            }
  -        }
  -        elsif (! defined $templates) {
  -            return;    # error already set by resource() method
  -        }
  -    }
  -    
  -    return $template
  -        || $self->decline("$name template not found");
  -}
  -
  -
  -sub plugin {
  -    my $self = shift;
  -    local $self->{ THROW } = 'plugin';
  -    $self->search( plugins => @_ );
  -}
  -
  -
  -#------------------------------------------------------------------------
  -# compiler($name)
  -#
  -# Search the compilers for one matching the name provided as an argument.
  -# If $name is undefined then instead search for a 'default' compiler
  -# which may return a compiler object or reference another by name 
  -# (e.g. default => 'tt3').
  -#------------------------------------------------------------------------
  +    # context vist()
   
  -sub compiler {
  -    my $self = shift;
  -    my $name = shift || 'default';
  -    
  -    # check to see if $name is already a compiler
  -    if (ref $name) {
  -        return UNIVERSAL::can($name, 'compile') 
  -            ? $name
  -            : $self->error("invalid compiler reference: $name");
  -    }
  +    $context = $context->child({
  +        templates => $self->{ templates },
  +        variables => { },
  +    });
   
  -    $self->debug("looking for $name compiler\n") if $self->{ DEBUG };
  +    $self->{ _HOT } = 1;
   
  -    return $self->{ cache }->{ compilers }->{ $name } ||= do {
  -        my $compiler = $self->search( compilers => $name )
  -            || return $self->decline("$name compiler not found");
  -
  -        # if $name is 'default' then the compiler returned may be the name 
  -        # of another compiler, e.g. { tt3 => $blah, default => 'tt3' }
  -        if (ref $compiler) {
  -            $self->debug("got $name $compiler\n") if $self->{ DEBUG };
  -        }
  -        elsif ($name eq 'default') {
  -            $name = $compiler;
  -            $self->debug("looking for default compiler: $name\n") 
  -                if $self->{ DEBUG };
  -            $compiler = $self->search( compilers => $name );
  -            return unless defined $compiler;
  -            return $self->decline("$name default compiler not found") 
  -                unless $compiler;
  -        }
  -        else {
  -            return $self->error("invalid compiler returned for $name: $compiler");
  -        }
  -        $compiler;
  +    my $result = eval {
  +        &$template($context);
       };
  -}
  -
  -
  -sub stash {
  -    my $self = shift;
  -    my $vars = $self->{ cache }->{ variables } ||= { };
  -    return Template::Stash->new( variables => $vars,
  -                                 component => $self );
  -}
  -
   
  -sub catch {
  -    my ($self, $error, $output) = @_;
  +    $self->{ _HOT } = 0;
   
  -    if (UNIVERSAL::isa($error, $EXCEPTION)) {
  -        $error->text($output) if $output;
  -        return $error;
  -    }
  -    else {
  -        # TODO: call throw() instead
  -        return $EXCEPTION->new('undef', $error, $output);
  -    }
  -}
  -
  -
  -
  -#------------------------------------------------------------------------
  -# DESTROY
  -#
  -# Call the finish() method.
  -#------------------------------------------------------------------------
  +    # context leave()
   
  -sub DESTROY {
  -    my $self = shift;
  -    $self->debug("DESTROY $self->{ name }\n") if $self->{ DEBUG };
  -    $self->finish();
  -#    $self->debug("DESTROY\n") if $self->{ DEBUG };
  +    die $context->catch($@)
  +        if $@;
  +	
  +    return $result;
   }
   
  -
   1;
   
   __END__
  @@ -688,7 +351,7 @@
   
   =head1 VERSION
   
  -$Revision: 1.6 $
  +$Revision: 1.7 $
   
   =head1 COPYRIGHT