The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Toader::Templates;

use warnings;
use strict;
use Toader::isaToaderDir;
use Cwd 'abs_path';
use base 'Error::Helper';
use Text::Template;
use Toader::Templates::Defaults;

=head1 NAME

Toader::Templates - This handles fetching Toader templates.

=head1 VERSION

Version 1.0.0

=cut

our $VERSION = '1.0.0';

=head1 SYNOPSIS

For information on the storage and rendering of entries,
please see 'Documentation/Templates.pod'.

=head1 METHODS

=head2 new

=head3 args hash ref

=head4 dir

This is the directory to intiate in.

=head4 toader

This is a L<Toader> object.

    my $foo = Toader::Templates->new( \%args );
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub new{
	my %args;
	if(defined($_[1])){
		%args= %{$_[1]};
	};

	my $self={
			  error=>undef,
			  errorString=>'',
			  perror=>undef,
			  isatd=>Toader::isaToaderDir->new(),
			  dir=>undef,
			  defaults=>Toader::Templates::Defaults->new,
			  errorExtra=>{
				  flags=>{
					  1=>'noDirSpecified',
					  2=>'isaToaderDirErrored',
					  3=>'notAtoaderDir',
					  4=>'invalidTemplateName',
					  5=>'openTemplateFailed',
					  6=>'noDefaultTemplate',
					  7=>'templateFillErrored',
					  8=>'templateStringUndef',
					  9=>'templateNameUndef',
					  10=>'notAtoaderObj',
					  11=>'getVCSerrored',
					  12=>'VCSusableErrored',
					  13=>'noTemplateSpecified',
					  14=>'underVCSerrored',
					  15=>'VCSaddErrored',
					  16=>'VCSdeleteErrored',
					  17=>'unlinkFailed',
					  18=>'notInDir',
					  19=>'noToaderObj',
				  },
			  },
			  VCSusable=>0,
			  };
	bless $self;

	if ( defined( $args{dir} ) ){
		if ( ! $self->{isatd}->isaToaderDir( $args{dir} ) ){
			$self->{perror}=1;
			$self->{error}=1,
			$self->{errorString}='The specified directory is not a Toader directory';
			$self->warn;
			return $self;
		}
		$self->{dir}=$args{dir};
	}

	#if we have a Toader object, reel it in
	if ( ! defined( $args{toader} ) ){
		$self->{error}=19;
		$self->{perror}=1;
		$self->{errorString}='No $args{toader} specified';
		$self->warn;
		return $self;
	}
	if ( ref( $args{toader} ) ne "Toader" ){
		$self->{perror}=1;
		$self->{error}=10;
		$self->{errorString}='The object specified is a "'.ref($args{toader}).'"';
		$self->warn;
		return $self;
	}
	$self->{toader}=$args{toader};

	#gets the Toader::VCS object
	$self->{vcs}=$self->{toader}->getVCS;
	if ( $self->{toader}->error ){
		$self->{perror}=1;
		$self->{error}=11;
		$self->{errorString}='Toader->getVCS errored. error="'.
			$self->{toader}->error.'" errorString="'.$self->{toader}->errorString.'"';
		$self->warn;
		return $self;
	}
	
	#checks if VCS is usable
	$self->{VCSusable}=$self->{vcs}->usable;
	if ( $self->{vcs}->error ){
		$self->{perror}=1;
		$self->{error}=12;
		$self->{errorString}='Toader::VCS->usable errored. error="'.
			$self->{toader}->error.'" errorString="'.$self->{toader}->errorString.'"';
		$self->warn;
		return $self;
	}

	return $self;
}

=head2 dirGet

This gets L<Toader> directory this entry is associated with.

This will only error if a permanent error is set.

    my $dir=$foo->dirGet;
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub dirGet{
	my $self=$_[0];

	if (!$self->errorblank){
		return undef;
	}

	return $self->{dir};
}

=head2 dirSet

This sets L<Toader> directory this entry is associated with.

