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

=head1 NAME

MooseX::Templated::Role - Role to render a Moose class with template

=head1 SYNOPSIS

Farm/Cow.pm

    package Farm::Cow;
    use Moose;
    with 'MooseX::Templated::Role';

    has 'spots'   => ( is => 'rw' );
    has 'hobbies' => ( is => 'rw', default => sub { ['mooing', 'chewing'] } );

    sub moo { "Moooooooo!" }

Farm/Cow.tt

    This cow has [% self.spots %] spots - it likes
    [% self.hobbies.join(" and ") %]. 
    [% self.moo %]!

Elsewhere on the Farm...

    my $cow = Farm::Cow->new( spots => '8' );
    
    $cow->render();
    
    # This cow has 8 spots - it likes
    # mooing and chewing.
    # Moooooooo!

=head2 Extended features

/alt/path/Farm/Cow.xml.tt2

    <cow sound="[% self.moo | html %]" spots="[% self.spots %]">
    
    [%- FOREACH hobby = self.hobbies %]
        <hobby name="[% hobby | html %]"/>
    [%- END %]
    
    </cow>

Then, back out on the farm

    $cow = Farm::Cow->new( spots => 8 );
    
    $cow->template_src_base( '/alt/path' );  # default is same as module
    $cow->template_src_ext ( '.tt2' );       # default is '.tt'

    $cow->render( 'xml' );                   # adds to file path

    # <cow sound="Moooooooo" spots="8">
    #    <hobby name="mooing"/>
    #    <hobby name="chewing"/>
    # </cow>

=cut

use Moose::Role;
use MooseX::Templated::View::TT;

use MooseX::Templated;
our $VERSION = $MooseX::Templated::VERSION; # CPAN complained when VERSION moved to MX::T

has 'template_view' => (
    is      => 'ro',
    does    => 'MooseX::Templated::View',
    lazy    => 1,
    handles => {
        template_src_ext  => 'template_src_ext',
        template_src_base => 'template_src_base',
    },
    default => sub { (shift)->_build_template_view() }
);

has 'template_view_class' => (
    is      => 'ro',
    isa     => 'ClassName',
    default => 'MooseX::Templated::View::TT',
    required => 1,
);

has 'template_view_config' => (
    is      => 'ro',
    isa     => 'HashRef',
    default => sub { {} },
);

# implemented as sub (rather than handles) so it can be
# 'excluded' in consuming objects
sub render {
    my $self = shift;
    $self->template_view->render( @_ );
}

sub _build_template_view {
    my $self = shift;
    return $self->template_view_class->new(
            'module' => $self,
            'config' => $self->template_view_config,
        );
}

=head1 METHODS

=head2 render( %options )

This renders the module consuming this role and returns the output as a string. This
method accepts the following optional key/values (either as %options or \%options):

=head3 source

Specifies the template source to be used for the rendering process. For 
flexibility, the source can be specified in a number of different ways - the
intention is for it to Do What You Mean (DWYM).

How the source is interpreted will depend partly on the default options specified by the 
L<template_view_class> that you are using (the default view is L<MooseX::Templated::View::TT>).
However, for consistency across your application, the recommended usage is to allow the
template source to be decided by using 'shortcuts'.

Default:

    Farm::Cow->new->render()

    # CHECKS FOR:
    #   - Farm::Cow::_template()
    #   - /path/to/Farm/Cow.tt

Using shortcuts:

    Farm::Cow->new->render( source => 'xml' )
    
    # CHECKS FOR:
    #   - Farm::Cow::_template_xml()
    #   - /path/to/Farm/Cow.xml.tt

These alternatives will also work:

    Farm::Cow->new->render( source => '/other/path/cow.tt' );
    Farm::Cow->new->render( source => 'Cow goes [% self.moo %]' );

See L<Setting the template source> for more information on the logic behind 
choosing how to interpret this string.

=head2 template_view

Provides access to the underlying MooseX::Templated::View object

=head2 template_view_class

Can be passed in the constructor to specify which MooseX::Templated::View to use. 

By default this is set to use the view based on Template Toolkit:

    'template_view_class' => 'MooseX::Templated::View::TT'

