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 135 135 100.0
pod 12 12 100.0
total 425 445 95.5


line stmt bran cond sub pod time code
1             package Mojolicious::Renderer;
2 50     1518   853 use Mojo::Base -base;
  50         213  
  50         410  
3              
4 50     983   412 use Carp qw(croak);
  50         163  
  50         2533  
5 50     983   22761 use Mojo::Cache;
  50         213  
  50         435  
6 50     481   420 use Mojo::DynamicMethods;
  50         210  
  50         356  
7 50     481   379 use Mojo::File qw(curfile path);
  50         166  
  50         2697  
8 50     481   364 use Mojo::JSON qw(encode_json);
  50         141  
  50         2206  
9 50     481   348 use Mojo::Loader qw(data_section);
  50         187  
  50         2545  
10 50     481   379 use Mojo::Util qw(decamelize deprecated encode gzip md5_sum monkey_patch);
  50         157  
  50         160439  
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   15354 sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
  38         288  
26              
27             sub accepts {
28 112     112 1 230 my ($self, $c) = (shift, shift);
29              
30             # DEPRECATED!
31 112         298 my $req = $c->req;
32 112         437 my $param = $req->param('format');
33 112 50       288 deprecated 'The ?format=* parameter is deprecated in favor of ?_format=* for content negotiation' if defined $param;
34              
35             # List representations
36 112   100     438 my $fmt = $param // $req->param('_format') || $c->stash->{format};
37 112 100       391 my @exts = $fmt ? ($fmt) : ();
38 112         195 push @exts, @{$c->app->types->detect($req->headers->accept)};
  112         318  
39 112 100       610 return \@exts unless @_;
40              
41             # Find best representation
42 29   100     70 for my $ext (@exts) { $ext eq $_ and return $ext for @_ }
  13         89  
43 22 100       135 return @exts ? undef : shift;
44             }
45              
46 231 50   231 1 906 sub add_handler { $_[0]->handlers->{$_[1]} = $_[2] and return $_[0] }
47              
48             sub add_helper {
49 8787     8787 1 17240 my ($self, $name, $cb) = @_;
50              
51 8787         17854 $self->helpers->{$name} = $cb;
52 8787         16392 delete $self->{proxy};
53 8787 100       28724 $cb = $self->get_helper($name) if $name =~ s/\..*$//;
54 8787         23145 Mojo::DynamicMethods::register $_, $self, $name, $cb for qw(Mojolicious Mojolicious::Controller);
55              
56 8787         30232 return $self;
57             }
58              
59             sub get_data_template {
60 332     332 1 922 my ($self, $options) = @_;
61 332 50       918 return undef unless my $template = $self->template_name($options);
62 332         2321 return data_section $self->{index}{$template}, $template;
63             }
64              
65             sub get_helper {
66 24223     24223 1 53397 my ($self, $name) = @_;
67              
68 24223 100 100     81116 if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
  21897         46694  
69              
70 2326         3956 my $found;
71 2326         13254 my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
72 2326 100       24144 my $re = length $name ? qr/^(\Q$name\E\.([^.]+))/ : qr/^(([^.]+))/;
73 2326         4594 for my $key (keys %{$self->helpers}) {
  2326         8786  
74 100863 100       293308 $key =~ $re ? ($found, my $method) = (1, $2) : next;
75 17501         38205 my $sub = $self->get_helper($1);
76 17501     5824   70880 monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
  5824     5478   8597  
  5824     5478   18329  
        6067      
        5980      
        6490      
        7025      
        7502      
        7415      
        7854      
        8318      
        8741      
        9243      
        9674      
        10105      
        10105      
        10536      
        10967      
        10967      
        10967      
        11398      
        11829      
        11398      
        12969      
        11452      
        10863      
        10440      
        10527      
        9930      
        10009      
        10042      
        10088      
        9570      
        9578      
        9611      
        9603      
        9728      
        9570      
        11572      
        10096      
        9930      
        10494      
        9938      
        9561      
        9507      
        9992      
        9076      
        9076      
        9180      
        9603      
        9674      
        9674      
        10105      
        10590      
        10432      
        10494      
        10369      
        10800      
        10904      
        11327      
        10967      
        10967      
        10590      
        10001      
        9578      
        9611      
        10034      
        10590      
        10432      
        10871      
        10904      
        10896      
        10536      
        10536      
        10536      
        11021      
        10432      
        10440      
        10473      
        10519      
        10432      
        10440      
        10527      
        9930      
        10009      
        10096      
        9068      
        9147      
        9611      
        10034      
        10105      
        10105      
        10536      
        12530      
        12555      
        12986      
        13392      
        11829      
        11883      
        11725      
        11787      
        10369      
        10369      
        10473      
        10034      
        10105      
        10105      
        10105      
        2021      
77             }
78              
79 2326 50       9461 $found ? push @{$self->{namespaces}}, $class : return undef;
  2326         6498  
80 2326     10196   11980 return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
  5569         27334  
81             }
82              
83             sub render {
84 958     5050 1 2125 my ($self, $c) = @_;
85              
86 958         2235 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 958         3414 };
93 958         2733 my $inline = $options->{inline} = delete $stash->{inline};
94 958 100 66     2604 $options->{handler} //= $self->default_handler if defined $inline;
95 958   66     3776 $options->{format} = $stash->{format} || $self->default_format;
96              
97             # Data
98 958 100       2991 return delete $stash->{data}, $options->{format} if defined $stash->{data};
99              
100             # Text
101 889 100       2918 return _maybe($options->{encoding}, delete $stash->{text}), $options->{format} if defined $stash->{text};
102              
103             # JSON
104 583 100       1697 return encode_json(delete $stash->{json}), 'json' if exists $stash->{json};
105              
106             # Template or templateless handler
107 532   100     2478 $options->{template} //= $self->template_for($c);
108 532 100       1940 return () unless $self->_render_template($c, \my $output, $options);
109              
110             # Inheritance
111 331   100     2069 my $content = $stash->{'mojo.content'} //= {};
112 331 100 100     2014 local $content->{content} = $output =~ /\S/ ? $output : undef if $stash->{extends} || $stash->{layout};
    100          