One argument is taken and it is the L<Toader> directory to set it to.

    $foo->dirSet($toaderDirectory);
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub dirSet{
	my $self=$_[0];
	my $dir=$_[1];

	if (!$self->errorblank){
		return undef;
	}

	#checks if the directory is Toader directory or not
    my $returned=$self->{isatd}->isaToaderDir($dir);
	if (! $returned ) {
		$self->{error}=1;
		$self->{errorString}='"'.$dir.'" is not a Toader directory';
		$self->warn;
		return undef;
	}

	$self->{dir}=$dir;

	return 1;
}

=head2 fill_in

This fills in a template that has been passed to it.

Two arguments are taken. The first is the template name.
The second is a hash reference. 

The returned string is the filled out template.

    my $rendered=$foo->fill_in( $templateName, \%hash );
    if ( $foo->error ){
        warn( 'Error:'.$foo->error.': '.$foo->errorString );
    }

=cut

sub fill_in{
	my $self=$_[0];
	my $name=$_[1];
	my $hash=$_[2];

	if( ! $self->errorblank ){
		return undef;
	}

	#make sure a template name is specified
	if ( ! defined( $name ) ){
		$self->{error}=9;
		$self->{errorString}='No template name specified';
		$self->warn;
		return undef;
	}

	#gets the template
	my $template=$self->getTemplate( $name );
	if ( $self->error ){
		return undef;
	}

	return $self->fill_in_string( $template, $hash );
}

=head2 fill_in_string

This fills in a template that has been passed to it.

Two arguments are required and the first is the template string to use and
second it is the hash to pass to it.

The returned string is the filled out template.

    my $rendered=$foo->fill_in_string( $templateString, \%hash );
    if ( $foo->error ){
        warn( 'Error:'.$foo->error.': '.$foo->errorString );
    }

=cut

sub fill_in_string{
	my $self=$_[0];
	my $string=$_[1];
	my $hash=$_[2];

	if( ! $self->errorblank ){
		return undef;
	}

	if ( ! defined( $string ) ){
		$self->{error}=8;
		$self->{errorString}='No template string specified';
		$self->warn;
		return undef;
	}

	my $template = Text::Template->new(
		TYPE => 'STRING',
		SOURCE => $string,
		DELIMITERS=>[ '[==', '==]' ],
		);

	my $rendered=$template->fill_in(
		HASH=>$hash,
		);

	if ( ! defined ( $rendered ) ){
		$self->{error}=7;
		$self->{errorString}='Error encountered filling in the template';
		$self->warn;
		return undef;
	}

	return $rendered;
}

=head2 findTemplate

This finds a specified template.

One arguement is taken and it is the name of the template.

A return of undef can mean either a error or it was not found.
If there was an error, the method error will return true.

    my $templateFile=$foo->findTemplate($templateName);
    if( !defined( $templateFile ) ){
        if($foo->error){
            warn('Error:'.$foo->error.': '.$foo->errorString);
        }else{
            print("Not found\n");
        }
    }else{
        print $templateFile."\n";
    }

=cut

sub findTemplate{
	my $self=$_[0];
	my $name=$_[1];

	if (!$self->errorblank){
		return undef;
	}

	#make sure a directory has been set
	if (!defined( $self->{dir} )) {
		$self->{error}=2;
		$self->{errorString}='No directory has been set yet';
		$self->warn;
		return undef;		
	}

	#checks if the name is valid
	my $returned=$self->templateNameCheck($name);
	if (! $returned ) {
		$self->{error}=4;
		$self->{errorString}='"'.$name.'" is not a valid template name';
		$self->warn;
		return undef;
	}

	#checks if the directory is Toader directory or not
    $returned=$self->{isatd}->isaToaderDir( $self->{dir} );
	if (! $returned ) {
		$self->{error}=3;
		$self->{errorString}='"'.$self->{dir}.'" is no longer a Toader directory';
		$self->warn;
		return undef;
	}

	#initial stuff to check
	my $dir=$self->{dir};
	my $template=$dir.'/.toader/templates/'.$name;

	#checks if the template exists
	if (-f $template) {
		return $template;
	}

	#recurse down trying to find the last one
	$dir=abs_path($dir.'/..');
	#we will always find something below so it is just set to 1
	while (1) {
		#we hit the FS root...
		#if he hit this, something is definitely wrong
		if ($dir eq '/') {
			return undef;
		}

		#make sure
		$returned=$self->{isatd}->isaToaderDir($dir);
		if (!$returned) {
			return undef;
		}

		#check if it exists
		$template=$dir.'/.toader/templates/'.$name;
		if (-f $template) {
			return $template;
		}

		#check the next directory
		$dir=abs_path($dir.'/..');
	}
}

