# ==================================================================
# Gossamer Threads Module Library - http://gossamer-threads.com/
#
#   GT::Template::Inheritance
#   Author: Scott Beck
#   CVS Info :                          
#   $Id: Inheritance.pm,v 1.7 2005/02/09 20:51:27 jagerman Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
# ==================================================================
#
# Description: Provides class methods to deal with template
#              inheritance.
#

package GT::Template::Inheritance;
# ==================================================================

use strict;
use vars qw($ERRORS);
use bases 'GT::Base' => '';
use GT::Template;

$ERRORS = { RECURSION => q _Recursive inheritance detected and interrupted: '%s'_ };

sub get_all_paths {
# ----------------------------------------------------------------------------
    my ($class, %opts) = @_;

    my $file = delete $opts{file};
    my $single = delete $opts{_single};
    $class->fatal(BADARGS => "No file specified to $class->" . ($single ? 'get_path' : 'get_all_paths')) unless defined $file;

    my $root = delete $opts{path};
    $class->fatal(BADARGS => "No path specified to $class->" . ($single ? 'get_path' : 'get_all_paths')) unless defined $root;
    $class->fatal(BADARGS => "Path $root does not exist or is not a directory") unless -d $root;

    my $local = exists $opts{local} ? delete $opts{local} : 1;
    my $inheritance = exists $opts{inheritance} ? delete $opts{inheritance} : 1;

    # Old no-longer-supported option:
    delete @opts{qw/use_inheritance use_local local_inheritance/};

    $class->fatal(BADARGS => "Unknown arguments: " . join ", ", keys %opts) if keys %opts;

    my @paths = $class->tree(path => $root, local => $local, inheritance => $inheritance);
    my @files;
    for (@paths) {
        if (-f "$_/$file" and -r _) {
            return "$_/$file" if $single;
            push @files, "$_/$file";
        }
    }
    return if $single;
    return @files;
}

sub get_path {
# ----------------------------------------------------------------------------
    shift->get_all_paths(@_, _single => 1);
}

sub tree {
# -----------------------------------------------------------------------------
    my $class = shift;
    my %opts = @_ > 1 ? @_ : (path => shift);

    my $root = delete $opts{path};
    $class->fatal(BADARGS => "No path specified for $class->tree") unless defined $root;
    $class->fatal(BADARGS => "Path '$root' does not exist or is not a directory") unless -d $root;

    my $local = exists $opts{local} ? delete $opts{local} : 1;
    my $inheritance = exists $opts{inheritance} ? delete $opts{inheritance} : 1;

    $class->fatal(BADARGS => "Unknown arguments: " . join ", ", keys %opts) if keys %opts;

    my @paths;
    push @paths, $root;
    my %encountered = ($root => 1);
    if ($inheritance) {
        for my $path (@paths) {
            my $tplinfo = GT::Template->load_tplinfo($path);
            next if not defined $tplinfo->{inheritance};
            my @inherit = ref $tplinfo->{inheritance} eq 'ARRAY' ? @{$tplinfo->{inheritance}} : $tplinfo->{inheritance};

            for (@inherit) {
                my $inh = m!^(?:[a-zA-Z]:)?[\\/]! ? $_ : "$path/$_";
                if (length $inh > 500 or $encountered{$inh}++) {
                    return $class->fatal(RECURSION => $inh);
                }

                push @paths, $inh;
            }
        }
    }

    if ($local) {
        for (my $i = 0; $i < @paths; $i++) {
            if (-d "$paths[$i]/local") {
                splice @paths, $i, 0, "$paths[$i]/local";
                $i++;
            }
        }
    }

    return @paths;
}

1;

__END__

=head1 NAME

GT::Template::Inheritance - Provides GT::Template inheritance/local file
determination.

=head1 SYNOPSIS

    use GT::Template::Inheritance;

    my $file = GT::Template::Inheritance->get_path(
        file => "foo.htm",
        path => "/path/to/my/template/set"
    );

    my @files = GT::Template::Inheritance->get_all_paths(
        file => "foo.htm",
        path => "/path/to/my/template/set"
    );

    my @paths = GT::Template::Inheritance->tree(
        path => "/path/to/my/template/set"
    );

=head1 DESCRIPTION

GT::Template::Inheritance provides an interface to accessing files for
GT::Template template parsing and include handling.  It supports following
inheritance directories and respects "local" template directories.


=head2 Inheritance

GT::Template inheritance works by looking for a .tplinfo file in the template
directory (or local/.tplinfo, if it exists).  In order for the template
directory to inherit from another template directory, this file must exist and
must evaluate to a hash reference containing an C<inheritance> key.  The
following is a possible .tplinfo file contents:

    {
        inheritance => '../other'
    }

The above example would indicate that files in this template set can be
inherited from the ../other path, relative to the current template set
directory.  The inheritance directory may also contain a full path.

=head2 Inheriting from multiple locations

You may also inherit from multiple locations by using an array reference for
the inheritance value:

    {
        inheritance => ['../other', '/full/path/to/a/third']
    }

With the above .tplinfo file, files would be checked for in the current path,
then C<../other>, then any of C<../other>'s inherited directories, then in
C<third>, then in any of C<third>'s inherited directories.

Also keep in mind that "local" directories, if they exist, will be checked for
the file before each of their respective directories.

Assuming that the initial template path was C</full/path/one>, and assuming
that C<../other> inherited from C<../other2>, the directories checked would be
as follows:

    /full/path/one/local
    /full/path/one
    /full/path/one/../other/local            # i.e. /full/path/other/local
    /full/path/one/../other                  # i.e. /full/path/other
    /full/path/one/../other/../other2/local  # i.e. /full/path/other2/local
    /full/path/one/../other/../other2        # i.e. /full/path/other2
    /full/path/to/a/third/local
    /full/path/to/a/third

=head1 METHODS

All methods in GT::Template::Inheritance are class methods.  Each method takes
a hash of options as an argument.

=head2 get_path

=head2 get_all_paths

These methods are used to obtain the location of the file GT::Template will
use, taking into account all inherited and "local" template directories.  The
get_path option will return the path to the file that will be included, while
the get_all_paths option returns the path to B<all> copies of the file found in
the local/inheritance tree.  Both methods take a hash containing the following:

=over 4

=item file

The name of the file desired.

=item path

The template directory at which to start looking for the above file.  Depending
on the existance of "local" directories and template inheritance, more than
just this directory will be checked for the file.

=item local

Optional.  Can be passed with a false value to override the checking of "local"
directories for files.

=item inheritance

Optional.  Can be passed with a false value to override the checking of
inheritance directories for files.

=back

=head2 tree

This method returns a list of directories that would be searched for a given
file, in the order they would be searched.  It takes the C<path>, C<local>, and
C<inheritance> options above, but not the C<file> option.

=head1 SEE ALSO

L<GT::Template>

=head1 MAINTAINER

Jason Rhinelander

=head1 COPYRIGHT

Copyright (c) 2005 Gossamer Threads Inc.  All Rights Reserved.
http://www.gossamer-threads.com/

=head1 VERSION

Revision: $Id: Inheritance.pm,v 1.7 2005/02/09 20:51:27 jagerman Exp $

=cut
