The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Module::Provision::TraitFor::Rendering;

use namespace::sweep;

use Class::Usul::Constants;
use Class::Usul::Functions  qw( app_prefix is_arrayref distname io throw );
use File::DataClass::Types  qw( ArrayRef Bool Directory Path SimpleStr );
use File::ShareDir            ( );
use Scalar::Util            qw( blessed weaken );
use Template;
use Unexpected::Functions   qw( Unspecified );
use Moo::Role;
use Class::Usul::Options;

requires qw( add_leader appldir builder config dist_module exec_perms file
             incdir initial_wd loc log perms stash testdir vcs yorn );

# Object attributes (public)
option 'force'        => is => 'ro',   isa => Bool, default => FALSE,
   documentation      => 'Overwrite files if they already exist',
   short              => 'f';

option 'templates'    => is => 'ro',   isa => SimpleStr, default => NUL,
   documentation      => 'Non default location of the code templates';

has 'template_dir'    => is => 'lazy', isa => Directory,
   coerce             => Directory->coercion, init_arg => undef;

has 'template_list'   => is => 'lazy', isa => ArrayRef, init_arg => undef;

# Object attributes (private)
has '_template_index' => is => 'lazy', isa => Path, coerce => Path->coercion,
   init_arg           => undef;

# Public methods
sub dump_stash : method {
   my $self = shift; $self->dumper( $self->stash ); return OK;
}

sub expand_tuple {
   my ($self, $tuple) = @_;

   for (my $i = 0, my $max = @{ $tuple }; $i < $max; $i++) {
      if (is_arrayref $tuple->[ $i ]) {
         $tuple->[ $i ]->[ 0 ] = $self->_deref_tmpl( $tuple->[ $i ]->[ 0 ] );
         $tuple->[ $i ] = io( $tuple->[ $i ] );
      }
      else {
         $tuple->[ $i ] = $self->_deref_tmpl( $tuple->[ $i ] );
      }
   }

   return $tuple;
}

sub init_templates : method {
   my $self = shift; $self->template_list; return OK;
}

sub render_template {
   my ($self, $template, $target) = @_;

   $template or throw class => Unspecified, args => [ 'Template' ];
   $target   or throw class => Unspecified, args => [ 'Template target' ];

   $target->exists and $target->is_dir
      and $target = $target->catfile( $template );
   $template = $self->template_dir->catfile( $template );

   $template->exists or
      return $self->log->warn( $self->loc( 'Path [_1] not found', $template ) );

   my $file  = $target->filename; my $prompt;

   $target->exists and not $self->force
      and $prompt = $self->loc( 'File [_1] exists, overwrite?', $file )
      and not $self->yorn( $self->add_leader( $prompt ), FALSE, TRUE )
      and return $target;

   my $tmplt = Template->new( $self->_template_args ) or throw $Template::ERROR;
   my $text  = NUL;

   $tmplt->process( $template->pathname, $self->stash, \$text )
      or throw $tmplt->error();
   $target->perms( $self->perms )->print( $text ); $target->close;
   return $target;
}

sub render_templates {
   my $self = shift; $self->output( 'Rendering templates' );

   for my $tuple (map { $self->expand_tuple( $_ ) } @{ $self->template_list }) {
      $self->render_template( @{ $tuple } );
   }

   return;
}

# Private methods
sub _build_template_dir {
   my $self  = shift;
   my $class = blessed $self;
   my $tgt   = $self->templates
             ? io( [ $self->templates ] )->absolute( $self->initial_wd )
             : io( [ $self->config->my_home, '.'.(app_prefix $class) ] );

   $tgt->exists and return $tgt; $tgt->mkpath( $self->exec_perms );

   my $dist  = io( File::ShareDir::dist_dir( distname $class ) );

   $_->copy( $tgt ) for ($dist->all_files);

   return $tgt;
}

sub _build__template_index {
   return $_[ 0 ]->template_dir->catfile( $_[ 0 ]->config->template_index );
}