=head2 getTemplate

This finds a template and then returns it.

The method findTemplate will be used and if that fails the default
template will be returned.

One arguement is required and it is the template name.

    my $template=$foo->getTemplate($templateName);
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub getTemplate{
	my $self=$_[0];
	my $name=$_[1];

	if (!$self->errorblank){
		return undef;
	}

	#make sure a template name is specified
    if ( ! defined( $name ) ){
        $self->{error}=9;
        $self->{errorString}='No template name specified';
        $self->warn;
        return undef;
    }

	#try to find it as a file
	#also allow this to do the error checking as it is the same
	my $file=$self->findTemplate($name);
	if ($self->error) {
		$self->warnString('findTemplate errored');
		return undef;
	}

	#the contents of the template to be returned
	my $template;

	#if we found a file, read it and return it
	if (defined($file)) {
		my $fh;
		if ( ! open( $fh, '<', $file ) ) {
			$self->{error}=5;
			$self->{errorString}="Unable to open '".$file."'";
			$self->warn;
			return undef;
		}
		$template=join('',<$fh>);
		close $fh;
		return $template;
	}

	#tries to fetch the default template
	$template=$self->{defaults}->getTemplate($name);
	if ( ! defined( $template ) ) {
			$self->{error}=6;
			$self->{errorString}='No default template';
			$self->warn;
			return undef;
	}

	return $template;
}

=head2 getTemplateDefault

This finds a default template and then returns it.

One arguement is required and it is the template name.

    my $template=$foo->getTemplate($templateName);
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub getTemplateDefault{
    my $self=$_[0];
    my $name=$_[1];

    if (!$self->errorblank){
        return undef;
    }

    #make sure a template name is specified
    if ( ! defined( $name ) ){
        $self->{error}=9;
        $self->{errorString}='No template name specified';
        $self->warn;
        return undef;
	}

    #tries to fetch the default template
    my $template=$self->{defaults}->getTemplate($name);
    if ( ! defined( $template ) ) {
		$self->{error}=6;
		$self->{errorString}='No default template';
		$self->warn;
		return undef;
    }
	
    return $template;
}

=head2 listTemplates

This lists the various templates in the directory.

    my @templates=$foo->listTemplates;
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub listTemplates{
	my $self=$_[0];

	if (!$self->errorblank){
		return undef;
	}

	#make sure a directory has been set
	if (!defined( $self->{dir} )) {
		$self->{error}=2;
		$self->{errorString}='No directory has been set yet';
		$self->warn;
		return undef;
	}

	#checks if the directory is Toader directory or not
    my $returned=$self->{isatd}->isaToaderDir( $self->{dir} );
	if (! $returned ) {
		$self->{error}=3;
		$self->{errorString}='"'.$self->{dir}.'" is no longer a Toader directory';
		$self->warn;
		return undef;
	}

	#the directory to list
	my $dir=$self->{dir}.'/.toader/templates/';

	#makes sure the template exists and if it does not, return only having the default
	if (! -d $dir) {
		return;
	}

	#lists each theme
	my $dh;
	if (opendir($dh, $dir )) {
		$self->{error}='4';
		$self->{errorString}='Failed to open the directory "'.$dir.'"';
		$self->warn;
		return undef;
	}
	my @templates=grep( { -f $dir.'/'.$_ } readdir($dh) );
	close($dh);
	
	return \@templates;
}

=head2 listDefaultTemplates

This lists the various templates in the directory.

    my @templates=$foo->listTemplates;
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub listDefaultTemplates{
    my $self=$_[0];

    if (!$self->errorblank){
        return undef;
    }

	return $self->{defaults}->listTemplates;
}

=head2 remove

This removes a template from the current directory.

