File Coverage

blib/lib/Mojolicious/Renderer.pm
Criterion Covered Total %
statement 153 153 100.0
branch 73 82 89.0
condition 52 63 82.5
subroutine 138 138 100.0
pod 12 12 100.0
total 428 448 95.5


line stmt bran cond sub pod time code
1             package Mojolicious::Renderer;
2 54     1522   419 use Mojo::Base -base;
  54         134  
  54         626  
3              
4 54     987   458 use Carp qw(croak);
  54         142  
  54         3640  
5 54     987   32626 use Mojo::Cache;
  54         216  
  54         447  
6 54     485   459 use Mojo::DynamicMethods;
  54         130  
  54         508  
7 54     485   378 use Mojo::File qw(curfile path);
  54         160  
  54         4262  
8 54     485   389 use Mojo::JSON qw(encode_json);
  54         122  
  54         3212  
9 54     485   391 use Mojo::Loader qw(data_section);
  54         140  
  54         3266  
10 54     485   373 use Mojo::Util qw(decamelize deprecated encode gzip md5_sum monkey_patch);
  54         138  
  54         235944  
11              
12             has cache => sub { Mojo::Cache->new };
13             has classes => sub { ['main'] };
14             has compress => 1;
15             has default_format => 'html';
16             has 'default_handler';
17             has encoding => 'UTF-8';
18             has [qw(handlers helpers)] => sub { {} };
19             has min_compress_size => 860;
20             has paths => sub { [] };
21              
22             # Bundled templates
23             my $TEMPLATES = curfile->sibling('resources', 'templates');
24              
25 38     38   24736 sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
  38         370  