sub _build_template_list {
   my $self = shift; my $index = $self->_template_index;

   my $data; $index->exists and $data = $self->file->data_load
      ( paths => [ $index ], storage_class => 'Any' )
      and return $self->_merge_lists( $data );
   my $builders  = {
      DZ => [ [ 'dist.ini',           '_appldir' ],
              [ 'DZ_Build.PL',      [ '_appldir', 'Build.PL' ], ], ],
      MB => [ [ 'Build.PL',           '_appldir' ], ],
      MI => [ [ 'MI_Makefile.PL',   [ '_appldir', 'Makefile.PL' ], ], ], };
   my $templates = [ [ 'Changes',         '_appldir'     ],
                     [ 'MANIFEST.SKIP',   '_appldir'     ],
                     [ 'perl_module.pm',  '_dist_module' ],
                     [ '01always_pass.t', '_testdir'     ],
                     [ '02pod.t',         '_testdir'     ],
                     [ '03podcoverage.t', '_testdir'     ],
                     [ '04critic.t',      '_testdir'     ],
                     [ '05kwalitee.t',    '_testdir'     ],
                     [ '06yaml.t',        '_testdir'     ],
                     [ '07podspelling.t', '_testdir'     ],
                     [ '10test_script.t', '_testdir'     ], ];
   my $vcs = {
      git => [ [ 'gitcommit-msg', [ '_appldir', '.gitcommit-msg' ] ],
               [ 'gitignore',     [ '_appldir', '.gitignore'     ] ],
               [ 'gitpre-commit', [ '_appldir', '.gitpre-commit' ] ], ],
      svn => [], };

   $self->output( 'Creating index [_1]', { args => [ $index ] } );
   $data = { builders => $builders, templates => $templates, vcs => $vcs };
   $self->file->data_dump
      ( data => $data, path => $index, storage_class => 'Any' );
   return $self->_merge_lists( $data );
}

sub _deref_tmpl {
   my ($self, $car) = @_; '_' ne substr $car, 0, 1 and return $car;

   my $reader = substr $car, 1; return $self->$reader();
}

sub _merge_lists {
   my ($self, $args) = @_; my $list = $args->{templates};

   push @{ $list }, @{ $args->{builders}->{ $self->builder } };
   $self->vcs ne 'none' and push @{ $list }, @{ $args->{vcs}->{ $self->vcs } };

   return $list;
}

sub _template_args {
   my $self = shift; weaken( $self ); my $args = { ABSOLUTE => TRUE, };

   $args->{VARIABLES}->{loc} = sub { $self->loc( @_ ) };

   return $args;
}

1;

__END__

=pod

=encoding utf8

=head1 Name

Module::Provision::TraitFor::Rendering - Renders Templates

=head1 Synopsis

   use Moose;

   extends 'Module::Provision::Base';
   with    'Module::Provision::TraitFor::Rendering';

=head1 Description

Renders templates. Uses a list stored in the index file F<index.json> which
by default is in the F<~/.module_provision> directory

=head1 Configuration and Environment

Requires the consuming class to define the attributes; C<appldir>,
C<builder>, C<dist_module>, C<incdir>, C<initial_wd>, C<stash>,
C<testdir>, and C<vcs>

Defines the following attributes;

=over 3

=item C<force>

Overwrite the output files if they already exist

=item C<templates>

Location of the code templates in the users home directory. Defaults to
F<.module_provision>

=item C<template_dir>

Directory where the templates live

=item C<template_list>

Data structure that maps the files in the template directory to the files
in the project directory

=back

=head1 Subroutines/Methods

=head2 dump_stash - Dump the hash ref used to render a template

   $exit_code = $self->dump_stash;

Uses the internal dumper method to produce a pretty coloured listing

=head2 expand_tuple

   $tuple = $self->expand_tuple( $tuple );

Expands the references in the passed tuple

=head2 init_templates - Initialize the template directory

   $exit_code = $self->init_templates;

Initialise the F<.module_provision> directory and create the F<index.json> file

=head2 render_template

   $target = $self->render_template( $template, $target );

Renders a single template using L<Template>

=head2 render_templates

   $self->render_templates;

Renders the list of templates in C<< $self->template_list >> be
repeatedly calling calling L</render_template>

=head1 Diagnostics

None

=head1 Dependencies

=over 3

=item L<Class::Usul>

=item L<File::DataClass>

=item L<File::ShareDir>

=item L<Moose::Role>

=item L<Template>

=back

=head1 Incompatibilities

There are no known incompatibilities in this module

=head1 Bugs and Limitations

There are no known bugs in this module.
Please report problems to the address below.
Patches are welcome

=head1 Acknowledgements

Larry Wall - For the Perl programming language

=head1 Author

Peter Flanigan, C<< <pjfl@cpan.org> >>

=head1 License and Copyright

Copyright (c) 2013 Peter Flanigan. All rights reserved

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself. See L<perlartistic>

This program is distributed in the hope that it will be useful,
but WITHOUT WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE

=cut

# Local Variables:
# mode: perl
# tab-width: 3
# End: