package Mojolicious::Plugin::TtRenderer::Engine;

use warnings;
use strict;
use 5.016;
use base 'Mojo::Base';
use Carp ();
use File::Spec ();
use Mojo::ByteStream 'b';
use Template ();
use Cwd qw/abs_path/;
use Scalar::Util 'weaken';
use POSIX ':errno_h';

# ABSTRACT: Template Toolkit renderer for Mojolicious
our $VERSION = '1.62'; # VERSION

__PACKAGE__->attr('tt');

sub build {
    my $self = shift->SUPER::new(@_);
    weaken($self->{app});
    $self->_init(@_);
    sub { $self->_render(@_) }
}

sub _init {
    my $self = shift;
    my %args = @_;

    #$Template::Parser::DEBUG = 1;

    my $dir;
    my $app = delete $args{mojo} || delete $args{app};
    if($dir=$args{cache_dir}) {

      if($app && substr($dir,0,1) ne '/') {
        $dir=$app->home->rel_file($dir);
      }
    }

    # TODO
    #   take and process options :-)

    my @renderer_paths = $app ? map { abs_path($_) } grep { -d $_ } @{ $app->renderer->paths } : ();
    push @renderer_paths, 'templates';

    my %config = (
        INCLUDE_PATH => \@renderer_paths,
        COMPILE_EXT  => '.ttc',
        UNICODE      => 1,
        ENCODING     => 'utf-8',
        CACHE_SIZE   => 128,
        RELATIVE     => 1,
        %{$args{template_options} || {}},
    );

    if ( !exists( $config{COMPILE_DIR} ) ) {
        $config{COMPILE_DIR} //= $dir || do {
          my $tmpdir = File::Spec->catdir(File::Spec->tmpdir, "ttr$<");
          mkdir $tmpdir unless -d $tmpdir;
          $tmpdir;
        };
    }

    $config{LOAD_TEMPLATES} =
      [Mojolicious::Plugin::TtRenderer::Provider->new(%config, renderer => $app->renderer)]
      unless $config{LOAD_TEMPLATES};

    $self->tt(Template->new(\%config))
      or Carp::croak "Could not initialize Template object: $Template::ERROR";

    $self;
}

sub _render {
    my ($self, $renderer, $c, $output, $options) = @_;

    # Inline
    my $inline = $options->{inline};

    # Template
    my $t = $renderer->template_name($options);
    $t = 'inline' if defined $inline;
    return unless $t;

    my $helper = Mojolicious::Plugin::TtRenderer::Helper->new(ctx => $c);

    # fixes for t/lite_app_with_default_layouts.t
    unless ($c->stash->{layout}) {
        $c->stash->{content} ||= $c->stash->{'mojo.content'}->{content};
    }

    my @params = ({%{$c->stash}, c => $c, h => $helper}, $output, {binmode => ':utf8'});
    my $provider = $self->tt->{SERVICE}->{CONTEXT}->{LOAD_TEMPLATES}->[0];
    $provider->options($options);

    my $ok = do {
        if (defined $inline) {
            $self->tt->process(\$inline, @params);
        }
        else {
            my @ret = $provider->fetch($t);

            if (not defined $ret[1]) {
                $self->tt->process($ret[0], @params);
            }
            elsif (not defined $ret[0]) { # not found
                return;
            }
            else { # error
                return if $! == ENOENT && (not ref $ret[0]); # not found when not blessed exception
                die $ret[0];
            }
        }
    };

    # Error
    die $self->tt->error unless $ok;
}

1;    # End of Mojolicious::Plugin::TtRenderer::Engine

package
  Mojolicious::Plugin::TtRenderer::Helper;

use strict;
use warnings;

use base 'Mojo::Base';

our $AUTOLOAD;

__PACKAGE__->attr('ctx');

sub AUTOLOAD {
    my $self = shift;

    my $method = $AUTOLOAD;

    return if $method =~ /^[A-Z]+?$/;
    return if $method =~ /^_/;
    return if $method =~ /(?:\:*?)DESTROY$/;

    $method = (split /::/, $method)[-1];

    die qq/Unknown helper: $method/ unless $self->ctx->app->renderer->helpers->{$method};

    $self->ctx->$method(@_);
}

1;

package
  Mojolicious::Plugin::TtRenderer::Provider;

use strict;
use warnings;

use base 'Template::Provider';
use Scalar::Util 'weaken';

sub new {
    my $class = shift;
    my %params = @_;

    my $renderer = delete $params{renderer};

    my $self = $class->SUPER::new(%params);
    $self->renderer($renderer);
    $self;
}