One argument is required and that is the name of the template.

    $foo->remove( $name );
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub remove{
	my $self=$_[0];
	my $name=$_[1];

	if (!$self->errorblank){
		return undef;
	}

	#make sure a directory has been set
	if (!defined( $self->{dir} )) {
		$self->{error}=2;
		$self->{errorString}='No directory has been set yet';
		$self->warn;
		return undef;
	}

	#make sure a template name is specified
    if ( ! defined( $name ) ){
        $self->{error}=9;
        $self->{errorString}='No template name specified';
        $self->warn;
        return undef;
    }

	#checks if the name is valid
	my $returned=$self->templateNameCheck($name);
	if (! $returned ) {
		$self->{error}=4;
		$self->{errorString}='"'.$name.'" is not a valid template name';
		$self->warn;
		return undef;
	}

	#make sure the template is in this directory
	if ( ! $self->templateInDir( $name ) ){
		$self->{error}=18;
		$self->{errorString}='Template "'.$name.'" is not in "'.
			$self->{dir}.'"';
		$self->warn;
		return undef;
	}

	#the file in question
	my $file=$self->{dir}.'/.toader/templates/'.$name;

	#try to unlink the file
	if ( ! unlink( $file ) ) {
		$self->{error}=17;
		$self->{errorString}='Unlink of "'.$file.'" failed';
		$self->warn;		
		return undef;
	}

	#if VCS is not usable, return here
	if ( ! $self->{VCSusable} ){
		return 1;
	}
	
	#if it is not under VCS, we have nothing to do
	my $underVCS=$self->{vcs}->underVCS($file);
	if ( $self->{vcs}->error ){
		$self->{error}=14;
		$self->{errorString}='Toader::VCS->underVCS errored. error="'.
			$self->{vcs}->error.'" errorString="'.$self->{vcs}->errorString.'"';
		$self->warn;
		return undef;
	}
	if ( $underVCS ){
		return 1;
	}

	#delete it as if we reach here it is not under VCS and VCS is being used
	$self->{vcs}->delete( $file );
	if ( $self->{vcs}->error ){
		$self->{error}=16;
		$self->{errorString}='Toader::VCS->delete errored. error="'.
			$self->{vcs}->error.'" errorString="'.$self->{vcs}->errorString.'"';
		$self->warn;
		return undef;
	}


	return 1;
}

=head2 set

This sets a template in the current directory.

Two arguments are required and those in order are the name
template and the template.

    $foo->set( $name, $template );
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub set{
	my $self=$_[0];
	my $name=$_[1];
	my $template=$_[2];

	if (!$self->errorblank){
		return undef;
	}

	#make sure a directory has been set
	if (!defined( $self->{dir} )) {
		$self->{error}=2;
		$self->{errorString}='No directory has been set yet';
		$self->warn;
		return undef;
	}

	#make sure a template name is specified
    if ( ! defined( $name ) ){
        $self->{error}=9;
        $self->{errorString}='No template name specified';
        $self->warn;
        return undef;
    }

	#checks if the name is valid
	my $returned=$self->templateNameCheck($name);
	if (! $returned ) {
		$self->{error}=4;
		$self->{errorString}='"'.$name.'" is not a valid template name';
		$self->warn;
		return undef;
	}

	#make sure a template name is specified
    if ( ! defined( $template ) ){
        $self->{error}=13;
        $self->{errorString}='No template specified';
        $self->warn;
        return undef;
    }	

	#the file in question
	my $file=$self->{dir}.'/.toader/templates/'.$name;

	#write the template out
	my $fh;
	if ( ! open( $fh, '>', $file ) ){
		$self->{error}=5;
		$self->{errorString}='Unable to open "'.$file.'" for writing';
		$self->warn;
		return undef;
	}
	print $fh $template;
	close $fh;

	#if VCS is not usable, return here
	if ( ! $self->{VCSusable} ){
		return 1;
	}
	
	#if it is not under VCS, we have nothing to do
	my $underVCS=$self->{vcs}->underVCS($file);
	if ( $self->{vcs}->error ){
		$self->{error}=14;
		$self->{errorString}='Toader::VCS->underVCS errored. error="'.
			$self->{vcs}->error.'" errorString="'.$self->{vcs}->errorString.'"';
		$self->warn;
		return undef;
	}
	if ( $underVCS ){
		return 1;
	}

	#add it as if we reach here it is not under VCS and VCS is being used
	$self->{vcs}->add( $file );
	if ( $self->{vcs}->error ){
		$self->{error}=15;
		$self->{errorString}='Toader::VCS->add errored. error="'.
			$self->{vcs}->error.'" errorString="'.$self->{vcs}->errorString.'"';
		$self->warn;
		return undef;
	}

	return 1;
}

