File Coverage

blib/lib/Catalyst/View/MojoTemplate.pm
Criterion Covered Total %
statement 15 140 10.7
branch 0 58 0.0
condition 0 23 0.0
subroutine 5 29 17.2
pod 3 14 21.4
total 23 264 8.7


line stmt bran cond sub pod time code
1             package Catalyst::View::MojoTemplate;
2              
3 1     1   2852 use Moo;
  1         14582  
  1         4  
4 1     1   2791 use Mojo::Template;
  1         260116  
  1         9  
5 1     1   65 use Mojo::ByteStream qw(b);
  1         2  
  1         1419  
6              
7             extends 'Catalyst::View';
8              
9             our $VERSION = 0.004;
10              
11             has app => (is=>'ro');
12             has auto_escape => (is=>'ro', required=>1, default=>1);
13             has append => (is=>'ro', required=>1, default=>'');
14             has prepend => (is=>'ro', required=>1, default=>'');
15             has capture_end => (is=>'ro', required=>1, default=>sub {'end'});
16             has capture_start => (is=>'ro', required=>1, default=>sub {'begin'});
17             has comment_mark => (is=>'ro', required=>1, default=>'#');
18             has encoding => (is=>'ro', required=>1, default=>'UTF-8');
19             has escape_mark => (is=>'ro', required=>1, default=>'=');
20             has expression_mark => (is=>'ro', required=>1, default=>'=');
21             has line_start => (is=>'ro', required=>1, default=>'%');
22             has replace_mark => (is=>'ro', required=>1, default=>'%');
23             has trim_mark => (is=>'ro', required=>1, default=>'%');
24             has tag_start=> (is=>'ro', required=>1, default=>'<%');
25             has tag_end => (is=>'ro', required=>1, default=>'%>');
26             has ['name', 'namespace'] => (is=>'rw');
27              
28             has template_extension => (is=>'ro', required=>1, default=>sub { '.ep' });
29              
30             has content_type => (is=>'ro', required=>1, default=>sub { 'text/html' });
31             has helpers => (is=>'ro', predicate=>'has_helpers');
32             has layout => (is=>'ro', predicate=>'has_layout');
33              
34             sub build_mojo_template {
35 0     0 0   my $self = shift;
36 0           my $prepend = 'my $c = _C;' . $self->prepend;
37 0           my %args = (
38             auto_escape => $self->auto_escape,
39             append => $self->append,
40             capture_end => $self->capture_end,
41             capture_start => $self->capture_start,
42             comment_mark => $self->comment_mark,
43             encoding => $self->encoding,
44             escape_mark => $self->escape_mark,
45             expression_mark => $self->expression_mark,
46             line_start => $self->line_start,
47             prepend => $prepend,
48             trim_mark => $self->trim_mark,
49             tag_start => $self->tag_start,
50             tag_end => $self->tag_end,
51             vars => 1,
52             );
53              
54 0           return Mojo::Template->new(%args);
55             }
56              
57              
58             has path_base => (
59             is=>'ro',
60             required=>1,
61             lazy=>1,
62             builder=>'_build_path_base');
63            
64             sub _build_path_base {
65 0     0     my $self = shift;
66 0           my $root = $self->app->path_to('root');
67 0 0         die "No directory '$root'" unless -e $root;
68              
69 0           return $root;
70             }
71              
72             sub COMPONENT {
73 0     0 1   my ($class, $app, $args) = @_;
74 0           $args = $class->merge_config_hashes($class->config, $args);
75 0           $args->{app} = $app;
76              
77 0           return $class->new($app, $args);
78             }
79              
80             sub ACCEPT_CONTEXT {
81 0     0 1   my ($self, $c, @args) = @_;
82             $c->stash->{'view.layout'} = $self->layout
83 0 0 0       if $self->has_layout && !exists($c->stash->{'view.layout'});
84              
85 0 0         if(@args) {
86 0 0         my %template_args = %{ pop(@args)||+{} };
  0            
87 0   0       my $template = shift @args || $self->find_template($c);
88 0           my %global_args = $self->template_vars($c);
89 0           my $output = $self->render($c, $template, +{%global_args, %template_args});
90 0           $self->set_response_from($c, $output);
91 0           return $self;
92             } else {
93 0           return $self;
94             }
95             }
96              
97             sub set_response_from {
98 0     0 0   my ($self, $c, $output) = @_;
99 0 0         $c->response->content_type($self->content_type) unless $c->response->content_type;
100 0 0         $c->response->body($output) unless $c->response->body;
101             }
102              
103             sub process {
104 0     0 1   my ($self, $c) = @_;
105 0           my $template = $self->find_template($c);
106 0           my %template_args = $self->template_vars($c);
107 0           my $output = $self->render($c, $template, \%template_args);
108 0           $self->set_response_from($c, $output);
109              
110 0           return 1;
111             }
112              
113             sub find_template {
114 0     0 0   my ($self, $c) = @_;
115             my $template = $c->stash->{template}
116 0   0       || $c->action . $self->template_extension;
117              
118 0 0         unless (defined $template) {
119 0 0         $c->log->debug('No template specified for rendering') if $c->debug;
120 0           return 0;
121             }
122              
123 0           return $template;
124             }
125              
126             sub render {
127 0     0 0   my ($self, $c, $template, $template_args) = @_;
128 0           my $output = $self->render_template($c, $template, $template_args);
129              
130 0 0         if(ref $output) {
131             # Its a Mojo::Exception;
132 0           $c->response->content_type('text/plain');
133 0           $c->response->body($output);
134 0           return $output;
135             }
136              
137 0           return $self->apply_layout($c, $output);
138             }
139              
140             sub apply_layout {
141 0     0 0   my ($self, $c, $output) = @_;
142 0 0         if(my $layout = $self->find_layout($c)) {
143 0 0         $c->log->debug(qq/Applying layout "$layout"/) if $c->debug;
144 0           $c->stash->{'view.content'}->{main} = b $output;
145 0           $output = $self->render($c, $layout, +{ $self->template_vars($c) });
146             }
147 0           return $output;
148             }
149              
150             sub find_layout {
151 0     0 0   my ($self, $c) = @_;
152 0 0         return exists $c->stash->{'view.layout'} ? delete $c->stash->{'view.layout'} : undef;
153             }
154              
155             sub render_template {
156 0     0 0   my ($self, $c, $template, $template_args) = @_;
157 0 0         $c->log->debug(qq/Rendering template "$template"/) if $c->debug;
158              
159 0   0       my $mojo_template = $self->{"__mojo_template_${template}"} ||= do {
160 0           my $mojo_template = $self->build_mojo_template;
161 0           $mojo_template->name($template);
162              
163 0           my $namespace_part = $template;
164 0           $namespace_part =~s/\//::/g;
165 0           $namespace_part =~s/\.ep$//;
166 0           $mojo_template->namespace( ref($self) .'::Sandbox::'. $namespace_part);
167              
168 0           my $template_full_path = $self->path_base->file($template);
169 0           my $template_contents = $template_full_path->slurp;
170 0 0         $c->log->debug(qq/Found template at path "$template_full_path"/) if $c->debug;
171              
172 0           my $output = $mojo_template->parse($template_contents);
173             };
174              
175 0           my $ns = $mojo_template->namespace;
176            
177 1     1   9 no strict 'refs';
  1         3  
  1         31  
178 1     1   6 no warnings 'redefine';
  1         2  
  1         1054  
179 0     0     local *{"${ns}::_C"} = sub { $c };
  0            
  0            
180              
181 0 0         unless($self->{"__mojo_helper_${ns}"}) {
182 0           $self->inject_helpers($c, $ns);
183 0           $self->{"__mojo_helper_${ns}"}++;
184             }
185              
186 0           return my $output = $mojo_template->process($template_args);
187             }
188              
189             sub inject_helpers {
190 0     0 0   my ($self, $c, $namespace) = @_;
191 0           my %helpers = $self->get_helpers;
192 0           foreach my $helper(keys %helpers) {
193 0           eval qq[
194             package $namespace;
195             sub $helper { \$self->get_helpers('$helper')->(\$self, _C, \@_) }
196 0 0         ]; die $@ if $@;
197             }
198             }
199              
200             sub template_vars {
201 0     0 0   my ($self, $c) = @_;
202             my %template_args = (
203             base => $c->req->base,
204             name => $c->config->{name} || '',
205             self => $self,
206 0 0 0       %{$c->stash||+{}},
  0            
207             );
208              
209 0           return %template_args;
210             }
211              
212             sub default_helpers {
213 0     0 0   my $self = shift;
214             return (
215             layout => sub {
216 0     0     my ($self, $c, $template, %args) = @_;
217 0           $c->stash('view.layout' => $template);
218 0 0         $c->stash(%args) if %args;
219             },
220             wrapper => sub {
221 0     0     my ($self, $c, $template, @args) = @_;
222 0           $c->stash->{'view.content'}->{main} = pop @args;
223 0           my %args = @args;
224 0           my %global_args = $self->template_vars($c);
225 0           return b($self->render_template($c, $template, +{ %global_args, %args }));
226             },
227             include => sub {
228 0     0     my ($self, $c, $template, %args) = @_;
229 0           my %global_args = $self->template_vars($c);
230 0           return b($self->render_template($c, $template, +{ %global_args, %args }));
231             },
232             content => sub {
233 0     0     my ($self, $c, $name, $proto) = @_;
234              
235 0   0       $name ||= 'main';
236 0 0 0       $c->stash->{'view.content'}->{$name} = _block($proto) if $proto && !exists($c->stash->{'view.content'}->{$name});
237              
238 0           my $value = $c->stash->{'view.content'}->{$name};
239 0 0         $value = '' unless defined($value);
240              
241 0           return b $value;
242             },
243             content_with => sub {
244 0     0     my ($self, $c, $name, $proto) = @_;
245              
246 0   0       $name ||= 'main';
247 0 0         $c->stash->{'view.content'}->{$name} = _block($proto) if $proto;
248              
249 0           my $value = $c->stash->{'view.content'}->{$name};
250 0 0         $value = '' unless defined($value);
251              
252 0           return b $value;
253             },
254             content_for => sub {
255 0     0     my ($self, $c, $name, $proto) = @_;
256              
257 0   0       $name ||= 'main';
258 0 0         $c->stash->{'view.content'}->{$name} .= _block($proto) if $proto;
259              
260 0           my $value = $c->stash->{'view.content'}->{$name};
261 0 0         $value = '' unless defined($value);
262              
263 0           return b $value;
264             },
265             stash => sub {
266 0     0     my ($self, $c, $name, $proto) = @_;
267              
268 0 0         $c->stash->{$name} = _$proto if $proto;
269              
270 0           my $value = $c->stash->{$name};
271 0 0         $value = '' unless defined($value);
272              
273 0           return b $value;
274             },
275              
276 0           );
277             }
278              
279 0 0   0     sub _block { ref $_[0] eq 'CODE' ? $_[0]() : $_[0] }
280              
281             sub get_helpers {
282 0     0 0   my ($self, $helper) = @_;
283 0 0         my %helpers = ($self->default_helpers, %{ $self->helpers || +{} });
  0            
284              
285 0 0         return $helpers{$helper} if defined $helper;
286 0           return %helpers;
287             }
288              
289             1;
290              
291             =head1 NAME
292              
293             Catalyst::View::MojoTemplate - Use Mojolicious Templates for your Catalyst View
294              
295             =head1 SYNOPSIS
296              
297             package Example::View::HTML;
298              
299             use Moose;
300             extends 'Catalyst::View::MojoTemplate';
301              
302             __PACKAGE__->config(helpers => +{
303             now => sub {
304             my ($self, $c, @args) = @_;
305             return localtime;
306             },
307             });
308              
309             __PACKAGE__->meta->make_immutable;
310              
311             Then called from a controller:
312              
313             package Example::Controller::Root;
314              
315             use Moose;
316             use MooseX::MethodAttributes;
317              
318             extends 'Catalyst::Controller';
319              
320             sub root :Chained(/) PathPart('') CaptureArgs(0) { }
321              
322             sub home :Chained(root) PathPart('') Args(0) {
323             my ($self, $c) = @_;
324             $c->stash(status => $c->model('Status'));
325             }
326              
327             sub profile :Chained(root) PathPart(profile) Args(0) {
328             my ($self, $c) = @_;
329             $c->view('HTML' => 'profile.ep', +{
330             me => $c->user,
331             });
332             }
333              
334             sub end : ActionClass('RenderView') {}
335              
336             __PACKAGE__->config(namespace=>'');
337             __PACKAGE__->meta->make_immutable;
338              
339             =head1 DESCRIPTION
340              
341             Use L<Mojo::Template> as your L<Catalyst> view. While ths might strike some as
342             odd, if you are using both L<Catalyst> and L<Mojolicious> you might like the option to
343             share the template code and expertise. You might also just want to use a Perlish
344             template system rather than a dedicated mini language (such as L<Xslate>) since you
345             already know Perl and don't have the time or desire to become an expert in another
346             system.
347              
348             This works just like many other L<Catalyst> views. It will load and render a template
349             based on either the current action private name or a stash variable called C<template>.
350             It will use the stash to populate variables in the template. It also offers an alternative
351             interface that lets you set a template in the actual call to the view, and pass variables.
352              
353             By default we look for templates in C<$APPHOME/root> which is the standard default location
354             for L<Catalyst> templates.
355              
356             Also like a lot of other template systems you can define helper methods which are injected
357             into your template and can take parameters (including text blocks).
358              
359             The intention here is to try and make this as similar to how L<Mojo::Template> is used
360             in L<Mojolicious> so that people that need to work in both frameworks could in theory use
361             this view in L<Catalyst> and be able to switch between the two with less trouble (at least
362             for doing view development). To that end we've added some default helpers that hopefully
363             work the same way as they do in L<Mojolicious>. These are helpers for template layouts
364             and includes as well as for sharing data between them. We've also added a 'wrapper'
365             helper because the author has found that feature of Template::Toolkit (L<Template>) to be so
366             useful he would have a hard time living without it. We did not include the L<Mojolicious>
367             tag helpers but there's no reason those could not be added as an add on role at a later
368             date should people take an interest in this thing.
369              
370             There's an example of sorts in the C<example> directory of the module distribution. You can
371             start the example server with the following command:
372              
373             perl -Ilib -I example/lib/ example/lib/Example/Server.pm
374              
375             B<NOTE> Warning, this is an early access module and I reserve the right to make breaking
376             changes if it turns out I totally confused how L<Mojolicious> works. There's actually
377             not a ton of code here since its just a thin wrapper over L<Mojo::Template> so you should
378             be confortable looking that over and coping if there's issues.
379              
380             =head1 CONFIGURATION
381              
382             This view defines the following configuration attributes. For the most part these
383             are just pass thru to the underlying L<Mojo::Template>. You would do well to review
384             those docs if you are not familiar.
385              
386             =head2 auto_escape
387              
388             =head2 append
389              
390             =head2 prepend
391              
392             =head2 capture_start
393              
394             =head2 capture_end
395              
396             =head2 encoding
397              
398             =head2 comment_mark
399              
400             =head2 escape_mark
401              
402             =head2 expression_mark
403              
404             =head2 line_start
405              
406             =head2 replace_mark
407              
408             These are just pass thru to L<Mojo::Template>. See that for details
409              
410             =head2 content_type
411              
412             The HTTP content-type that is set in the response unless it is already set.
413              
414             =head2 helpers
415              
416             A hashref of helper functions. For example:
417              
418             __PACKAGE__->config(helpers=>+{
419             now => sub {
420             my ($self, $c, @args) = @_;
421             return localtime;
422             },
423             );
424              
425             All arguments are passed from the template. If you are building a block
426             helper then the last argument will be a coderef to the enclosed block. You
427             may wish to view the source code around the default helpers for more examples of
428             this.
429              
430             =head2 layout
431              
432             Set a default layout which will be used if none are defined. Optional.
433              
434             =head1 HELPERS
435              
436             The following is a list of the default helpers.
437              
438             =head2 layout
439              
440             % layout "layout.ep", title => "Hello";
441             <h1>The Awesome new Content</h1>
442             <p>You are doomed to discover you can never recover from the narcoleptic
443             country in which you once stood, where the fires alway burning but there's
444             never enough wood</p>
445              
446             C<layout> sets a global template wrapper around your content. Arguments passed
447             get merged into the stash and are available to the layout. The output of your
448             template is placed into the 'main' content block. See L<Mojolicious::Plugin::DefaultHelpers/layout>
449             for more.
450              
451             =head2 include
452              
453             See L<Mojolicious::Plugin::DefaultHelpers/include>
454              
455             =head2 content
456              
457             See L<Mojolicious::Plugin::DefaultHelpers/content>
458              
459             =head2 wrapper
460              
461             Similar to the C<layout> helper, the C<wrapper> helper wraps the contained content
462             inside a another template. However unlike C<layout> you can have more than one
463             C<wrapper> in your template. Example:
464              
465             %= wrapper "wrapper.ep", header => "The Story Begins...", begin
466             <p>
467             The story begins like many others; something interesting happend to someone
468             while sone other sort of interesting thing was happening all over. And then
469             there wre monkeys. Monkeys are great, you ever get stuck writing a story I
470             really recommend adding monkeys since they help the more boring story.
471             </p>
472             %end
473              
474             This works similar to the WRAPPER directive in Template::Toolkit, if you are familiar
475             with that system.
476              
477             =head1 AUTHOR
478            
479             jnap - John Napiorkowski (cpan:JJNAPIORK) L<email:jjnapiork@cpan.org>
480             With tremendous thanks to SRI and the Mojolicious team!
481              
482             =head1 SEE ALSO
483            
484             L<Catalyst>, L<Catalyst::View>, L<Mojolicious>
485            
486             =head1 COPYRIGHT & LICENSE
487            
488             Copyright 2020, John Napiorkowski L<email:jjnapiork@cpan.org>
489            
490             This library is free software; you can redistribute it and/or modify it under
491             the same terms as Perl itself.
492              
493             =cut