sub renderer      { @_ > 1 ? weaken($_[0]->{renderer} = $_[1]) : $_[0]->{renderer} }
sub options       { @_ > 1 ? $_[0]->{options}       = $_[1] : $_[0]->{options} }

sub _template_modified {
    my($self, $template) = @_;
    $self->SUPER::_template_modified($template) || $template =~ /^templates(?:\/|\\)/ || undef;
}

sub _template_content {
    my $self = shift;
    my ($path) = @_;

    my $options = delete $self->{options};

    # Convert backslashes to forward slashes to make inline templates work on Windows
    $path =~ s/\\/\//g;
    my ($t) = ($path =~ m{templates\/(.*)$});

    if (-r $path) {
        return $self->SUPER::_template_content(@_);
    }

    my $data;
    my $error = '';

    # Try DATA section
    if(defined $options) {
        $data = $self->renderer->get_data_template($options);
    } else {
        foreach my $class (@{ $self->renderer->classes }) {
            $data = Mojo::Loader::data_section($class, $t);
            last if $data;
        }
    }

    unless($data) {
        $data = '';
        $error = "$path: not found";
    }
    wantarray ? ($data, $error, time) : $data;  ## no critic (Freenode::Wantarray)
}

1;

__END__

=pod

=encoding utf-8

=head1 NAME

Mojolicious::Plugin::TtRenderer::Engine - Template Toolkit renderer for Mojolicious

=head1 VERSION

version 1.62

=head1 SYNOPSIS

Add the handler:

 sub startup {
     ...
 
     # Via mojolicious plugin
     $self->plugin(tt_renderer => {template_options => {FILTERS => [ ... ]}});
 
     # Or manually
     use Mojolicious::Plugin::TtRenderer::Engine;
 
     my $tt = Mojolicious::Plugin::TtRenderer::Engine->build(
         mojo => $self,
         template_options => {
             PROCESS  => 'tpl/wrapper',
             FILTERS  => [ ... ],
             UNICODE  => 1,
             ENCODING => 'UTF-8',
         }
     );
 
     $self->renderer->add_handler( tt => $tt );
 }

Template parameter are taken from C<$c-E<gt>stash>.

=head1 DESCRIPTION

See L<Mojolicious::Plugin::TtRenderer> for details on the plugin interface to this module.

This module provides an engine for the rendering of L<Template Toolkit|Template> templates
within a Mojolicious context.  Templates may be, stored on the local file system, provided
inline by the controller or included in the C<__DATA__> section.  Where possible this modules
attempts to provide a TT analogue interface to the L<Perlish templates|Mojo::Template> which
come with Mojolicious.

=for stopwords Bjørn
Szász
Árpád
Романов
Сергей

=head1 RENDERING

The template file for C<"example/welcome"> would be C<"templates/welcome.html.tt">.

When template file is not available rendering from C<__DATA__> is attempted.

 __DATA__
 
 @@ welcome.html.tt
 Welcome, [% user.name %]!

Inline template is also supported:

 $self->render(inline => '[% 1 + 1 %]', handler => 'tt');

=head1 HELPERS

Helpers are exported automatically under C<h> namespace.

 [% h.url_for('index') %]

=head1 METHODS

=head2 build

This method returns a handler for the Mojolicious renderer.

Supported parameters are

=over 4

=item mojo
C<build> currently uses a C<mojo> parameter pointing to the base class (Mojo).
object. When used the INCLUDE_PATH will be set to

=item template_options

A hash reference of options that are passed to Template->new().  Note that if you
specify an C<INCLUDE_PATH> through this option it will remove the DATA section
templates from your path.  A better way to specify an C<INCLUDE_PATH> if you also
want to use DATA section templates it by manipulating the L<Mojolicious::Renderer>
path.

=item cache_dir

Absolute or relative dir to your app home, to cache processed versions of your
templates. Will default to a temp-dir if not set.

=back

=head1 SEE ALSO

L<Mojolicious::Plugin::TtRenderer>,
L<Mojolicious>,
L<Mojolicious::Guides>,
L<http://mojolicious.org>.

=head1 AUTHOR

Original author: Ask Bjørn Hansen

Current maintainer: Graham Ollis E<lt>plicease@cpan.orgE<gt>

Contributors:

vti

Marcus Ramberg

Matthias Bethke

Htbaa

Magnus Holm

Maxim Vuets

Rafael Kitover

giftnuss

Cosimo Streppone

Fayland Lam

Jason Crowther

spleenjack

Árpád Szász

Сергей Романов

uwisser

Dinis Lage

jay mortensen (GMORTEN)

Matthew Lawrence (MATTLAW)

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2009-2021 by Ask Bjørn Hansen.

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
