package Catalyst::View::TT; use strict; use warnings; use base qw/Catalyst::View/; use Data::Dump 'dump'; use Template; use Template::Timer; use MRO::Compat; use Scalar::Util qw/blessed weaken/; our $VERSION = '0.46'; $VERSION =~ tr/_//d; __PACKAGE__->mk_accessors('template'); __PACKAGE__->mk_accessors('expose_methods'); __PACKAGE__->mk_accessors('include_path'); __PACKAGE__->mk_accessors('content_type'); *paths = \&include_path; =head1 NAME Catalyst::View::TT - Template View Class =head1 SYNOPSIS # use the helper to create your View myapp_create.pl view Web TT # add custom configuration in View/Web.pm __PACKAGE__->config( # any TT configuration items go here TEMPLATE_EXTENSION => '.tt', CATALYST_VAR => 'c', TIMER => 0, ENCODING => 'utf-8' # Not set by default PRE_PROCESS => 'config/main', WRAPPER => 'site/wrapper', render_die => 1, # Default for new apps, see render method docs expose_methods => [qw/method_in_view_class/], ); # add include path configuration in MyApp.pm __PACKAGE__->config( 'View::Web' => { INCLUDE_PATH => [ __PACKAGE__->path_to( 'root', 'src' ), __PACKAGE__->path_to( 'root', 'lib' ), ], }, ); # render view from lib/MyApp.pm or lib/MyApp::Controller::SomeController.pm sub message : Global { my ( $self, $c ) = @_; $c->stash->{template} = 'message.tt2'; $c->stash->{message} = 'Hello World!'; $c->forward( $c->view('Web') ); } # access variables from template The message is: [% message %]. # example when CATALYST_VAR is set to 'Catalyst' Context is [% Catalyst %] The base is [% Catalyst.req.base %] The name is [% Catalyst.config.name %] # example when CATALYST_VAR isn't set Context is [% c %] The base is [% base %] The name is [% name %] =cut sub _coerce_paths { my ( $paths, $dlim ) = shift; return () if ( !$paths ); return @{$paths} if ( ref $paths eq 'ARRAY' ); # tweak delim to ignore C:/ unless ( defined $dlim ) { $dlim = ( $^O eq 'MSWin32' ) ? ':(?!\\/)' : ':'; } return split( /$dlim/, $paths ); } sub new { my ( $class, $c, $arguments ) = @_; my $config = { EVAL_PERL => 0, TEMPLATE_EXTENSION => '', CLASS => 'Template', %{ $class->config }, %{$arguments}, }; if ( ! (ref $config->{INCLUDE_PATH} eq 'ARRAY') ) { my $delim = $config->{DELIMITER}; my @include_path = _coerce_paths( $config->{INCLUDE_PATH}, $delim ); if ( !@include_path ) { my $root = $c->config->{root}; my $base = Path::Class::dir( $root, 'base' ); @include_path = ( "$root", "$base" ); } $config->{INCLUDE_PATH} = \@include_path; } # if we're debugging and/or the TIMER option is set, then we install # Template::Timer as a custom CONTEXT object, but only if we haven't # already got a custom CONTEXT defined if ( $config->{TIMER} ) { if ( $config->{CONTEXT} ) { $c->log->error( 'Cannot use Template::Timer - a TT CONTEXT is already defined' ); } else { $config->{CONTEXT} = Template::Timer->new(%$config); } } if ( $c->debug && $config->{DUMP_CONFIG} ) { $c->log->debug( "TT Config: ", dump($config) ); } my $self = $class->next::method( $c, { %$config }, ); # Set base include paths. Local'd in render if needed $self->include_path($config->{INCLUDE_PATH}); $self->expose_methods($config->{expose_methods}); $self->config($config); # Creation of template outside of call to new so that we can pass [ $self ] # as INCLUDE_PATH config item, which then gets ->paths() called to get list # of include paths to search for templates. # Use a weakened copy of self so we don't have loops preventing GC from working my $copy = $self; Scalar::Util::weaken($copy); $config->{INCLUDE_PATH} = [ sub { $copy->paths } ]; if ( $config->{PROVIDERS} ) { my @providers = (); if ( ref($config->{PROVIDERS}) eq 'ARRAY') { foreach my $p (@{$config->{PROVIDERS}}) { my $pname = $p->{name}; my $prov = 'Template::Provider'; if($pname eq '_file_') { $p->{args} = { %$config }; } else { if($pname =~ s/^\+//) { $prov = $pname; } else { $prov .= "::$pname"; } # We copy the args people want from the config # to the args $p->{args} ||= {}; if ($p->{copy_config}) { map { $p->{args}->{$_} = $config->{$_} } grep { exists $config->{$_} } @{ $p->{copy_config} }; } } local $@; eval "require $prov"; if(!$@) { push @providers, "$prov"->new($p->{args}); } else { $c->log->warn("Can't load $prov, ($@)"); } } } delete $config->{PROVIDERS}; if(@providers) { $config->{LOAD_TEMPLATES} = \@providers; } } $self->{template} = $config->{CLASS}->new($config) || do { my $error = $config->{CLASS}->error(); $c->log->error($error); $c->error($error); return undef; }; return $self; } sub process { my ( $self, $c ) = @_; my $template = $c->stash->{template} || $c->action . $self->config->{TEMPLATE_EXTENSION}; unless (defined $template) { $c->log->debug('No template specified for rendering') if $c->debug; return 0; } local $@; my $output = eval { $self->render($c, $template) }; if (my $err = $@) { return $self->_rendering_error($c, $template . ': ' . $err); } if (blessed($output) && $output->isa('Template::Exception')) { $self->_rendering_error($c, $output); } unless ( $c->response->content_type ) { my $default = $self->content_type || 'text/html; charset=UTF-8'; $c->response->content_type($default); } $c->response->body($output); return 1; } sub _rendering_error { my ($self, $c, $err) = @_; my $error = qq/Couldn't render template "$err"/; $c->log->error($error); $c->error($error); return 0; } sub render { my ($self, $c, $template, $args) = @_; $c->log->debug(qq/Rendering template "$template"/) if $c && $c->debug; my $output; my $vars = { (ref $args eq 'HASH' ? %$args : %{ $c->stash() }), $self->template_vars($c) }; local $self->{include_path} = [ @{ $vars->{additional_template_paths} }, @{ $self->{include_path} } ] if ref $vars->{additional_template_paths}; unless ( $self->template->process( $template, $vars, \$output ) ) { if (exists $self->{render_die}) { die $self->template->error if $self->{render_die}; return $self->template->error; } $c->log->debug('The Catalyst::View::TT render() method will start dying on error in a future release. Unless you are calling the render() method manually, you probably want the new behaviour, so set render_die => 1 in config for ' . blessed($self) . '. If you wish to continue to return the exception rather than throwing it, add render_die => 0 to your config.') if $c && $c->debug; return $self->template->error; } return $output; } sub template_vars { my ( $self, $c ) = @_; return () unless $c; my $cvar = $self->config->{CATALYST_VAR}; my %vars = defined $cvar ? ( $cvar => $c ) : ( c => $c, base => $c->req->base, name => $c->config->{name} ); if ($self->expose_methods) { my $meta = $self->meta; foreach my $method_name (@{$self->expose_methods}) { my $method = $meta->find_method_by_name( $method_name ); unless ($method) { Catalyst::Exception->throw( "$method_name not found in TT view" ); } my $method_body = $method->body; my $weak_ctx = $c; weaken $weak_ctx; my $sub = sub { my @args = @_; my $ret; eval { $ret = $self->$method_body($weak_ctx, @args); }; if ($@) { if (blessed($@)) { die $@; } else { Catalyst::Exception->throw($@); } } return $ret; }; $vars{$method_name} = $sub; } } return %vars; } 1; __END__ =head1 DESCRIPTION This is the Catalyst view class for the L