File Coverage

blib/lib/Catalyst/View/HTML/Mason.pm
Criterion Covered Total %
statement 95 100 95.0
branch 20 32 62.5
condition 5 14 35.7
subroutine 22 23 95.6
pod 2 3 66.6
total 144 172 83.7


line stmt bran cond sub pod time code
1             package Catalyst::View::HTML::Mason;
2             our $AUTHORITY = 'cpan:FLORA';
3             # ABSTRACT: HTML::Mason rendering for Catalyst
4             $Catalyst::View::HTML::Mason::VERSION = '0.19';
5 5     5   6897400 use Moose;
  5         9  
  5         28  
6 5     5   21535 use Try::Tiny;
  5         8  
  5         339  
7 5     5   22 use MooseX::Types::Moose qw/ArrayRef HashRef ClassName Str Bool Object CodeRef/;
  5         21  
  5         57  
8 5     5   23640 use MooseX::Types::Structured qw/Tuple/;
  5         1141746  
  5         32  
9 5     5   1353 use Encode::Encoding;
  5         9  
  5         126  
10 5     5   3211 use Data::Visitor::Callback;
  5         206698  
  5         216  
11 5     5   38 use Module::Runtime;
  5         6  
  5         32  
12              
13 5     5   161 use namespace::autoclean;
  5         7  
  5         43  