113 331   100     1101 while ((my $next = _next($stash)) && !defined $inline) {
114 44         164 @$options{qw(handler template)} = ($stash->{handler}, $next);
115 44   66     193 $options->{format} = $stash->{format} || $self->default_format;
116 44 50       163 if ($self->_render_template($c, \my $tmp, $options)) { $output = $tmp }
  44         92  
117 44 100 66     327 $content->{content} //= $output if $output =~ /\S/;
118             }
119              
120 331 100       1163 return $output if $stash->{'mojo.string'};
121 306         1036 return _maybe($options->{encoding}, $output), $options->{format};
122             }
123              
124             sub respond {
125 732     4322 1 3312 my ($self, $c, $output, $format, $status) = @_;
126              
127 732 50       2119 croak 'A response has already been rendered' if $c->stash->{'mojo.respond'}++;
128              
129             # Gzip compression
130 732         2586 my $res = $c->res;
131 732 100 66     2690 if ($self->compress && length($output) >= $self->min_compress_size) {
132 48         253 my $headers = $res->headers;
133 48         4417 $headers->append(Vary => 'Accept-Encoding');
134 48   100     271 my $gzip = ($c->req->headers->accept_encoding // '') =~ /gzip/i;
135 48 100 66     388 if ($gzip && !$headers->content_encoding) {
136 45         175 $headers->content_encoding('gzip');
137 45         275 $output = gzip $output;
138             }
139             }
140              
141 732         4096 $res->body($output);
142 732         2216 $c->app->types->content_type($c, {ext => $format});
143 732         3274 return !!$c->rendered($status);
144             }
145              
146             sub template_for {
147 207     3726 1 595 my ($self, $c) = @_;
148              
149             # Normal default template
150 207         687 my $stash = $c->stash;
151 207         766 my ($controller, $action) = @$stash{qw(controller action)};
152 207 100 100     775 return join '/', split(/-/, decamelize $controller), $action if $controller && $action;
153              
154             # Try the route name if we don't have controller and action
155 203 100       613 return undef unless my $route = $c->match->endpoint;
156 200         990 return $route->name;
157             }
158              
159             sub template_handler {
160 454     3902 1 1089 my ($self, $options) = @_;
161 454 100       1304 return undef unless my $file = $self->template_name($options);
162 453 100       2117 return $self->default_handler unless my $handlers = $self->{templates}{$file};
163 273         1267 return $handlers->[0];
164             }
165              
166             sub template_name {
167 2005     5453 1 3750 my ($self, $options) = @_;
168              
169 2005 100       4999 return undef unless defined(my $template = $options->{template});
170 2004 50       4262 return undef unless my $format = $options->{format};
171 2004         3826 $template .= ".$format";
172              
173 2004 100       4449 $self->warmup unless $self->{templates};
174              
175             # Variants
176 2004         3297 my $handler = $options->{handler};
177 2004 100       4184 if (defined(my $variant = $options->{variant})) {
178 21         49 $variant = "$template+$variant";
179 21   100     94 my $handlers = $self->{templates}{$variant} // [];
180 21 100 100     119 $template = $variant if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
  10   100     44  
181             }
182              
183 2004 100       9670 return defined $handler ? "$template.$handler" : $template;
184             }
185              
186             sub template_path {
187 383     3831 1 860 my ($self, $options) = @_;
188 383 50       903 return undef unless my $name = $self->template_name($options);
189 383         1720 my @parts = split /\//, $name;
190 383   100     866 -r and return $_ for map { path($_, @parts)->to_string } @{$self->paths}, $TEMPLATES;
  770         2269  
  383         1106  
191 332         3310 return undef;
192             }
193              
194             sub warmup {
195 55     3072 1 176 my $self = shift;
196              
197 55         452 my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
198              
199             # Handlers for templates
200 55         141 for my $path (@{$self->paths}, $TEMPLATES) {
  55         256  
201 763         5939 s/\.(\w+)$// and push @{$templates->{$_}}, $1
202 111   33 3349   529 for path($path)->list_tree->map(sub { join '/', @{$_->to_rel($path)} })->each;
  763         1129  
  763         1707  
203             }
204              
205             # Handlers and classes for DATA templates
206 55         181 for my $class (reverse @{$self->classes}) {
  55         299  
207 81         193 $index->{$_} = $class for my @keys = sort keys %{data_section $class};
  81         303  
208 81   66     768 s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
  202         1487  
209             }
210             }
211              
212 612 100   2336   2615 sub _maybe { $_[0] ? encode @_ : $_[1] }
213              
214             sub _next {
215 375     1668   685 my $stash = shift;
216 375 100       1089 return delete $stash->{extends} if $stash->{extends};
217 362 100       1805 return undef unless my $layout = delete $stash->{layout};
218 33         210 return join '/', 'layouts', $layout;
219             }
220              
221             sub _render_template {
222 576     1007   1402 my ($self, $c, $output, $options) = @_;
223              
224 576   100     2265 my $handler = $options->{handler} ||= $self->template_handler($options);
225 576 100       1441 return undef unless $handler;
226             $c->helpers->log->error(qq{No handler for "$handler" found}) and return undef
227 575 50 0     1729 unless my $renderer = $self->handlers->{$handler};
228              
229 575         2558 $renderer->($self, $c, $output, $options);
230 555 100       3528 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