[Templates] "very-local" includes patch

Blake D. Mills IV blakem@blakem.com
Thu, 7 Mar 2002 03:31:06 -0500


I just started playing with TT2 a few days ago, and have been very
impressed.  I'm using it to build dynamic websites and the one thing
that frustrated me was that INCLUDEs had no knowledge of a current
working directory.  Pulling in /a/path/to/my/includefile from
/a/path/to/my/template should simple, but I found myself jumping
through hoops to make sure the correct 'includefile' was getting
processed.

So, I dug around in the code came up with a solution to the "very
local" include problem, that works for me.  I added a new metadata
field to the component called 'file' which is set to the full pathname
of the template.  i.e.

     full filename of toplevel template [% template.file %]
     full filename of current component [% component.file %]

When processing an INCLUDE or PROCESS command, the Provider uses this
new information to check in the local directory before walking
the INCLUDE_PATHs.  This is the behavior most people probably expect 
with the RELATIVE setting turned on.

After applying the following patch, you can turn on the new behavior
by setting INCLUDE_PARENTS_PATH to 1 in your handler:

 Template->new({ 
                 INCLUDE_PARENTS_PATH => 1,  # try new stuff!
               });


I probably haven't implemented this in the slickest way... the
parameter gets passed around a bit to much when it should sit in an
object somewhere.  And, it only deals with real files, not globs or
pipes or any of the other data sources TT2 claims to play nicely with.
However, it does solve a problem for me, and its clean enough that I
thought I'd share.  Questions? Comments?

Again, I'm quite impressed with TT2.... looks like you've gained
another Mason convert. ;-)

-Blake

Patch against v2.06

diff -C 2 old/Context.pm new/Context.pm
*** old/Context.pm	Thu Mar  7 02:21:39 2002
--- new/Context.pm	Thu Mar  7 03:11:15 2002
***************
*** 79,82 ****
--- 79,85 ----
      my $providers;
  
+     # Keep track of the filename of the template we're being pulled into
+     my $parentfile = $self->{STASH}->get(['component',0,'file',0]);
+ 
      # references to Template::Document (or sub-class) objects objects, or
      # CODE references are assumed to be pre-compiled templates and are
***************
*** 127,131 ****
      # reference by name
      foreach my $provider (@$providers) {
! 	($template, $error) = $provider->fetch($name);
  	return $template unless $error;
  #	return $self->error($template)
--- 130,134 ----
      # reference by name
      foreach my $provider (@$providers) {
! 	($template, $error) = $provider->fetch($name,$parentfile);
  	return $template unless $error;
  #	return $self->error($template)
***************
*** 334,338 ****
      # localise the variable stash with any parameters passed
      $stash = $self->{ STASH } = $self->{ STASH }->clone($params);
- 
      eval {
  	foreach $name (@$template) {
--- 337,340 ----
diff -C 2 old/Provider.pm new/Provider.pm
*** old/Provider.pm	Thu Mar  7 02:21:49 2002
--- new/Provider.pm	Thu Mar  7 03:11:22 2002
***************
*** 87,91 ****
  
  sub fetch {
!     my ($self, $name) = @_;
      my ($data, $error);
  
--- 87,91 ----
  
  sub fetch {
!     my ($self, $name, $parentfile) = @_;
      my ($data, $error);
  
***************
*** 119,123 ****
  	# otherwise, it's a file name relative to INCLUDE_PATH
  	($data, $error) = $self->{ INCLUDE_PATH } 
! 	    ? $self->_fetch_path($name) 
  	    : (undef, Template::Constants::STATUS_DECLINED);
      }
--- 119,123 ----
  	# otherwise, it's a file name relative to INCLUDE_PATH
  	($data, $error) = $self->{ INCLUDE_PATH } 
! 	    ? $self->_fetch_path($name, $parentfile) 
  	    : (undef, Template::Constants::STATUS_DECLINED);
      }
***************
*** 323,326 ****
--- 323,327 ----
      $self->{ ABSOLUTE }     = $params->{ ABSOLUTE } || 0;
      $self->{ RELATIVE }     = $params->{ RELATIVE } || 0;
+     $self->{ INCLUDE_PARENTS_PATH } = $params->{ INCLUDE_PARENTS_PATH } || 0;
      $self->{ TOLERANT }     = $params->{ TOLERANT } || 0;
      $self->{ PARSER }       = $params->{ PARSER };
***************
*** 401,405 ****
  
  sub _fetch_path {
!     my ($self, $name) = @_;
      my ($size, $compext, $compdir) = 
  	@$self{ qw( SIZE COMPILE_EXT COMPILE_DIR ) };
--- 402,406 ----
  
  sub _fetch_path {
!     my ($self, $name, $parentfile) = @_;
      my ($size, $compext, $compdir) = 
  	@$self{ qw( SIZE COMPILE_EXT COMPILE_DIR ) };
***************
*** 424,429 ****
  	}
  
  	# search the INCLUDE_PATH for the file, in cache or on disk
! 	foreach $dir (@{ $self->{ INCLUDE_PATH } }) {
  	    next unless $dir;
  	    $path = "$dir/$name";
--- 425,438 ----
  	}
  
+         # Add parent's path to INCLUDE_PATH if INCLUDE_PARENTS_PATH is set
+         my @includepaths = @{ $self->{ INCLUDE_PATH } };
+         if ($self->{INCLUDE_PARENTS_PATH} && $parentfile) {
+             # File::Basename might be better, but this works fine for me
+             (my $parentpath = $parentfile) =~ s|/[^/]+$||;
+             unshift(@includepaths,$parentpath);
+         }
+ 
  	# search the INCLUDE_PATH for the file, in cache or on disk
! 	foreach $dir (@includepaths) {
  	    next unless $dir;
  	    $path = "$dir/$name";
***************
*** 572,575 ****
--- 581,585 ----
  		time => (stat $name)[9],
  		load => $now,
+                 file => $name,
  	    };
  	}
***************
*** 756,759 ****
--- 766,770 ----
  	    'name'    => $data->{ name },
  	    'modtime' => $data->{ time },
+             'file'    => $data->{ file },
  	    %{ $parsedoc->{ METADATA } },
  	};