package Dancer2::Template::Caribou;
BEGIN {
  $Dancer2::Template::Caribou::AUTHORITY = 'cpan:YANICK';
}
{
  $Dancer2::Template::Caribou::VERSION = '0.1.0';
}
#ABSTRACT: Template::Caribou wrapper for Dancer2


use strict;
use warnings;

use Path::Tiny;
use Path::Iterator::Rule;
use Moose::Util qw/ with_traits find_meta /;

use Moo;

with 'Dancer2::Core::Role::Template';

has '+default_tmpl_ext' => (
    default => sub { 'bou' },
);

has view_class => (
    is => 'ro',
    default => sub { { } },
);

has layout_class => (
    is => 'ro',
    default => sub { { } },
);

has auto_reload => (
    is => 'ro',
    default => sub { 0 },
);

has namespace => (
    is => 'ro',
    lazy => 1,
    default => sub {
        $_[0]->config->{namespace} || 'Dancer2::View';
    },
);

sub _build_type { 'Caribou' };

sub BUILD {
    my $self = shift;

    my $views_dir = $self->views;

    my @views =
    Path::Iterator::Rule->new->skip_dirs('layouts')->file->name('bou')->all(
        $views_dir );

    $self->generate_view_class( $_ ) for @views;

    my @layouts =
    Path::Iterator::Rule->new->file->name('bou')->all(
        path( $views_dir, 'layouts' ) );

    $self->generate_layout_class( $_ ) for @layouts;
}

sub generate_layout_class {
    my( $self, $bou ) = @_;

    my $bou_dir = path($bou)->parent;
    my $segment = ''.path($bou)->relative($self->views.'/layouts')->parent;

    ( my $name = $segment ) =~ s#/#::#;
    $name = join '::', $self->namespace, $name;

    my $inner = path($bou)->slurp;

    eval qq{
package $name;

use Moose::Role;
use Template::Caribou;

with 'Template::Caribou';

# line 1 "$bou"

$inner;

with 'Template::Caribou::Files' => {
    dirs => [ '$bou_dir' ],
    auto_reload => 1,
};

1;
} unless find_meta( $name );

    warn $@ if $@;

    $self->layout_class->{$segment} = $name;

}

sub generate_view_class {
    my( $self, $bou ) = @_;

    my $bou_dir = path($bou)->parent;
    my $segment = ''.path($bou)->relative($self->views)->parent;

    ( my $name = $segment ) =~ s#/#::#;
    $name = join '::', $self->namespace, $name;

    return if $self->layout_class->{$segment};

    my $inner = path($bou)->slurp;

    my $auto_reload = $self->auto_reload;

    eval qq{
package $name;

use Moose;
use Template::Caribou;

with 'Template::Caribou';

has app => (
    is => 'ro',
    handles => [ 'config' ],
);

# line 1 "$bou"

$inner;

with 'Template::Caribou::Files' => {
    dirs => [ '$bou_dir' ],
    auto_reload => $auto_reload,
};

1;
} unless find_meta($name);

    warn $@ if $@;

    $self->view_class->{$segment} = $name;

}

sub apply_layout {
    return $_[1];
}

sub render {
    my( $self, $template, $tokens ) = @_;

    $template =~ s/\.bou$//;

    $template = path( $template )->relative( $self->views );

    my $class = $self->view_class->{$template};
    
    unless ( $class ) {
        my $c = $template;
        $c =~ s#/#::#g;
        $c = join '::', $self->namespace, $c;
        die "template '$template' not found\n"
            unless eval { $c->DOES('Template::Caribou') };
        $class = $c;
    }

    if ( my $lay = $self->layout || $self->context->app->config->{layout} ) {
        my $role = $self->layout_class->{$lay}
            or die "layout '$lay' not defined\n";

        $class = with_traits( $class, $role, 
        )
    }

    return $class->new( app => $self->context->app, %$tokens)->render('page');
}

1;

__END__

=pod

=head1 NAME

Dancer2::Template::Caribou - Template::Caribou wrapper for Dancer2

=head1 VERSION

version 0.1.0

=head1 SYNOPSIS

    # in 'config.yml'
    template: Caribou

    engines:
      template:
        Caribou:
          namespace:    MyApp::View
          auto_reload:  1


    # and then in the application
    get '/' => sub { 
        ...;

        template 'main' => \%options;
    };

=head1 DESCRIPTION

C<Dancer2::Template::Caribou> is an interface for the L<Template::Caribou>
template system. Be forewarned, both this module and C<Template::Caribou>
itself are alpha-quality software and are still subject to any changes. <Caveat
Maxima Emptor>.

=head2 Basic Usage

At the base, if you do

    get '/' => sub {
        ...

        return template 'MyView', \%options;
    };

the template name (here I<MyView>) will be concatenated with the 
configured view namespace (which defaults to I<Dancer2::View>)
to generate the Caribou class name. A Caribou object is created
using C<%options> as its arguments, and its inner template C<page> is then
rendered. In other words, the last line of the code above becomes 
equivalent to 

    return Dancer2::View::MyView->new( %options )->render('page');

=head2 '/views' template classes

Template classes can be created straight from the C</views> directory.
Any directory containing a file named C<bou> will be turned into a 
C<Template::Caribou> class. Additionally, any file with a C<.bou> extension
contained within that directory will be turned into a inner template for 
that class.

=head3 The 'bou' file

The 'bou' file holds the custom bits of the Template::Caribou class.

For example, a basic welcome template could be:

    # in /views/welcome/bou
    
    use Template::Caribou::Tags::HTML ':all';

    has name => ( is => 'ro' );

    template page => sub {
        my $self = shift;

        html {
            head { title { 'My App' } };
            body {
                h1 { 'hello ' . $self->name .'!' };
            };
        }
    };

which would be invoqued via

    get '/hi/:name' => sub {
        template 'welcome' => { name => param('name') };
    };

=head3 The inner template files

All files with a '.bou' extension found in the same directory as the 'bou'
file become inner templates for the class. So, to continue with the example
above, we could change it into

    # in /views/howdie/bou
    
    use Template::Caribou::Tags::HTML ':all';

    has name => ( is => 'ro' );


    # in /views/howdie/page
    html {
        head { title { 'My App' } };
        body {
            h1 { 'howdie ' . $self->name . '!' };
        };
    }

=head3 Layouts as roles

For the layout sub-directory, an additional piece of magic is performed.
The 'bou'-marked directories are turned into roles instead of classes, which will be applied to
the template class. Again, to take our example:

    # in /views/layouts/main/bou
    # empty file

    # in /views/layouts/main/page
    
    # the import of tags really needs to be here 
    # instead than in the 'bou' file 
    use Template::Caribou::Tags::HTML ':all';

    html {
        head { title { 'My App' } };
        body {
            show( 'inner' );
        };
    }

    # in /views/hullo/bou
    
    use Template::Caribou::Tags::HTML ':all';

    has name => ( is => 'ro' );

    # in /views/howdie/inner
    h1 { 'hullo ' . $self->name . '!' };

=head1 CONFIGURATION

=over

=item namespace 

The namespace under which the Caribou classes are created.
defaults to C<Dancer2::View>.

=item auto_reload

If set to C<true>, the Caribou object will verify if any of the 
template files have changed before rendering and, if that's the case,
will self-update. Defaults to C<false>.

=back

=head1 AUTHOR

Yanick Champoux <yanick@babyl.dyndns.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Yanick Champoux.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