14              
15             extends 'Catalyst::View';
16              
17              
18             has interp => (
19             is => 'ro',
20             isa => Object,
21             lazy => 1,
22             builder => '_build_interp',
23             );
24              
25              
26             {
27 5     5   518 use Moose::Util::TypeConstraints;
  5         8  
  5         46  
28              
29             my $tc = subtype as ClassName;
30             coerce $tc, from Str, via { Module::Runtime::require_module($_); $_ };
31              
32             has interp_class => (
33             is => 'ro',
34             isa => $tc,
35             coerce => 1,
36             builder => '_build_interp_class',
37             );
38             }
39              
40              
41             has interp_args => (
42             is => 'ro',
43             isa => HashRef,
44             default => sub { +{} },
45             );
46              
47              
48             has template_extension => (
49             is => 'ro',
50             isa => Str,
51             default => '',
52             );
53              
54              
55             has always_append_template_extension => (
56             is => 'ro',
57             isa => Bool,
58             default => 0,
59             );
60              
61              
62             {
63             my $tc = subtype as 'Encode::Encoding';
64             coerce $tc, from Str, via { Encode::find_encoding($_) };
65              
66             has encoding => (
67             is => 'ro',
68             isa => $tc,
69             coerce => 1,
70             predicate => 'has_encoding',
71             );
72             }
73              
74              
75             {
76             my $glob_spec = subtype as Tuple[Str,CodeRef];
77             coerce $glob_spec, from Str, via {
78             my ($type, $var) = split q//, $_, 2;
79             my $fn = {
80             '$' => sub { $_[0] },
81             '@' => sub {
82             return unless defined $_[0];
83             ref $_[0] eq 'ARRAY'
84             ? @{ $_[0] }
85             : !ref $_[0]
86             ? $_[0]
87             : ();
88             },
89             '%' => sub {
90             return unless defined $_[0];
91             ref $_[0] eq 'HASH'
92             ? %{ $_[0] }
93             : ();
94             },
95             }->{ $type };
96             [$_ => sub { $fn->( $_[1]->stash->{$var} ) }];
97             };
98              
99             my $tc = subtype as ArrayRef[$glob_spec];
100             coerce $tc, from ArrayRef, via { [map { $glob_spec->coerce($_) } @{ $_ } ]};
101             coerce $tc, from Str, via { [ $glob_spec->coerce( $_ ) ] };
102              
103             has globals => (
104             is => 'ro',
105             isa => $tc,
106             coerce => 1,
107             builder => '_build_globals',
108             );
109             }
110              
111             sub BUILD {
112 13     13 0 2903 my ($self) = @_;
113 13         407 $self->interp;
114             }
115              
116 1     1   1672 sub _build_globals { [] }
117              
118 13     13   19549 sub _build_interp_class { 'HTML::Mason::Interp' }
119              
120             sub _build_interp {
121 13     13   22 my ($self) = @_;
122              
123 13         72 my %args = %{ $self->interp_args };
  13         384  
124 13 100       437 if ($self->has_encoding) {
125 2         5 my $old_func = delete $args{postprocess_text};
126             $args{postprocess_text} = sub {
127 4 50   4   2196 $old_func->($_[0]) if $old_func;
128 4         132 ${ $_[0] } = $self->encoding->decode(${ $_[0] });
  4         25  
  4         23  
129 2         11 };
130             }
131              
132 13   50     87 $args{allow_globals} ||= [];
133 13         16 unshift @{ $args{allow_globals}}, map { $_->[0] } @{ $self->globals };
  13         29  
  29         86  
  13         424  
134              
135 13   33     55 $args{in_package} ||= sprintf '%s::Commands', do {
136 13 50       61 if (my $meta = Class::MOP::class_of($self)) {
137 13         252 $meta->name;
138             } else {
139 0         0 ref $self;
140             }
141             } ;
142              
143             my $v = Data::Visitor::Callback->new(
144 10 50   10   10433 'Path::Class::Entity' => sub { blessed $_ ? $_->stringify : $_ },
145 13         449 );
146              
147 13         3103 return $self->interp_class->new( $v->visit(%args) );
148             }
149              
150              
151             sub render {
152 10     10 1 22 my ($self, $ctx, $comp, $args) = @_;
153 10         21 my $output = '';
154              
155 10         14 for (@{ $self->globals }) {
  10         584  
156 23         412 my ($decl, @values) = ($_->[0] => $_->[1]->($self, $ctx));
157 23 100       68 if (@values) {
158 20         544 $self->interp->set_global($decl, @values);
159             } else {
160             # HTML::Mason::Interp->set_global would crash on empty lists
161 3         22 $self->_unset_interp_global($decl);
162             }
163             }
164              
165             try {
166             $self->interp->make_request(
167             comp => $self->_fetch_comp($comp),
168 10 50   10   611 args => [$args ? %{ $args } : %{ $ctx->stash }],
  0         0  
  10         36  
169             out_method => \$output,
170             )->exec;
171             }
172             catch {
173 0     0   0 confess $_;
174 10         1492 };
175              
176 10         16020 return $output;
177             }
178              
179             sub process {
180 10     10 1 226076 my ($self, $ctx) = @_;
181              
182 10         65 my $comp = $self->_get_component($ctx);
183 10         180 my $output = $self->render($ctx, $comp);
184              
185 10         251 $ctx->response->body($output);
186             }
187              
188             sub _fetch_comp {
189 10     10   21 my ($self, $comp) = @_;
190 10         14 my $method;
191              
192 10 50 33     49 $comp = $comp->stringify
193             if blessed $comp && $comp->isa( 'Path::Class' );
194              
195 10 50       38 return $comp
196             if blessed $comp;
197              
198 10 50 33     33 ($comp, $method) = @{ $comp }
  0         0  
199             if ref $comp && ref $comp eq 'ARRAY';
200              
201 10 50       45 $comp = "/$comp"
202             unless $comp =~ m{^/};
203              
204 10         278 my $component = $self->interp->load($comp);
205 10 50       13915 confess "Can't find component for path $comp"
206             unless $component;
207              
208 10 50       29 $component = $component->methods($method)
209             if defined $method;
210              
211 10         33 return $component;
212             }
213              
214              
215             sub _get_component {
216 10     10   15 my ($self, $ctx) = @_;
217              
218 10         32 my $comp = $ctx->stash->{template};
219 10         756 my $extension = $self->template_extension;
220              
221 10 100       40 if (defined $comp) {
222 5 50 33     187 $comp .= $extension
223             if !ref $comp && $self->always_append_template_extension;
224              
225 5         12 return $comp;
226             }
227              
228 5         91 return $ctx->action->reverse . $extension;
229             }
230              
231             sub _unset_interp_global {
232 3     3   5 my ($self, $decl) = @_;
233 3         10 my ($prefix, $name) = split q//, $decl, 2;
234 3         75 my $package = $self->interp->compiler->in_package;
235 3         18 my $varname = sprintf "%s::%s", $package, $name;
236              
237 5     5   12799 no strict 'refs';
  5         9  
  5         665  
238 3 50       12 if ($prefix eq '$') { $$varname = undef }
  0 100       0  
239 1         6 elsif ($prefix eq '@') { @$varname = () }
240 2         9 else { %$varname = () }
241             }
242              
243             __PACKAGE__->meta->make_immutable;
244              
245             1;
246              
247             __END__
248              
249             =pod
250              
251             =encoding UTF-8
252              
253             =head1 NAME
254              
255             Catalyst::View::HTML::Mason - HTML::Mason rendering for Catalyst
256              
257             =head1 SYNOPSIS
258              
259             package MyApp::View::Mason;
260              
261             use Moose;
262             use namespace::autoclean;
263              
264             extends 'Catalyst::View::HTML::Mason';
265              
266             __PACKAGE__->config(
267             interp_args => {
268             comp_root => MyApp->path_to('root'),
269             },
270             );
271              
272             1;
273              
274             =head1 DESCRIPTION
275              
276             This module provides rendering of HTML::Mason templates for Catalyst
277             applications.
278              
279             It's basically a rewrite of L<Catalyst::View::Mason|Catalyst::View::Mason>,
280             which became increasingly hard to maintain over time, while keeping backward
281             compatibility.
282              
283             =head1 ATTRIBUTES
284              
285             =head2 interp
286              
287             The mason interpreter instance responsible for rendering templates.
288              
289             =head2 interp_class
290              
291             The class the C<interp> instance is constructed from. Defaults to
292             C<HTML::Mason::Interp>.
293              
294             =head2 interp_args
295              
296             Arguments to be passed to the construction of C<interp>. Defaults to an empty
297             hash reference.
298              
299             =head2 template_extension
300              
301             File extension to be appended to every component file. By default it's only
302             appended if no explicit component file has been provided in
303             C<< $ctx->stash->{template} >>.
304              
305             =head2 always_append_template_extension
306              
307             If this is set to a true value, C<template_extension> will also be appended to
308             component paths provided in C<< $ctx->stash->{template} >>.
309              
310             =head2 encoding
311              
312             Encode Mason output with the given encoding. Can be a string encoding
313             name (which will be resolved using Encode::find_encoding()), or an
314             Encode::Encoding object. See L<Encode::Supported> for a list of
315             encodings.
316              
317             B<NOTE> Starting in L<Catalyst> v5.90080 we encode text like body
318             responses as UTF8 automatically. In some cases templates that did
319             not declare an encoding previously will now need to. In general I
320             find setting this to 'UTF-8' is a forward looking approach.
321              
322             =head2 globals
323              
324             An array reference specifying globals to be made available in components. Empty
325             by default.
326              
327             Each global specification may be either a plain string containing a variable
328             name, or an array reference consisting of a variable name and a callback.
329              
330             When using the array-reference form, the provided callback will be used to
331             generate the value of the global for each request. The callback will be invoked
332             with the view instance as well as the current request context.
333              
334             When specifying plain strings, the value will be generated by looking up the
335             variable name minus the sigil in C<< $ctx->stash >>.
336              
337             Examples:
338              
339             globals => [ '$foo', '%bar' ],
340              
341             globals => '$baz',
342              
343             globals => [
344             ['$ctx', sub { $_[1] } ],
345             ['$current_user, sub { $_[1]->user } ],
346             ],
347              
348             Would export $foo and %bar to every Mason component as globals using
349             identically-named values in the stash, similar to:
350              
351             our $foo = $ctx->stash->{foo};
352             our %bar = %{ $ctx->stash->{bar} };
353              
354             =head1 METHODS
355              
356             =head2 render($ctx, $component, \%args)
357              
358             Renders the given component and returns its output.
359              
360             A hash of template variables may be provided in C<$args>. If C<$args> isn't
361             given, template variables will be taken from C<< $ctx->stash >>.
362              
363             =head1 A NOTE ABOUT DHANDLERS
364              
365             Note that this view does not support automatic dispatching to Mason
366             dhandlers. Dhandlers can still be used, but they must be referred to
367             explicitly like any other component.
368              
369             =for Pod::Coverage BUILD
370              
371             =head1 AUTHORS
372              
373             =over 4
374              
375             =item *
376              
377             Florian Ragwitz <rafl@debian.org>
378              
379             =item *
380              
381             Sebastian Willert <willert@cpan.org>
382              
383             =item *
384              
385             Robert Buels <rbuels@cpan.org>
386              
387             =back
388              
389             =head1 COPYRIGHT AND LICENSE
390              
391             This software is copyright (c) 2015 by Florian Ragwitz.
392              
393             This is free software; you can redistribute it and/or modify it under
394             the same terms as the Perl 5 programming language system itself.
395              
396             =cut