package Catalyst::View::Xslate;
use Moose;
use Moose::Util::TypeConstraints qw(coerce from where via subtype);
use Encode;
use Text::Xslate;
use namespace::autoclean;
use Scalar::Util qw/blessed weaken/;
use File::Find ();
our $VERSION = '0.00016';
extends 'Catalyst::View';
with 'Catalyst::Component::ApplicationAttribute';
has catalyst_var => (
is => 'rw',
isa => 'Str',
default => 'c'
);
has template_extension => (
is => 'rw',
isa => 'Str',
default => '.tx'
);
has content_charset => (
is => 'rw',
isa => 'Str',
default => 'UTF-8'
);
has encode_body => (
is => 'rw',
isa => 'Bool',
default => 1,
);
my $clearer = sub { $_[0]->clear_xslate };
has path => (
is => 'rw',
isa => 'ArrayRef',
trigger => $clearer,
lazy => 1, builder => '_build_path',
);
sub _build_path { return [ shift->_app->path_to('root') ] }
has cache_dir => (
is => 'rw',
isa => 'Str',
trigger => $clearer,
);
has cache => (
is => 'rw',
isa => 'Int',
default => 1,
trigger => $clearer,
);
has function => (
is => 'rw',
isa => 'HashRef',
default => sub { +{} },
trigger => $clearer,
);
has footer => (
is => 'rw',
isa => 'ArrayRef',
default => sub { +[] },
trigger => $clearer
);
has header => (
is => 'rw',
isa => 'ArrayRef',
default => sub { +[] },
trigger => $clearer
);
has module => (
is => 'rw',
isa => 'ArrayRef',
default => sub { +[] },
trigger => $clearer,
);
has input_layer => (
is => 'rw',
isa => 'Str',
trigger => $clearer,
);
has syntax => (
is => 'rw',
isa => 'Str',
trigger => $clearer,
);
has escape => (
is => 'rw',
isa => 'Str',
trigger => $clearer,
);
has suffix => (
is => 'rw',
isa => 'Str',
trigger => $clearer,
default => '.tx',
);
has verbose => (
is => 'rw',
isa => 'Int',
trigger => $clearer,
);
has xslate => (
is => 'rw',
isa => 'Text::Xslate',
clearer => 'clear_xslate',
lazy => 1, builder => '_build_xslate',
);
my $expose_methods_tc = subtype 'HashRef', where { $_ };
coerce $expose_methods_tc,
from 'ArrayRef',
via {
my %values = map { $_ => $_ } @$_;
return \%values;
};
has expose_methods => (
is => 'ro',
isa => $expose_methods_tc,
predicate => 'has_expose_methods',
coerce => 1,
);
has preload => (
is => 'ro',
isa => 'Bool',
default => 1,
);
sub _build_xslate {
my $self = shift;
my $app = $self->_app;
my $name = $app;
$name =~ s/::/_/g;
my %args = (
path => $self->path,
cache_dir => $self->cache_dir || File::Spec->catdir(File::Spec->tmpdir, $name),
map { ($_ => $self->$_) }
qw( cache footer function header module )
);
# optional stuff
foreach my $field ( qw( input_layer syntax escape verbose suffix ) ) {
if (my $value = $self->$field) {
$args{$field} = $value;
}
}
return Text::Xslate->new(%args);
}
sub BUILD {
my $self = shift;
if ($self->preload) {
$self->preload_templates();
}
}
sub preload_templates {
my $self = shift;
my ( $paths, $suffix ) = ( $self->path, $self->suffix );
my $xslate = $self->xslate;
foreach my $path (@$paths) {
File::Find::find( sub {
if (/\Q$suffix\E$/) {
my $file = $File::Find::name;
$file =~ s/\Q$path\E .//xsm;
$xslate->load_file($file);
}
}, $path);
}
}
sub process {
my ($self, $c) = @_;
my $stash = $c->stash;
my $template = $stash->{template} || $c->action . $self->template_extension;
if (! defined $template) {
$c->log->debug('No template specified for rendering') if $c->debug;
return 0;
}
my $output = eval {
$self->render( $c, $template, $stash );
};
if (my $err = $@) {
return $self->_rendering_error($c, $err);
}
my $res = $c->response;
if (! $res->content_type) {
$res->content_type('text/html; charset=' . $self->content_charset);
}
if ( $self->encode_body ) {
$res->body( encode($self->content_charset, $output) );
}
else {
$res->body( $output );
}
return 1;
}
sub build_exposed_method {
my ( $self, $ctx, $code ) = @_;
return sub { $self->$code($ctx, @_) };
}
sub render {
my ($self, $c, $template, $vars) = @_;
$vars = $vars ? $vars : $c->stash;
if ($self->has_expose_methods) {
foreach my $exposed_method( keys %{$self->expose_methods} ) {
if(my $code = $self->can( $self->expose_methods->{$exposed_method} )) {
my $weak_ctx = $c;
weaken $weak_ctx;
$vars->{$exposed_method} = $self->build_exposed_method($weak_ctx, $code);
} else {
Catalyst::Exception->throw( "$exposed_method not found in Xslate view" );
}
}
}
local $vars->{ $self->catalyst_var } =
$vars->{ $self->catalyst_var } || $c;
if(ref $template eq 'SCALAR') {
return $self->xslate->render_string( $$template, $vars );
} else {
return $self->xslate->render($template, $vars );
}
}
sub _rendering_error {
my ($self, $c, $err) = @_;
my $error = qq/Couldn't render template "$err"/;
$c->log->error($error);
$c->error($error);
return 0;
}
__PACKAGE__->meta->make_immutable();
1;
__END__
=head1 NAME
Catalyst::View::Xslate - Text::Xslate View Class
=head1 SYNOPSIS
package MyApp::View::Xslate;
use Moose;
extends 'Catalyst::View::Xslate';
1;
=head1 VIEW CONFIGURATION
You may specify the following configuration items in from your config file
or directly on the view object.
=head2 catalyst_var
The name used to refer to the Catalyst app object in the template
=head2 template_extension
The suffix used to auto generate the template name from the action name
(when you do not explicitly specify the template filename);
Do not confuse this with the C<suffix> option, which is passed directly to
the Text::Xslate object instance. This option works on the filename used
for the initial request, while C<suffix> controls what C<cascade> and
C<include> directives do inside Text::Xslate.
=head2 content_charset
The charset used to output the response body. The value defaults to 'UTF-8'.
=head2 encode_body
By default, output will be encoded to C<content_charset>.
You can set it to 0 to disable this behavior.
(you need to do this if you're using C<Catalyst::Plugin::Unicode::Encoding>)
=head2 Text::Xslate CONFIGURATION
The following parameters are passed to the Text::Xslate constructor.
When reset during the life cyle of the Catalyst app, these parameters will
cause the previously created underlying Text::Xslate object to be cleared
=head2 path
=head2 cache_dir
=head2 cache
=head2 header
=head2 escape
=head2 footer
=head2 function
=head2 input_layer
=head2 module
=head2 syntax
=head2 verbose
=head2 suffix
Use this to enable TT2 compatible variable methods via Text::Xslate::Bridge::TT2 or Text::Xslate::Bridge::TT2Like
package MyApp::View::Xslate;
use Moose;
extends 'Catalyst::View::Xslate';
has '+module' => (
default => sub { [ 'Text::Xslate::Bridge::TT2Like' ] }
);
=head1 preload
Boolean flag indicating if templates should be preloaded. By default this is enabled.
Preloading templates will basically cutdown the cost of template compilation for the first hit.
=head2 expose_methods
Use this option to specify methods from the View object to be exposed in the
template. For example, if you have the following View:
package MyApp::View::Xslate;
use Moose;
extends 'Catalyst::View::Xslate';
sub foo {
my ( $self, $c, @args ) = @_;
return ...; # do something with $self, $c, @args
}
then by setting expose_methods, you will be able to use $foo() as a function in
the template:
<: $foo("a", "b", "c") # calls $view->foo( $c, "a", "b", "c" ) :>
C<expose_methods> takes either a list of method names to expose, or a hash reference, in order to alias it differently in the template.
MyApp::View::Xslate->new(
# exposes foo(), bar(), baz() in the template
expose_methods => [ qw(foo bar baz) ]
);
MyApp::View::Xslate->new(
# exposes $foo_alias(), $bar_alias(), $baz_alias() in the template,
# but they will in turn call foo(), bar(), baz(), on the view object.
expose_methods => {
foo => "foo_alias",
bar => "bar_alias",
baz => "baz_alias",
}
);
NOTE: you can mangle the process of building the exposed methods, see C<build_exposed_method>.
=head1 METHODS
=head1 C<$view->process($c)>
Called by Catalyst.
=head2 C<$view->render($c, $template, \%vars)>
Renders the given C<$template> using variables \%vars.
C<$template> can be a template file name, or a scalar reference to a template
string.
$view->render($c, "/path/to/a/template.tx", \%vars );
$view->render($c, \'This is a xslate template!', \%vars );
=head2 C<$view->preload_templates>
Preloads templates in $view-E<gt>path.
=head2 C<$view->build_exposed_method>
Hook point for mangling the building process of exposed methods.
=head1 AUTHOR
Copyright (c) 2010 Daisuke Maki C<< <daisuke@endeworks.jp> >>
=head1 LICENSE
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
See http://www.perl.com/perl/misc/Artistic.html
=cut