=head2 templateInDir

This checks if the template is in the current directory.

One argument is required and that is the name of the template.

    $foo->templateInDir( $name );
    if($foo->error){
        warn('Error:'.$foo->error.': '.$foo->errorString);
    }

=cut

sub templateInDir{
	my $self=$_[0];
	my $name=$_[1];

	if (!$self->errorblank){
		return undef;
	}

	#make sure a directory has been set
	if (!defined( $self->{dir} )) {
		$self->{error}=2;
		$self->{errorString}='No directory has been set yet';
		$self->warn;
		return undef;
	}

	#make sure a template name is specified
    if ( ! defined( $name ) ){
        $self->{error}=9;
        $self->{errorString}='No template name specified';
        $self->warn;
        return undef;
    }

	#checks if the name is valid
	my $returned=$self->templateNameCheck($name);
	if (! $returned ) {
		$self->{error}=4;
		$self->{errorString}='"'.$name.'" is not a valid template name';
		$self->warn;
		return undef;
	}

	#the file in question
	my $file=$self->{dir}.'/.toader/templates/'.$name;

	#checks if it exists
	if ( -f $file ){
		return 1;
	}
	
	return 0;
}

=head2 templateNameCheck

This makes sure checks to make sure a template name is valid.

    my $returned=$foo->templateNameCheck($name);
    if ($returned){
        print "Valid\n";
    }

=cut

sub templateNameCheck{
	my $self=$_[0];
	my $name=$_[1];

	if (!$self->errorblank){
		return undef;
	}

	if (!defined($name)) {
		return 0;
	}
	if ($name =~ /^ /) {
		return 0;
	}
	if ($name =~ /\t/) {
		return 0;
	}
	if ($name =~ /\n/) {
		return 0;
	}
	if ($name =~ / $/) {
		return 0;
	}

	return 1;
}

=head1 ERROR CODES

=head2 1, noDirSpecified

The specified directory is not a L<Toader> directory.

=head2 2, isaToaderDirErrored

No directory has been specified yet.

=head2 3, notAtoaderDir

The directory in question is no longer a toader directory.

=head2 4, invalidTemplateName

Not a valid template name.

=head2 5, openTemplateFailed

Unable to open the template file.

=head2 6, noDefaultTemplate

Unable to fetch the default template. It does not exist.

=head2 7, templateFillErrored

Errored filling out the template string.

=head2 8, templateStringUndef

Nothing specified for the template string.

=head2 9, templateNameUndef

Template name is not defined.

=head2 10, notAtoaderObj

The object in question is not a Toader object.

=head2 11, getVCSerrored

L<Toader>->getVCS errored.

=head2 12, VCSusableErrored

L<Toader::VCS>->usable errored.

=head2 13, noTemplateSpecified

Nothing specified for the data for a template.

=head2 14, underVCSerrored

L<Toader::VCS>->underVCS errored.

=head2 15, VCSaddErrored

L<Toader::VCS>->add errored.

=head2 16, VCSdeleteErrored

L<Toader::VCS>->delete errored.

=head2 17, unlinkFailed

Failed to unlink the template.

=head2 18, notInDir

The requested template is not in this Toader dir.

=head2 19, noToaderObj

No L<Toader> object is given.

=head1 AUTHOR

Zane C. Bowers-Hadley, C<< <vvelox at vvelox.net> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-toader at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Toader>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.




=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Toader::Templates


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Toader>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Toader>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Toader>

=item * Search CPAN

L<http://search.cpan.org/dist/Toader/>

=back


=head1 ACKNOWLEDGEMENTS


=head1 LICENSE AND COPYRIGHT

Copyright 2013 Zane C. Bowers-Hadley.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.


=cut

1; # End of Toader::Templates