However, it is entirely possible that views will be written for different templating 
engines in the future (contribs welcome!).

See L<MooseX::Templated::View> for details on implementing your own view.

=head2 template_view_config( \%options )

Config options to be passed to the template view class when creating the template
engine. These will be merged with any default parameters set by the view engine.

=cut


1; # Magic true value required at end of module
__END__

=head1 DESCRIPTION

=head2 Setting the template source

The decision process for assigning the source is indicated by first 
successful step in the sequence below:

=head2 render( source => "SCALAR" )

=over 8

=item 1. Internal method (shortcut)

Check if the calling module provides a method named '_template' or '_template_{TYPE}'.
If so, the template source will be set from the result of that method call.

    package Farm::Cow;
    
    with 'MooseX::Templated::Role';
    
    has 'spots'    => ( is => 'rw', default => 8 );
    has 'hobbies'  => ( is => 'rw', default => sub { [ 'mooing', 'chewing' ] } );
    
    sub _template { 'Cow has [% self.spots %] spots' }
    
    sub _template_xml {
        my $self = shift;
        return <<"_XML";
    <cow spots='[% self.spots %]'>
        [% FOREACH hobby = self.hobbies %]
        <hobby name="[% hobby %]" />
        [% END %]
    </cow>
    _XML
    };
    
    1;
    
    package main;
    
    print Farm::Cow->new->render;
    # 'Cow has 8 spots'
    
    print Farm::Cow->new->render( source => 'XML' );
    # <cow spots="8">
    #   <hobby name="mooing"/>
    #   <hobby name="chewing"/>
    # </cow>

=item 2. External file (shortcut)

Use an associated template file if it exists (see L<build_build_src_path> for more 
information on how the template file path is built). This is the recommended approach
as it separates the template location and content from the application code.

/path/to/lib/Farm/Cow.pm
    
    package Farm::Cow;
    
    with 'MooseX::Templated::Role';
    
    has 'spots'    => ( is => 'rw', default => 8 );
    has 'hobbies'  => ( is => 'rw', default => sub { [ 'mooing', 'chewing' ] } );
    
/path/to/lib/Farm/Cow.tt
    
    Cow has [% self.spots %] spots
    
/path/to/lib/Farm/Cow.xml.tt
    
    <cow spots="[% self.spots %]">
        [% FOREACH hobby = self.hobbies %]
        <hobby name="[% hobby %]" />
        [% END %]
    </cow>
    
Elsewhere...
    
    use Farm::Cow;
    
    print Farm::Cow->new->render;
    # 'Cow has 8 spots'
    
    print Farm::Cow->new->render( source => 'XML' );
    # <cow spots="8">
    #   <hobby name="mooing"/>
    #   <hobby name="chewing"/>
    # </cow>


=item 3. External file

Treats the string as a path to the template source file. This is not the recommended approach 
as liberally scattering hardcoded file paths into your application code is most probably a
bad idea.

    Farm::Cow->new->render( source => '/path/to/lib/Farm/Cow.tt' );
    # 'Cow has 2 spots'

=back

=head2 render( source => \"SCALAR" )

Alternatively, if you pass the source as a SCALAR REF then it will be 
assumed to be raw template source. This is not a recommended approach, 
but hey - TMTOWTDI.

    Farm::Cow->new->render( source => \'Cow goes "[% self.moo %]"!' );
    # 'Cow goes "Moooooooo!"'


=head1 DEPENDENCIES

L<Moose>, L<Template>, L<Readonly>, L<File::Slurp>, L<File::Where>

=head1 INCOMPATIBILITIES

None reported.

=head1 BUGS AND LIMITATIONS

No bugs have been reported.

Please report any bugs or feature requests to
C<bug-moosex-templated@rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org>.

=head1 ACKNOWLEDGEMENTS

Chris Prather (perigrin)

=head1 AUTHOR

Ian Sillitoe  C<< <isillitoe@cpan.org> >>

=head1 LICENCE AND COPYRIGHT

Copyright (c) 2008, Ian Sillitoe C<< <isillitoe@cpan.org> >>. All rights reserved.

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