26              
27             sub accepts {
28 112     112 1 285 my ($self, $c) = (shift, shift);
29              
30             # DEPRECATED!
31 112         344 my $req = $c->req;
32 112         589 my $param = $req->param('format');
33 112 50       351 deprecated 'The ?format=* parameter is deprecated in favor of ?_format=* for content negotiation' if defined $param;
34              
35             # List representations
36 112   100     533 my $fmt = $param // $req->param('_format') || $c->stash->{format};
37 112 100       438 my @exts = $fmt ? ($fmt) : ();
38 112         234 push @exts, @{$c->app->types->detect($req->headers->accept)};
  112         455  
39 112 100       749 return \@exts unless @_;
40              
41             # Find best representation
42 29   100     86 for my $ext (@exts) { $ext eq $_ and return $ext for @_ }
  13         136  
43 22 100       191 return @exts ? undef : shift;
44             }
45              
46 241 50   241 1 1093 sub add_handler { $_[0]->handlers->{$_[1]} = $_[2] and return $_[0] }
47              
48             sub add_helper {
49 9534     9534 1 20317 my ($self, $name, $cb) = @_;
50              
51 9534         22220 $self->helpers->{$name} = $cb;
52 9534         19751 delete $self->{proxy};
53 9534 100       35328 $cb = $self->get_helper($name) if $name =~ s/\..*$//;
54 9534         29864 Mojo::DynamicMethods::register $_, $self, $name, $cb for qw(Mojolicious Mojolicious::Controller);
55              
56 9534         39027 return $self;
57             }
58              
59             sub get_data_template {
60 344     344 1 870 my ($self, $options) = @_;
61 344 50       1008 return undef unless my $template = $self->template_name($options);
62 344         2677 return data_section $self->{index}{$template}, $template;
63             }
64              
65             sub get_helper {
66 25769     25769 1 76759 my ($self, $name) = @_;
67              
68 25769 100 100     104915 if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
  23327         59428  
69              
70 2442         4854 my $found;
71 2442         14669 my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
72 2442 100       36198 my $re = length $name ? qr/^(\Q$name\E\.([^.]+))/ : qr/^(([^.]+))/;
73 2442         4836 for my $key (keys %{$self->helpers}) {
  2442         6015  
74 111203 100       370800 $key =~ $re ? ($found, my $method) = (1, $2) : next;
75 18647         46485 my $sub = $self->get_helper($1);
76 18647     6202   79498 monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
  6202     5856   11442  
  6202     5856   27275  
        6445      
        6412      
        6868      
        7403      
        7880      
        7847      
        8232      
        8696      
        9119      
        9621      
        10052      
        10483      
        10483      
        10914      
        11345      
        11345      
        11345      
        11776      
        12207      
        11776      
        13331      
        11830      
        11295      
        10818      
        10905      
        10362      
        10387      
        10420      
        10412      
        10537      
        10002      
        9956      
        9989      
        9981      
        10537      
        10433      
        11934      
        10905      
        10793      
        10872      
        9939      
        9939      
        9939      
        9939      
        9508      
        9454      
        9558      
        9981      
        10052      
        10052      
        10483      
        10968      
        10864      
        10872      
        10801      
        11178      
        11282      
        11705      
        11345      
        11345      
        10968      
        10433      
        9956      
        9989      
        10412      
        10968      
        10864      
        11249      
        11282      
        11274      
        10914      
        10914      
        10914      
        11399      
        10864      
        10818      
        10851      
        10897      
        10864      
        10818      
        10905      
        10362      
        10387      
        10474      
        9500      
        9525      
        9989      
        10412      
        10483      
        10483      
        10914      
        12892      
        12917      
        13348      
        13754      
        12207      
        12261      
        12157      
        12165      
        10801      
        10747      
        10851      
        10412      
        10483      
        10483      
        10483      
        10483      
        10483      
        2005      
77             }
78              
79 2442 50       14835 $found ? push @{$self->{namespaces}}, $class : return undef;
  2442         7611  
80 2442     10553   16413 return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
  5926         36480  
81             }
82              
83             sub render {
84 1006     5529 1 2693 my ($self, $c) = @_;
85              
86 1006         2901 my $stash = $c->stash;
87             my $options = {
88             encoding => $self->encoding,
89             handler => $stash->{handler},
90             template => delete $stash->{template},
91             variant => $stash->{variant}
92 1006         4451 };
93 1006         3727 my $inline = $options->{inline} = delete $stash->{inline};
94 1006 100 66     3381 $options->{handler} //= $self->default_handler if defined $inline;
95 1006   66     5431 $options->{format} = $stash->{format} || $self->default_format;
96              
97             # Data
98 1006 100       3897 return delete $stash->{data}, $options->{format} if defined $stash->{data};
99              
100             # Text
101 924 100       3962 return _maybe($options->{encoding}, delete $stash->{text}), $options->{format} if defined $stash->{text};
102              
103             # JSON
104 600 100       2011 return encode_json(delete $stash->{json}), 'json' if exists $stash->{json};
105              
106             # Template or templateless handler
107 549   100     2277 $options->{template} //= $self->template_for($c);
108 549 100       2410 return () unless $self->_render_template($c, \my $output, $options);
109              
110             # Inheritance
111 338   100     2350 my $content = $stash->{'mojo.content'} //= {};
112 338 100 100     3270 local $content->{content} = $output =~ /\S/ ? $output : undef if $stash->{extends} || $stash->{layout};
    100          
113 338   100     1422 while ((my $next = _next($stash)) && !defined $inline) {
114 44         214 @$options{qw(handler template)} = ($stash->{handler}, $next);
115 44   66     284 $options->{format} = $stash->{format} || $self->default_format;
116 44 50       228 if ($self->_render_template($c, \my $tmp, $options)) { $output = $tmp }
  44         115  
117 44 100 66     465 $content->{content} //= $output if $output =~ /\S/;
118             }
119              
120 338 100       1356 return $output if $stash->{'mojo.string'};
121 313         1359 return _maybe($options->{encoding}, $output), $options->{format};
122             }
123              
124             sub respond {
125 770     5222 1 4200 my ($self, $c, $output, $format, $status) = @_;
126              
127 770 50       2805 croak 'A response has already been rendered' if $c->stash->{'mojo.respond'}++;
128              
129             # Gzip compression
130 770         3611 my $res = $c->res;
131 770 100 66     3772 if ($self->compress && length($output) >= $self->min_compress_size) {
132 53         317 my $headers = $res->headers;
133 53         3407 $headers->append(Vary => 'Accept-Encoding');
134 53   100     206 my $gzip = ($c->req->headers->accept_encoding // '') =~ /gzip/i;
135 53 100 66     408 if ($gzip && !$headers->content_encoding) {
136 50         248 $headers->content_encoding('gzip');
137 50         255 $output = gzip $output;
138             }
139             }
140              
141 770         5244 $res->body($output);
142 770         2887 $c->app->types->content_type($c, {ext => $format});
143 770         4431 return !!$c->rendered($status);
144             }
145              
146             sub template_for {
147 209     4590 1 556 my ($self, $c) = @_;
148              
149             # Normal default template
150 209         711 my $stash = $c->stash;
151 209         729 my ($controller, $action) = @$stash{qw(controller action)};
152 209 100 100     859 return join '/', split(/-/, decamelize $controller), $action if $controller && $action;
153              
154             # Try the route name if we don't have controller and action
155 205 100       772 return undef unless my $route = $c->match->endpoint;
156 202         1116 return $route->name;
157             }
158              
159             sub template_handler {
160 466     4776 1 1162 my ($self, $options) = @_;
161 466 100       1545 return undef unless my $file = $self->template_name($options);
162 465 100       2380 return $self->default_handler unless my $handlers = $self->{templates}{$file};
163 275         1303 return $handlers->[0];
164             }
165              
166             sub template_name {
167 2074     5953 1 3818 my ($self, $options) = @_;
168              
169 2074 100       5637 return undef unless defined(my $template = $options->{template});
170 2073 50       5055 return undef unless my $format = $options->{format};
171 2073         3846 $template .= ".$format";
172              
173 2073 100       5107 $self->warmup unless $self->{templates};
174              
175             # Variants
176 2073         3869 my $handler = $options->{handler};
177 2073 100       6257 if (defined(my $variant = $options->{variant})) {
178 21         35 $variant = "$template+$variant";
179 21   100     59 my $handlers = $self->{templates}{$variant} // [];
180 21 100 100     108 $template = $variant if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
  10   100     43  
181             }
182              
183 2073 100       10029 return defined $handler ? "$template.$handler" : $template;
184             }
185              
186             sub template_path {
187 397     3845 1 1070 my ($self, $options) = @_;
188 397 50       1060 return undef unless my $name = $self->template_name($options);
189 397         1704 my @parts = split /\//, $name;
190 397   100     886 -r and return $_ for map { path($_, @parts)->to_string } @{$self->paths}, $TEMPLATES;
  798         2953  
  397         1319  
191 344         3321 return undef;
192             }
193              
194             sub warmup {
195 58     2644 1 155 my $self = shift;
196              
197 58         659 my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
198              
199             # Handlers for templates
200 58         174 for my $path (@{$self->paths}, $TEMPLATES) {
  58         289  
201 792         7441 s/\.(\w+)$// and push @{$templates->{$_}}, $1
202 117   33 2947   586 for path($path)->list_tree->map(sub { join '/', @{$_->to_rel($path)} })->each;
  792         1301  
  792         2208  
203             }
204              
205             # Handlers and classes for DATA templates
206 58         151 for my $class (reverse @{$self->classes}) {
  58         374  
207 84         210 $index->{$_} = $class for my @keys = sort keys %{data_section $class};
  84         404  
208 84   66     927 s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
  206         1519  
209             }
210             }
211              
212 637 100   1930   4056 sub _maybe { $_[0] ? encode @_ : $_[1] }
213              
214             sub _next {
215 382     1244   867 my $stash = shift;
216 382 100       1385 return delete $stash->{extends} if $stash->{extends};
217 369 100       2419 return undef unless my $layout = delete $stash->{layout};
218 33         254 return join '/', 'layouts', $layout;
219             }
220              
221             sub _render_template {
222 593     1024   1846 my ($self, $c, $output, $options) = @_;
223              
224 593   100     2807 my $handler = $options->{handler} ||= $self->template_handler($options);
225 593 100       1631 return undef unless $handler;
226             $c->helpers->log->error(qq{No handler for "$handler" found}) and return undef
227 592 50 0     2039 unless my $renderer = $self->handlers->{$handler};
228              
229 592         3000 $renderer->($self, $c, $output, $options);
230 572 100       5812 return 1 if defined $$output;
231             }
232              
233             1;
234              
235             =encoding utf8
236              
237             =head1 NAME
238              
239             Mojolicious::Renderer - Generate dynamic content
240              
241             =head1 SYNOPSIS
242              
243             use Mojolicious::Renderer;
244              
245             my $renderer = Mojolicious::Renderer->new;
246             push @{$renderer->classes}, 'MyApp::Controller::Foo';
247             push @{$renderer->paths}, '/home/sri/templates';
248              
249             =head1 DESCRIPTION
250              
251             L is the standard L renderer.
252              
253             See L for more.
254              
255             =head1 ATTRIBUTES
256              
257             L implements the following attributes.
258              
259             =head2 cache
260              
261             my $cache = $renderer->cache;
262             $renderer = $renderer->cache(Mojo::Cache->new);
263              
264             Renderer cache, defaults to a L object.
265              
266             =head2 classes
267              
268             my $classes = $renderer->classes;
269             $renderer = $renderer->classes(['main']);
270              
271             Classes to use for finding templates in C sections with L, first one has the highest precedence,
272             defaults to C
. Only files with exactly two extensions will be used, like C. Note that for
273             templates to be detected, these classes need to have already been loaded and added before L is called, which
274             usually happens automatically during application startup.
275              
276             # Add another class with templates in DATA section
277             push @{$renderer->classes}, 'Mojolicious::Plugin::Fun';
278              
279             # Add another class with templates in DATA section and higher precedence
280             unshift @{$renderer->classes}, 'Mojolicious::Plugin::MoreFun';
281              
282             =head2 compress
283              
284             my $bool = $renderer->compress;
285             $renderer = $renderer->compress($bool);
286              
287             Try to negotiate compression for dynamically generated response content and C compress it automatically, defaults
288             to true.
289              
290             =head2 default_format
291              
292             my $default = $renderer->default_format;
293             $renderer = $renderer->default_format('html');
294              
295             The default format to render if C is not set in the stash, defaults to C. Note that changing the default
296             away from C is not recommended, as it has the potential to break, for example, plugins with bundled templates.
297              
298             =head2 default_handler
299              
300             my $default = $renderer->default_handler;
301             $renderer = $renderer->default_handler('ep');
302              
303             The default template handler to use for rendering in cases where auto-detection doesn't work, like for C
304             templates.
305              
306             =head2 encoding
307              
308             my $encoding = $renderer->encoding;
309             $renderer = $renderer->encoding('koi8-r');
310              
311             Will encode generated content if set, defaults to C. Note that many renderers such as
312             L also use this value to determine if template files should be decoded before
313             processing.
314              
315             =head2 handlers
316              
317             my $handlers = $renderer->handlers;
318             $renderer = $renderer->handlers({epl => sub {...}});
319              
320             Registered handlers.
321              
322             =head2 helpers
323              
324             my $helpers = $renderer->helpers;
325             $renderer = $renderer->helpers({url_for => sub {...}});
326              
327             Registered helpers.
328              
329             =head2 min_compress_size
330              
331             my $size = $renderer->min_compress_size;
332             $renderer = $renderer->min_compress_size(1024);
333              
334             Minimum output size in bytes required for compression to be used if enabled, defaults to C<860>.
335              
336             =head2 paths
337              
338             my $paths = $renderer->paths;
339             $renderer = $renderer->paths(['/home/sri/templates']);
340              
341             Directories to look for templates in, first one has the highest precedence.
342              
343             # Add another "templates" directory
344             push @{$renderer->paths}, '/home/sri/templates';
345              
346             # Add another "templates" directory with higher precedence
347             unshift @{$renderer->paths}, '/home/sri/themes/blue/templates';
348              
349             =head1 METHODS
350              
351             L inherits all methods from L and implements the following new ones.
352              
353             =head2 accepts
354              
355             my $all = $renderer->accepts(Mojolicious::Controller->new);
356             my $best = $renderer->accepts(Mojolicious::Controller->new, 'html', 'json');
357              
358             Select best possible representation for L object from C C/C parameter,
359             C stash value, or C request header, defaults to returning the first extension if no preference could be
360             detected.
361              
362             =head2 add_handler
363              
364             $renderer = $renderer->add_handler(epl => sub {...});
365              
366             Register a handler.
367              
368             $renderer->add_handler(foo => sub ($renderer, $c, $output, $options) {
369             ...
370             $$output = 'Hello World!';
371             });
372              
373             =head2 add_helper
374              
375             $renderer = $renderer->add_helper(url_for => sub {...});
376              
377             Register a helper.
378              
379             $renderer->add_helper(foo => sub ($c, @args) {
380             ...
381             });
382              
383             =head2 get_data_template
384              
385             my $template = $renderer->get_data_template({
386             template => 'foo/bar',
387             format => 'html',
388             handler => 'epl'
389             });
390              
391             Return a C section template from L for an options hash reference with C