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   388 use Mojo::Base -base;
  50         153  
  50         392  
3              
4 50     983   367 use Carp qw(croak);
  50         166  
  50         2343  
5 50     983   22027 use Mojo::Cache;
  50         166  
  50         417  
6 50     481   398 use Mojo::DynamicMethods;
  50         157  
  50         321  
7 50     481   365 use Mojo::File qw(curfile path);
  50         138  
  50         2547  
8 50     481   344 use Mojo::JSON qw(encode_json);
  50         146  
  50         2176  
9 50     481   329 use Mojo::Loader qw(data_section);
  50         143  
  50         2410  
10 50     481   333 use Mojo::Util qw(decamelize deprecated encode gzip md5_sum monkey_patch);
  50         166  
  50         153376  
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   15305 sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
  38         244  
26              
27             sub accepts {
28 112     112 1 235 my ($self, $c) = (shift, shift);
29              
30             # DEPRECATED!
31 112         356 my $req = $c->req;
32 112         511 my $param = $req->param('format');
33 112 50       384 deprecated 'The ?format=* parameter is deprecated in favor of ?_format=* for content negotiation' if defined $param;
34              
35             # List representations
36 112   100     489 my $fmt = $param // $req->param('_format') || $c->stash->{format};
37 112 100       422 my @exts = $fmt ? ($fmt) : ();
38 112         205 push @exts, @{$c->app->types->detect($req->headers->accept)};
  112         369  
39 112 100       652 return \@exts unless @_;
40              
41             # Find best representation
42 29   100     84 for my $ext (@exts) { $ext eq $_ and return $ext for @_ }
  13         102  
43 22 100       127 return @exts ? undef : shift;
44             }
45              
46 231 50   231 1 820 sub add_handler { $_[0]->handlers->{$_[1]} = $_[2] and return $_[0] }
47              
48             sub add_helper {
49 8787     8787 1 17300 my ($self, $name, $cb) = @_;
50              
51 8787         17670 $self->helpers->{$name} = $cb;
52 8787         16233 delete $self->{proxy};
53 8787 100       28587 $cb = $self->get_helper($name) if $name =~ s/\..*$//;
54 8787         22895 Mojo::DynamicMethods::register $_, $self, $name, $cb for qw(Mojolicious Mojolicious::Controller);
55              
56 8787         29031 return $self;
57             }
58              
59             sub get_data_template {
60 332     332 1 907 my ($self, $options) = @_;
61 332 50       829 return undef unless my $template = $self->template_name($options);
62 332         2078 return data_section $self->{index}{$template}, $template;
63             }
64              
65             sub get_helper {
66 24223     24223 1 53645 my ($self, $name) = @_;
67              
68 24223 100 100     79221 if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
  21897         46481  
69              
70 2326         3919 my $found;
71 2326         12802 my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
72 2326 100       23828 my $re = length $name ? qr/^(\Q$name\E\.([^.]+))/ : qr/^(([^.]+))/;
73 2326         4497 for my $key (keys %{$self->helpers}) {
  2326         9979  
74 100863 100       292072 $key =~ $re ? ($found, my $method) = (1, $2) : next;
75 17501         37469 my $sub = $self->get_helper($1);
76 17501     5824   69072 monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
  5824     5478   8508  
  5824     5478   17913  
        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       8953 $found ? push @{$self->{namespaces}}, $class : return undef;
  2326         6090  
80 2326     10196   11795 return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
  5569         26220  
81             }
82              
83             sub render {
84 958     5050 1 2018 my ($self, $c) = @_;
85              
86 958         2174 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         3335 };
93 958         2743 my $inline = $options->{inline} = delete $stash->{inline};
94 958 100 66     2500 $options->{handler} //= $self->default_handler if defined $inline;
95 958   66     3680 $options->{format} = $stash->{format} || $self->default_format;
96              
97             # Data
98 958 100       2801 return delete $stash->{data}, $options->{format} if defined $stash->{data};
99              
100             # Text
101 889 100       2791 return _maybe($options->{encoding}, delete $stash->{text}), $options->{format} if defined $stash->{text};
102              
103             # JSON
104 583 100       1710 return encode_json(delete $stash->{json}), 'json' if exists $stash->{json};
105              
106             # Template or templateless handler
107 532   100     2218 $options->{template} //= $self->template_for($c);
108 532 100       1901 return () unless $self->_render_template($c, \my $output, $options);
109              
110             # Inheritance
111 331   100     1816 my $content = $stash->{'mojo.content'} //= {};
112 331 100 100     1901 local $content->{content} = $output =~ /\S/ ? $output : undef if $stash->{extends} || $stash->{layout};
    100          
113 331   100     1047 while ((my $next = _next($stash)) && !defined $inline) {
114 44         151 @$options{qw(handler template)} = ($stash->{handler}, $next);
115 44   66     168 $options->{format} = $stash->{format} || $self->default_format;
116 44 50       149 if ($self->_render_template($c, \my $tmp, $options)) { $output = $tmp }
  44         90  
117 44 100 66     339 $content->{content} //= $output if $output =~ /\S/;
118             }
119              
120 331 100       949 return $output if $stash->{'mojo.string'};
121 306         892 return _maybe($options->{encoding}, $output), $options->{format};
122             }
123              
124             sub respond {
125 732     4322 1 3206 my ($self, $c, $output, $format, $status) = @_;
126              
127 732 50       2067 croak 'A response has already been rendered' if $c->stash->{'mojo.respond'}++;
128              
129             # Gzip compression
130 732         2469 my $res = $c->res;
131 732 100 66     2444 if ($self->compress && length($output) >= $self->min_compress_size) {
132 48         3627 my $headers = $res->headers;
133 48         301 $headers->append(Vary => 'Accept-Encoding');
134 48   100     208 my $gzip = ($c->req->headers->accept_encoding // '') =~ /gzip/i;
135 48 100 66     311 if ($gzip && !$headers->content_encoding) {
136 45         163 $headers->content_encoding('gzip');
137 45         224 $output = gzip $output;
138             }
139             }
140              
141 732         3739 $res->body($output);
142 732         2255 $c->app->types->content_type($c, {ext => $format});
143 732         3280 return !!$c->rendered($status);
144             }
145              
146             sub template_for {
147 207     3726 1 537 my ($self, $c) = @_;
148              
149             # Normal default template
150 207         563 my $stash = $c->stash;
151 207         712 my ($controller, $action) = @$stash{qw(controller action)};
152 207 100 100     713 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       560 return undef unless my $route = $c->match->endpoint;
156 200         823 return $route->name;
157             }
158              
159             sub template_handler {
160 454     3902 1 983 my ($self, $options) = @_;
161 454 100       1218 return undef unless my $file = $self->template_name($options);
162 453 100       1931 return $self->default_handler unless my $handlers = $self->{templates}{$file};
163 273         1161 return $handlers->[0];
164             }
165              
166             sub template_name {
167 2005     5453 1 3712 my ($self, $options) = @_;
168              
169 2005 100       4872 return undef unless defined(my $template = $options->{template});
170 2004 50       4070 return undef unless my $format = $options->{format};
171 2004         3785 $template .= ".$format";
172              
173 2004 100       4375 $self->warmup unless $self->{templates};
174              
175             # Variants
176 2004         3159 my $handler = $options->{handler};
177 2004 100       4166 if (defined(my $variant = $options->{variant})) {
178 21         53 $variant = "$template+$variant";
179 21   100     74 my $handlers = $self->{templates}{$variant} // [];
180 21 100 100     127 $template = $variant if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
  10   100     106  
181             }
182              
183 2004 100       8934 return defined $handler ? "$template.$handler" : $template;
184             }
185              
186             sub template_path {
187 383     3831 1 855 my ($self, $options) = @_;
188 383 50       849 return undef unless my $name = $self->template_name($options);
189 383         1618 my @parts = split /\//, $name;
190 383   100     712 -r and return $_ for map { path($_, @parts)->to_string } @{$self->paths}, $TEMPLATES;
  770         2110  
  383         1139  
191 332         3186 return undef;
192             }
193              
194             sub warmup {
195 55     3072 1 153 my $self = shift;
196              
197 55         487 my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
198              
199             # Handlers for templates
200 55         139 for my $path (@{$self->paths}, $TEMPLATES) {
  55         232  
201 763         5832 s/\.(\w+)$// and push @{$templates->{$_}}, $1
202 111   33 3349   439 for path($path)->list_tree->map(sub { join '/', @{$_->to_rel($path)} })->each;
  763         1130  
  763         1670  
203             }
204              
205             # Handlers and classes for DATA templates
206 55         150 for my $class (reverse @{$self->classes}) {
  55         273  
207 81         210 $index->{$_} = $class for my @keys = sort keys %{data_section $class};
  81         297  
208 81   66     732 s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
  202         1439  
209             }
210             }
211              
212 612 100   2336   2499 sub _maybe { $_[0] ? encode @_ : $_[1] }
213              
214             sub _next {
215 375     1668   656 my $stash = shift;
216 375 100       1077 return delete $stash->{extends} if $stash->{extends};
217 362 100       1713 return undef unless my $layout = delete $stash->{layout};
218 33         210 return join '/', 'layouts', $layout;
219             }
220              
221             sub _render_template {
222 576     1007   1352 my ($self, $c, $output, $options) = @_;
223              
224 576   100     2219 my $handler = $options->{handler} ||= $self->template_handler($options);
225 576 100       1386 return undef unless $handler;
226             $c->helpers->log->error(qq{No handler for "$handler" found}) and return undef
227 575 50 0     1592 unless my $renderer = $self->handlers->{$handler};
228              
229 575         2354 $renderer->($self, $c, $output, $options);
230 555 100       3441 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